= EMPTY GEOMETRY Handling = The semantics of NULL are well defined for us already in SQL standards. However, in the world of Geometry / Geography we have another entity which is not quite NULL nor quite fully defined: the empty geometry. Empty geometries can be created with things like intersection calls that have have no intersection, e.g. ST_Intersection('POINT(0 0)', 'POINT(1 1)') == POINT EMPTY. So, in the presence of empty, how should functions behave? Ideally ''GEOMETRY EMPTY'' should behave more like 'zero' (0) than NULL - it is an expected, valid result of a successful, valid operation. In other words, dealing with ''EMPTY'' should require as little "special case" handling as possible in logical expressions making use of multiple geometry operations and/or comparisons. Maintaining compatibility with other spatial databases is probably valuable in its own right, and also because they have probably already done some thinking around these issues. * ST_Buffer(empty, tolerance) == empty * ST_Union(geometry, empty) == geometry * ST_Union(empty, empty) == empty * ST_Intersection(geometry, empty) == geometry * fabian thinks this should be empty * In set theory, A ∩ ∅ = ∅ * strk agrees with fabian * ST_Intersection(empty, empty) == empty * ST_Difference(geometry, empty) == geometry * ST_Difference(empty, geometry) == empty * ST_Distance(geometry, empty) == NULL * ST_DWithin(geometry, empty, tolerance) == FALSE * chodgson thinks this should be null. It reduces to: * ST_Distance( geometry, empty ) < tolerance * NULL < tolerance * NULL (by the SQL standard) * ST_Contains(geometry, empty) == FALSE (SQL Server says False) * fabian thinks this should be TRUE * In set theory, every set contains ∅ * strk agrees with fabian * ST_Within(geometry, empty) == FALSE (SQL Server says False) * ST_Contains(empty, geometry) == FALSE (SQL Server says False) * ST_Within(empty, geometry) == FALSE (SQL Server says False) * fabian thinks this should be TRUE * In set theory, every set contains ∅ * strk agrees with fabian * ST_Contains(empty, empty) == FALSE (SQL Server says False) * ST_Intersects(geometry, empty) == FALSE * ST_Intersects(empty, empty) == FALSE (SQL Server says False) * ST_Disjoint(empty, empty) == TRUE (SQL Server says True) * ST_Disjoint(geometry, empty) == TRUE (SQL Server says True) * ST_Equals(empty, empty) == TRUE (SQL Servers says False, Oracle says True) * ST_IsSimple(empty) == TRUE * ST_IsValid(empty) == TRUE * ST_NumGeometries(empty) == 0 * ST_NRings(empty) == 0 * ST_NumPoints(empty) == 0 * ST_GeometryN(empty, n) == NULL * ST_InteriorRingN(empty, n) == NULL * ST_ExteriorRing(empty) == empty * ST_Area(empty) == 0 * ST_Length(empty) == 0 ''Collections:'' When working against collections that might contain both empty and non-empty members, if there is any non-empty member, ignore all the empty members and treat the collection as if the empty portions are not there. If the collection is entirely made of empty components, behave as above. ''Note:'' It might make sense to try to maintain the geometric truisms when specifying the semantics around ''empty'' - ie. things like: * intersects( A, B ) => !disjoint( A, B ) * A == B => intersects( A, B ) * contains( A, B ) && contains( B, A ) => A == B If we don't maintain these we will probably need to complicate logical comparisons with special cases for ''empty'' - it may be the case that this is necessary, but it would be nice if it just 'came out in the wash'. This suggests to me that perhaps: * ST_Intersects( empty, empty ) == TRUE * ST_Disjoint( empty, empty ) == FALSE * ST_Distance( empty, empty) == 0 * ST_DWithin( empty, empty, tolerance) == true Hmmm.. perhaps this isn't even possible generally, as: * intersects( A, B ) => intersection( A, B ) != empty but I think that we certainly want: * intersection( empty, empty) == empty ''Operators'' * EMPTY ~= EMPTY == TRUE * EMPTY && EMPTY == FALSE == SQL Server 2008 == For reference and comparison, courtesy of Robe: {{{ SELECT Geometry::STGeomFromText('POINT(1 2)',4326).STUnion(Geometry::STGeomFromText('POLYGON EMPTY',4326)) => POINT (1 2) SELECT (Geometry::STGeomFromText('POLYGON EMPTY',4326).STUnion(Geometry::STGeomFromText('POLYGON EMPTY',4326))).STAsText() => GEOMETRYCOLLECTION EMPTY SELECT (Geometry::STGeomFromText('POINT(1 3)',4326).STDifference(Geometry::STGeomFromText('POLYGON EMPTY',4326))).STAsText() => POINT (1 3) SELECT (Geometry::STGeomFromText('POLYGON EMPTY',4326).STDifference(Geometry::STGeomFromText('POLYGON EMPTY',4326))).STAsText() => GEOMETRY COLLECTION EMPTY SELECT (Geometry::STGeomFromText('POINT(1 3)',4326).STContains(Geometry::STGeomFromText('POLYGON EMPTY',4326))) => 0 SELECT (Geometry::STGeomFromText('POLYGON EMPTY',4326).STContains(Geometry::STGeomFromText('POLYGON EMPTY',4326))) => 0 SELECT (Geometry::STGeomFromText('POINT(1 3)',4326).STIntersects(Geometry::STGeomFromText('POLYGON EMPTY',4326))) => 0 SELECT (Geometry::STGeomFromText('POLYGON EMPTY',4326).STIntersects(Geometry::STGeomFromText('POLYGON EMPTY',4326))) => 0 SELECT (Geometry::STGeomFromText('POLYGON EMPTY',4326).STDisjoint(Geometry::STGeomFromText('POLYGON EMPTY',4326))) => 1 SELECT (Geometry::STGeomFromText('POINT(1 3)',4326).STDisjoint(Geometry::STGeomFromText('POLYGON EMPTY',4326))) => 1 SELECT Geometry::STGeomFromText('POLYGON EMPTY',4326).STIsSimple() => 1 SELECT Geometry::STGeomFromText('POLYGON EMPTY',4326).STIsValid() => 1 SELECT (Geometry::STGeomFromText('POLYGON EMPTY',4326).STDistance(Geometry::STGeomFromText('POLYGON EMPTY',4326))) => NULL SELECT (Geometry::STGeomFromText('POLYGON EMPTY',4326).STIntersection(Geometry::STGeomFromText('POLYGON EMPTY',4326))).STAsText() => GEOMETRY COLLECTION EMPTY --geometry contains empty SELECT Geometry::STGeomFromText('POINT(1 2)',4326).STContains(Geometry::STGeomFromText('POLYGON EMPTY',4326)) => 0 --geometry within empty SELECT Geometry::STGeomFromText('POINT(1 2)',4326).STWithin(Geometry::STGeomFromText('POLYGON EMPTY',4326)) => 0 --empty contains geometry SELECT Geometry::STGeomFromText('POLYGON EMPTY',4326).STContains(Geometry::STGeomFromText('POINT(1 2)',4326)) => 0 --empty within geometry SELECT Geometry::STGeomFromText('POLYGON EMPTY',4326).STWithin(Geometry::STGeomFromText('POINT(1 2)',4326)) => 0 SELECT Geometry::STGeomFromText('POINT(1 2)',4326).STContains(Geometry::STGeomFromText('POLYGON EMPTY',4326)) => 0 --geometry within empty SELECT Geometry::STGeomFromText('POINT(1 2)',4326).STWithin(Geometry::STGeomFromText('POLYGON EMPTY',4326)) => 0 --empty contains geometry SELECT Geometry::STGeomFromText('POLYGON EMPTY',4326).STContains(Geometry::STGeomFromText('POINT(1 2)',4326)) => 0 --empty within geometry SELECT Geometry::STGeomFromText('POLYGON EMPTY',4326).STWithin(Geometry::STGeomFromText('POINT(1 2)',4326)) => 0 -- empty relation to empty (evidentally its FFFFFFFF2) -- in otherwords exteriors, interiors and boundaries do not intersect except for exterior x exterior and that intersection is 2-d (infinite space) SELECT Geometry::STGeomFromText('POLYGON EMPTY',4326).STRelate(Geometry::STGeomFromText('POLYGON EMPTY',4326),'FFFFFFFF2') => 1 }}}