From d86802f13fd821f006371b95d888d294297874fd Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 23 Sep 2024 18:21:02 +0100 Subject: Fixed #35783 -- Added NumDimensions GIS database function and __num_dimensions lookup. --- django/contrib/gis/db/backends/base/operations.py | 1 + django/contrib/gis/db/backends/mysql/operations.py | 1 + .../contrib/gis/db/backends/oracle/operations.py | 1 + .../contrib/gis/db/backends/postgis/operations.py | 1 + .../gis/db/backends/spatialite/operations.py | 1 + django/contrib/gis/db/models/functions.py | 7 ++++++ docs/ref/contrib/gis/db-api.txt | 2 ++ docs/ref/contrib/gis/functions.txt | 13 +++++++++++ docs/ref/contrib/gis/geoquerysets.txt | 22 +++++++++++++++++++ docs/releases/6.1.txt | 5 +++++ tests/gis_tests/geoapp/test_functions.py | 25 +++++++++++++++++++++- 11 files changed, 78 insertions(+), 1 deletion(-) diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py index b3bf4383ad..e468c6f610 100644 --- a/django/contrib/gis/db/backends/base/operations.py +++ b/django/contrib/gis/db/backends/base/operations.py @@ -62,6 +62,7 @@ class BaseSpatialOperations: "LineLocatePoint", "MakeValid", "MemSize", + "NumDimensions", "NumGeometries", "NumPoints", "Perimeter", diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index b82bd16abb..f48e05b67d 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -98,6 +98,7 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations): "LineLocatePoint", "MakeValid", "MemSize", + "NumDimensions", "Perimeter", "PointOnSurface", "Reverse", diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 467fc530b3..1c09281809 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -132,6 +132,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations): "LineLocatePoint", "MakeValid", "MemSize", + "NumDimensions", "Rotate", "Scale", "SnapToGrid", diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 5a403fd7fe..e0a6dee7be 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -176,6 +176,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): "BoundingCircle": "ST_MinimumBoundingCircle", "FromWKB": "ST_GeomFromWKB", "FromWKT": "ST_GeomFromText", + "NumDimensions": "ST_NDims", "NumPoints": "ST_NPoints", "GeometryType": "GeometryType", } diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 54ec249f07..51a04d5c50 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -76,6 +76,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): "IsEmpty": "ST_IsEmpty", "Length": "ST_Length", "LineLocatePoint": "ST_Line_Locate_Point", + "NumDimensions": "ST_NDims", "NumPoints": "ST_NPoints", "Reverse": "ST_Reverse", "Scale": "ScaleCoords", diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index b6ff35858f..54505b136a 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -521,6 +521,13 @@ class MemSize(GeoFunc): arity = 1 +@BaseSpatialField.register_lookup +class NumDimensions(GeoFuncMixin, Transform): + lookup_name = "num_dimensions" + output_field = IntegerField() + arity = 1 + + class NumGeometries(GeoFunc): output_field = IntegerField() arity = 1 diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index b51001ecd1..8f062e503d 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -365,6 +365,7 @@ Lookup Type PostGIS Oracle MariaDB MySQL [#]_ :lookup:`intersects` X X X X X B :lookup:`isempty` X X :lookup:`isvalid` X X X (≥ 12.0.1) X X +:lookup:`num_dimensions` X X :lookup:`overlaps` X X X X X B :lookup:`relate` X X X X C :lookup:`same_as` X X X X X B @@ -420,6 +421,7 @@ Function PostGIS Oracle MariaDB MySQL :class:`LineLocatePoint` X X :class:`MakeValid` X X (LWGEOM/RTTOPO) :class:`MemSize` X +:class:`NumDimensions` X X :class:`NumGeometries` X X X X X :class:`NumPoints` X X X X X :class:`Perimeter` X X X diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index ab540627eb..2c4d344867 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -669,6 +669,19 @@ Accepts a geographic field or expression and returns its geometry type. Accepts a single geographic field or expression and returns the memory size (number of bytes) that the geometry field takes. +``NumDimensions`` +----------------- + +.. versionadded:: 6.1 + +.. class:: NumDimensions(expression, **extra) + +*Availability*: `PostGIS `__, +SpatiaLite + +Accepts a single geometry field or expression and returns the number of +dimensions used by the geometry. + ``NumGeometries`` ----------------- diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index 6109bafb4f..70ac9d4d28 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -425,6 +425,28 @@ Oracle ``SDO_GEOMETRY.GET_GTYPE(geom)`` SpatiaLite ``GeometryType(geom)`` ========== ========================== +.. fieldlookup:: num_dimensions + +``num_dimensions`` +------------------ + +.. versionadded:: 6.1 + +*Availability*: `PostGIS `__, +SpatiaLite + +Returns the number of dimensions used by the geometry. + +Example:: + + Zipcode.objects.filter(geom__num_dimensions=3) + +=================== ================== +Backend SQL Equivalent +=================== ================== +PostGIS, SpatiaLite ``ST_NDims(geom)`` +=================== ================== + .. fieldlookup:: overlaps ``overlaps`` diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt index ae588a3fdb..0d1b6ccdca 100644 --- a/docs/releases/6.1.txt +++ b/docs/releases/6.1.txt @@ -118,6 +118,11 @@ Minor features :class:`IsEmpty() ` database function are now supported on SpatiaLite. +* The new :lookup:`num_dimensions` lookup and :class:`NumDimensions() + ` database function + allow filtering geometries by the number of dimensions on PostGIS and + SpatiaLite. + :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 917e63cd57..a6b072542c 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -17,7 +17,7 @@ from django.contrib.gis.geos import ( from django.contrib.gis.geos.libgeos import geos_version_tuple from django.contrib.gis.measure import Area from django.db import NotSupportedError, connection -from django.db.models import IntegerField, Sum, Value +from django.db.models import F, IntegerField, Sum, Value from django.test import TestCase, skipUnlessDBFeature from ..utils import FuncTestMixin @@ -28,6 +28,7 @@ from .models import ( Feature, ManyPointModel, State, + ThreeDimensionalFeature, Track, ) @@ -608,6 +609,28 @@ class GISFunctionsTests(FuncTestMixin, TestCase): else: self.assertEqual(1, city.num_geom) + @skipUnlessDBFeature("has_NumDimensions_function") + def test_num_dimensions(self): + for c in Country.objects.annotate(num_dims=functions.NumDimensions("mpoly")): + self.assertEqual(2, c.num_dims) + + ThreeDimensionalFeature.objects.create( + name="London", geom=Point(-0.126418, 51.500832, 0) + ) + qs = ThreeDimensionalFeature.objects.annotate( + num_dims=functions.NumDimensions("geom") + ) + self.assertEqual(qs[0].num_dims, 3) + + qs = ThreeDimensionalFeature.objects.annotate( + num_dims=F("geom__num_dimensions") + ) + self.assertEqual(qs[0].num_dims, 3) + + msg = "'NumDimensions' takes exactly 1 argument (2 given)" + with self.assertRaisesMessage(TypeError, msg): + Country.objects.annotate(num_dims=functions.NumDimensions("point", "error")) + @skipUnlessDBFeature("has_NumPoints_function") def test_num_points(self): coords = [(-95.363151, 29.763374), (-95.448601, 29.713803)] -- cgit v1.3