Browse Source

Fixed #33783 -- Added IsEmpty GIS database function and __isempty lookup on PostGIS.

Claude Paroz 2 years ago
parent
commit
2a14b8df39

+ 1 - 0
django/contrib/gis/db/backends/base/operations.py

@@ -48,6 +48,7 @@ class BaseSpatialOperations:
         "GeoHash",
         "GeometryDistance",
         "Intersection",
+        "IsEmpty",
         "IsValid",
         "Length",
         "LineLocatePoint",

+ 1 - 0
django/contrib/gis/db/backends/mysql/operations.py

@@ -72,6 +72,7 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
             "BoundingCircle",
             "ForcePolygonCW",
             "GeometryDistance",
+            "IsEmpty",
             "LineLocatePoint",
             "MakeValid",
             "MemSize",

+ 1 - 0
django/contrib/gis/db/backends/oracle/operations.py

@@ -122,6 +122,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
         "ForcePolygonCW",
         "GeoHash",
         "GeometryDistance",
+        "IsEmpty",
         "LineLocatePoint",
         "MakeValid",
         "MemSize",

+ 1 - 1
django/contrib/gis/db/backends/spatialite/operations.py

@@ -78,7 +78,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
 
     @cached_property
     def unsupported_functions(self):
-        unsupported = {"BoundingCircle", "GeometryDistance", "MemSize"}
+        unsupported = {"BoundingCircle", "GeometryDistance", "IsEmpty", "MemSize"}
         if not self.geom_lib_version():
             unsupported |= {"Azimuth", "GeoHash", "MakeValid"}
         return unsupported

+ 6 - 0
django/contrib/gis/db/models/functions.py

@@ -381,6 +381,12 @@ class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
     geom_param_pos = (0, 1)
 
 
+@BaseSpatialField.register_lookup
+class IsEmpty(GeoFuncMixin, Transform):
+    lookup_name = "isempty"
+    output_field = BooleanField()
+
+
 @BaseSpatialField.register_lookup
 class IsValid(OracleToleranceMixin, GeoFuncMixin, Transform):
     lookup_name = "isvalid"

+ 2 - 0
docs/ref/contrib/gis/db-api.txt

@@ -316,6 +316,7 @@ Lookup Type                        PostGIS    Oracle   MariaDB   MySQL [#]_   Sp
 :lookup:`equals`                   X          X        X         X            X          C
 :lookup:`exact <same_as>`          X          X        X         X            X          B
 :lookup:`intersects`               X          X        X         X            X          B
+:lookup:`isempty`                  X
 :lookup:`isvalid`                  X          X                  X            X
 :lookup:`overlaps`                 X          X        X         X            X          B
 :lookup:`relate`                   X          X        X                      X          C
@@ -361,6 +362,7 @@ Function                              PostGIS  Oracle         MariaDB      MySQL
 :class:`ForcePolygonCW`               X                                                X
 :class:`GeoHash`                      X                                    X           X (LWGEOM/RTTOPO)
 :class:`Intersection`                 X        X              X            X           X
+:class:`IsEmpty`                      X
 :class:`IsValid`                      X        X                           X           X
 :class:`Length`                       X        X              X            X           X
 :class:`LineLocatePoint`              X                                                X

+ 17 - 5
docs/ref/contrib/gis/functions.txt

@@ -23,11 +23,11 @@ Function's summary:
 =========================  ========================  ======================  =======================  ==================  =====================
 Measurement                Relationships             Operations              Editors                  Output format       Miscellaneous
 =========================  ========================  ======================  =======================  ==================  =====================
-:class:`Area`              :class:`Azimuth`          :class:`Difference`     :class:`ForcePolygonCW`  :class:`AsGeoJSON`  :class:`IsValid`
-:class:`Distance`          :class:`BoundingCircle`   :class:`Intersection`   :class:`MakeValid`       :class:`AsGML`      :class:`MemSize`
-:class:`GeometryDistance`  :class:`Centroid`         :class:`SymDifference`  :class:`Reverse`         :class:`AsKML`      :class:`NumGeometries`
-:class:`Length`            :class:`Envelope`         :class:`Union`          :class:`Scale`           :class:`AsSVG`      :class:`NumPoints`
-:class:`Perimeter`         :class:`LineLocatePoint`                          :class:`SnapToGrid`      :class:`AsWKB`
+:class:`Area`              :class:`Azimuth`          :class:`Difference`     :class:`ForcePolygonCW`  :class:`AsGeoJSON`  :class:`IsEmpty`
+:class:`Distance`          :class:`BoundingCircle`   :class:`Intersection`   :class:`MakeValid`       :class:`AsGML`      :class:`IsValid`
+:class:`GeometryDistance`  :class:`Centroid`         :class:`SymDifference`  :class:`Reverse`         :class:`AsKML`      :class:`MemSize`
+:class:`Length`            :class:`Envelope`         :class:`Union`          :class:`Scale`           :class:`AsSVG`      :class:`NumGeometries`
+:class:`Perimeter`         :class:`LineLocatePoint`                          :class:`SnapToGrid`      :class:`AsWKB`      :class:`NumPoints`
 ..                         :class:`PointOnSurface`                           :class:`Transform`       :class:`AsWKT`
 ..                                                                           :class:`Translate`       :class:`GeoHash`
 =========================  ========================  ======================  =======================  ==================  =====================
@@ -368,6 +368,18 @@ it provides index-assisted nearest-neighbor result sets.
 Accepts two geographic fields or expressions and returns the geometric
 intersection between them.
 
+``IsEmpty``
+===========
+
+.. versionadded:: 4.2
+
+.. class:: IsEmpty(expr)
+
+*Availability*: `PostGIS <https://postgis.net/docs/ST_IsEmpty.html>`__
+
+Accepts a geographic field or expression and tests if the value is an empty
+geometry. Returns ``True`` if its value is empty and ``False`` otherwise.
+
 ``IsValid``
 ===========
 

+ 15 - 0
docs/ref/contrib/gis/geoquerysets.txt

@@ -346,6 +346,21 @@ MySQL       ``ST_Intersects(poly, geom)``
 SpatiaLite  ``Intersects(poly, geom)``
 ==========  =================================================
 
+.. fieldlookup:: isempty
+
+``isempty``
+-----------
+
+.. versionadded:: 4.2
+
+*Availability*: `PostGIS <https://postgis.net/docs/ST_IsEmpty.html>`__
+
+Tests if the geometry is empty.
+
+Example::
+
+    Zipcode.objects.filter(poly__isempty=True)
+
 .. fieldlookup:: isvalid
 
 ``isvalid``

+ 4 - 0
docs/releases/4.2.txt

@@ -155,6 +155,10 @@ Minor features
 * :class:`~django.contrib.gis.forms.widgets.OpenLayersWidget` is now based on
   OpenLayers 7.2.2 (previously 4.6.5).
 
+* The new :lookup:`isempty` lookup and
+  :class:`IsEmpty() <django.contrib.gis.db.models.functions.IsEmpty>`
+  expression allow filtering empty geometries on PostGIS.
+
 :mod:`django.contrib.messages`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 12 - 0
tests/gis_tests/geoapp/test_functions.py

@@ -371,6 +371,18 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
             else:
                 self.assertIs(c.inter.empty, True)
 
+    @skipUnlessDBFeature("supports_empty_geometries", "has_IsEmpty_function")
+    def test_isempty(self):
+        empty = City.objects.create(name="Nowhere", point=Point(srid=4326))
+        City.objects.create(name="Somewhere", point=Point(6.825, 47.1, srid=4326))
+        self.assertSequenceEqual(
+            City.objects.annotate(isempty=functions.IsEmpty("point")).filter(
+                isempty=True
+            ),
+            [empty],
+        )
+        self.assertSequenceEqual(City.objects.filter(point__isempty=True), [empty])
+
     @skipUnlessDBFeature("has_IsValid_function")
     def test_isvalid(self):
         valid_geom = fromstr("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))")