summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2022-12-31 15:32:35 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-01-03 05:47:44 +0100
commit2a14b8df39b573124ea42dec0ce96147c8e767d4 (patch)
treec18003040c58d555c245ab1fe5e1e3085754eade
parent6774e9359cbdbf7b8c448597c103288ac381519a (diff)
Fixed #33783 -- Added IsEmpty GIS database function and __isempty lookup on PostGIS.
-rw-r--r--django/contrib/gis/db/backends/base/operations.py1
-rw-r--r--django/contrib/gis/db/backends/mysql/operations.py1
-rw-r--r--django/contrib/gis/db/backends/oracle/operations.py1
-rw-r--r--django/contrib/gis/db/backends/spatialite/operations.py2
-rw-r--r--django/contrib/gis/db/models/functions.py6
-rw-r--r--docs/ref/contrib/gis/db-api.txt2
-rw-r--r--docs/ref/contrib/gis/functions.txt22
-rw-r--r--docs/ref/contrib/gis/geoquerysets.txt15
-rw-r--r--docs/releases/4.2.txt4
-rw-r--r--tests/gis_tests/geoapp/test_functions.py12
10 files changed, 60 insertions, 6 deletions
diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py
index e7bffb11b4..f6eaf8f503 100644
--- a/django/contrib/gis/db/backends/base/operations.py
+++ b/django/contrib/gis/db/backends/base/operations.py
@@ -48,6 +48,7 @@ class BaseSpatialOperations:
"GeoHash",
"GeometryDistance",
"Intersection",
+ "IsEmpty",
"IsValid",
"Length",
"LineLocatePoint",
diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py
index 6d04874537..46d8fc5d6a 100644
--- a/django/contrib/gis/db/backends/mysql/operations.py
+++ b/django/contrib/gis/db/backends/mysql/operations.py
@@ -72,6 +72,7 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
"BoundingCircle",
"ForcePolygonCW",
"GeometryDistance",
+ "IsEmpty",
"LineLocatePoint",
"MakeValid",
"MemSize",
diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py
index ba7e3ca4d8..aefb2f74e2 100644
--- a/django/contrib/gis/db/backends/oracle/operations.py
+++ b/django/contrib/gis/db/backends/oracle/operations.py
@@ -122,6 +122,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
"ForcePolygonCW",
"GeoHash",
"GeometryDistance",
+ "IsEmpty",
"LineLocatePoint",
"MakeValid",
"MemSize",
diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py
index 8003fcb6c6..47f3d4ca75 100644
--- a/django/contrib/gis/db/backends/spatialite/operations.py
+++ b/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
diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py
index 5f6c7b5cfd..f97c540a1a 100644
--- a/django/contrib/gis/db/models/functions.py
+++ b/django/contrib/gis/db/models/functions.py
@@ -382,6 +382,12 @@ class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
@BaseSpatialField.register_lookup
+class IsEmpty(GeoFuncMixin, Transform):
+ lookup_name = "isempty"
+ output_field = BooleanField()
+
+
+@BaseSpatialField.register_lookup
class IsValid(OracleToleranceMixin, GeoFuncMixin, Transform):
lookup_name = "isvalid"
output_field = BooleanField()
diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt
index bc216c7caf..97f1f6ca6b 100644
--- a/docs/ref/contrib/gis/db-api.txt
+++ b/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
diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt
index 00c48e665e..1c062515c6 100644
--- a/docs/ref/contrib/gis/functions.txt
+++ b/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``
===========
diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt
index 760d508b17..53a8b6c3b9 100644
--- a/docs/ref/contrib/gis/geoquerysets.txt
+++ b/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``
diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt
index 6d2fb32644..40ca5ba1a7 100644
--- a/docs/releases/4.2.txt
+++ b/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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py
index e1a66d573e..535e552aa1 100644
--- a/tests/gis_tests/geoapp/test_functions.py
+++ b/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))")