Version 31 (modified by wiki, 3 years ago)

--

# 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_Intersect('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_Union(geometry, empty) == geometry
• ST_Union(empty, empty) == empty
• ST_Intersection(geometry, empty) == geometry
• fabian thinks this should be empty
• In set theory, A ∩ ∅ = ∅
• 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 ∅
• 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)
• 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_IsSimple(empty) == TRUE
• ST_IsValid(empty) == TRUE
• ST_NumGeometries(empty) == 0
• ST_NumRings(empty) == 0
• ST_NumPoints(empty) == 0
• ST_GeometryN(empty, n) == empty
• ST_RingN(geometry, n) == 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

## 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

```