Skip to content

Commit a5b0a61

Browse files
LeOndazfelixxm
andcommitted
Fixed #28696 -- Added GeometryType GIS database function and __geom_type lookup.
Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com>
1 parent 6aa05fd commit a5b0a61

9 files changed

Lines changed: 192 additions & 54 deletions

File tree

django/contrib/gis/db/backends/base/operations.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def select_extent(self):
5454
"FromWKT",
5555
"GeoHash",
5656
"GeometryDistance",
57+
"GeometryType",
5758
"Intersection",
5859
"IsEmpty",
5960
"IsValid",

django/contrib/gis/db/backends/oracle/operations.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from django.contrib.gis.geos.prototypes.io import wkb_r
1919
from django.contrib.gis.measure import Distance
2020
from django.db.backends.oracle.operations import DatabaseOperations
21+
from django.utils.functional import cached_property
2122

2223
DEFAULT_TOLERANCE = "0.05"
2324

@@ -117,23 +118,28 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
117118
"dwithin": SDODWithin(),
118119
}
119120

120-
unsupported_functions = {
121-
"AsKML",
122-
"AsSVG",
123-
"Azimuth",
124-
"ClosestPoint",
125-
"ForcePolygonCW",
126-
"GeoHash",
127-
"GeometryDistance",
128-
"IsEmpty",
129-
"LineLocatePoint",
130-
"MakeValid",
131-
"MemSize",
132-
"Rotate",
133-
"Scale",
134-
"SnapToGrid",
135-
"Translate",
136-
}
121+
@cached_property
122+
def unsupported_functions(self):
123+
unsupported = {
124+
"AsKML",
125+
"AsSVG",
126+
"Azimuth",
127+
"ClosestPoint",
128+
"ForcePolygonCW",
129+
"GeoHash",
130+
"GeometryDistance",
131+
"IsEmpty",
132+
"LineLocatePoint",
133+
"MakeValid",
134+
"MemSize",
135+
"Rotate",
136+
"Scale",
137+
"SnapToGrid",
138+
"Translate",
139+
}
140+
if self.connection.oracle_version < (23,):
141+
unsupported.add("GeometryType")
142+
return unsupported
137143

138144
def geo_quote_name(self, name):
139145
return super().geo_quote_name(name).upper()

django/contrib/gis/db/backends/postgis/operations.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ def function_names(self):
177177
"FromWKB": "ST_GeomFromWKB",
178178
"FromWKT": "ST_GeomFromText",
179179
"NumPoints": "ST_NPoints",
180+
"GeometryType": "GeometryType",
180181
}
181182
return function_names
182183

django/contrib/gis/db/models/functions.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from django.db.models import (
1010
BinaryField,
1111
BooleanField,
12+
CharField,
1213
FloatField,
1314
Func,
1415
IntegerField,
@@ -422,6 +423,29 @@ class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
422423
geom_param_pos = (0, 1)
423424

424425

426+
@BaseSpatialField.register_lookup
427+
class GeometryType(GeoFuncMixin, Transform):
428+
output_field = CharField()
429+
lookup_name = "geom_type"
430+
431+
def as_oracle(self, compiler, connection, **extra_context):
432+
lhs, params = compiler.compile(self.lhs)
433+
sql = (
434+
"(SELECT DECODE("
435+
f"SDO_GEOMETRY.GET_GTYPE({lhs}),"
436+
"1, 'POINT',"
437+
"2, 'LINESTRING',"
438+
"3, 'POLYGON',"
439+
"4, 'COLLECTION',"
440+
"5, 'MULTIPOINT',"
441+
"6, 'MULTILINESTRING',"
442+
"7, 'MULTIPOLYGON',"
443+
"8, 'SOLID',"
444+
"'UNKNOWN'))"
445+
)
446+
return sql, params
447+
448+
425449
@BaseSpatialField.register_lookup
426450
class IsEmpty(GeoFuncMixin, Transform):
427451
lookup_name = "isempty"

docs/ref/contrib/gis/db-api.txt

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -339,42 +339,43 @@ divided into the three categories described in the :ref:`raster lookup details
339339
<spatial-lookup-raster>`: native support ``N``, bilateral native support ``B``,
340340
and geometry conversion support ``C``.
341341

342-
================================= ========= ======== ============ ============ ========== ========
343-
Lookup Type PostGIS Oracle MariaDB MySQL [#]_ SpatiaLite PGRaster
344-
================================= ========= ======== ============ ============ ========== ========
345-
:lookup:`bbcontains` X X X X N
346-
:lookup:`bboverlaps` X X X X N
347-
:lookup:`contained` X X X X N
348-
:lookup:`contains <gis-contains>` X X X X X B
349-
:lookup:`contains_properly` X B
350-
:lookup:`coveredby` X X X (≥ 12.0.1) X X B
351-
:lookup:`covers` X X X X B
352-
:lookup:`crosses` X X X X C
353-
:lookup:`disjoint` X X X X X B
354-
:lookup:`distance_gt` X X X X X N
355-
:lookup:`distance_gte` X X X X X N
356-
:lookup:`distance_lt` X X X X X N
357-
:lookup:`distance_lte` X X X X X N
358-
:lookup:`dwithin` X X X B
359-
:lookup:`equals` X X X X X C
360-
:lookup:`exact <same_as>` X X X X X B
361-
:lookup:`intersects` X X X X X B
342+
================================= ========= ========= ============ ============ ========== ========
343+
Lookup Type PostGIS Oracle MariaDB MySQL [#]_ SpatiaLite PGRaster
344+
================================= ========= ========= ============ ============ ========== ========
345+
:lookup:`bbcontains` X X X X N
346+
:lookup:`bboverlaps` X X X X N
347+
:lookup:`contained` X X X X N
348+
:lookup:`contains <gis-contains>` X X X X X B
349+
:lookup:`contains_properly` X B
350+
:lookup:`coveredby` X X X (≥ 12.0.1) X X B
351+
:lookup:`covers` X X X X B
352+
:lookup:`crosses` X X X X C
353+
:lookup:`disjoint` X X X X X B
354+
:lookup:`distance_gt` X X X X X N
355+
:lookup:`distance_gte` X X X X X N
356+
:lookup:`distance_lt` X X X X X N
357+
:lookup:`distance_lte` X X X X X N
358+
:lookup:`dwithin` X X X B
359+
:lookup:`equals` X X X X X C
360+
:lookup:`exact <same_as>` X X X X X B
361+
:lookup:`geom_type` X X (≥ 23c) X X X
362+
:lookup:`intersects` X X X X X B
362363
:lookup:`isempty` X
363-
:lookup:`isvalid` X X X (≥ 12.0.1) X X
364-
:lookup:`overlaps` X X X X X B
365-
:lookup:`relate` X X X X C
366-
:lookup:`same_as` X X X X X B
367-
:lookup:`touches` X X X X X B
368-
:lookup:`within` X X X X X B
369-
:lookup:`left` X C
370-
:lookup:`right` X C
371-
:lookup:`overlaps_left` X B
372-
:lookup:`overlaps_right` X B
373-
:lookup:`overlaps_above` X C
374-
:lookup:`overlaps_below` X C
375-
:lookup:`strictly_above` X C
376-
:lookup:`strictly_below` X C
377-
================================= ========= ======== ============ ============ ========== ========
364+
:lookup:`isvalid` X X X (≥ 12.0.1) X X
365+
:lookup:`overlaps` X X X X X B
366+
:lookup:`relate` X X X X C
367+
:lookup:`same_as` X X X X X B
368+
:lookup:`touches` X X X X X B
369+
:lookup:`within` X X X X X B
370+
:lookup:`left` X C
371+
:lookup:`right` X C
372+
:lookup:`overlaps_left` X B
373+
:lookup:`overlaps_right` X B
374+
:lookup:`overlaps_above` X C
375+
:lookup:`overlaps_below` X C
376+
:lookup:`strictly_above` X C
377+
:lookup:`strictly_below` X C
378+
================================= ========= ========= ============ ============ ========== ========
378379

379380
.. _database-functions-compatibility:
380381

@@ -408,6 +409,7 @@ Function PostGIS Oracle MariaDB MySQL
408409
:class:`FromWKT` X X X X X
409410
:class:`GeoHash` X X (≥ 12.0.1) X X (LWGEOM/RTTOPO)
410411
:class:`GeometryDistance` X
412+
:class:`GeometryType` X X (≥ 23c) X X X
411413
:class:`Intersection` X X X X X
412414
:class:`IsEmpty` X
413415
:class:`IsValid` X X X (≥ 12.0.1) X X

docs/ref/contrib/gis/functions.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,18 @@ Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
635635

636636
MariaDB 12.0.1+ support was added.
637637

638+
``GeometryType``
639+
----------------
640+
641+
.. versionadded:: 6.0
642+
643+
.. class:: GeometryType(expr)
644+
645+
*Availability*: `PostGIS <https://postgis.net/docs/GeometryType.html>`__,
646+
Oracle 23c+, MariaDB, MySQL, SpatiaLite
647+
648+
Accepts a geographic field or expression and returns its geometry type.
649+
638650
``MemSize``
639651
-----------
640652

docs/ref/contrib/gis/geoquerysets.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,32 @@ Oracle ``SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(p
399399

400400
MariaDB 12.0.1+ support was added.
401401

402+
.. fieldlookup:: geom_type
403+
404+
``geom_type``
405+
-------------
406+
407+
.. versionadded:: 6.0
408+
409+
*Availability*: `PostGIS <https://postgis.net/docs/GeometryType.html>`__,
410+
Oracle 23c+, MariaDB, MySQL, SpatiaLite
411+
412+
Returns the geometry type of the geometry field.
413+
414+
Example::
415+
416+
Zipcode.objects.filter(poly__geom_type="POLYGON")
417+
418+
========== ==========================
419+
Backend SQL Equivalent
420+
========== ==========================
421+
PostGIS ``GeometryType(geom)``
422+
MariaDB ``ST_GeometryType(geom)``
423+
MySQL ``ST_GeometryType(geom)``
424+
Oracle ``SDO_GEOMETRY.GET_GTYPE(geom)``
425+
SpatiaLite ``GeometryType(geom)``
426+
========== ==========================
427+
402428
.. fieldlookup:: overlaps
403429

404430
``overlaps``

docs/releases/6.0.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ Minor features
119119
:class:`~django.contrib.gis.db.models.functions.IsValid` database functions
120120
are now supported on MariaDB 12.0.1+.
121121

122+
* The new :lookup:`geom_type` lookup and
123+
:class:`GeometryType() <django.contrib.gis.db.models.functions.GeometryType>`
124+
database function allow filtering geometries by their types.
125+
122126
:mod:`django.contrib.messages`
123127
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
124128

tests/gis_tests/geoapp/test_functions.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,31 @@
44
from decimal import Decimal
55

66
from django.contrib.gis.db.models import GeometryField, PolygonField, functions
7-
from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon, fromstr
7+
from django.contrib.gis.geos import (
8+
GEOSGeometry,
9+
LineString,
10+
MultiLineString,
11+
MultiPoint,
12+
MultiPolygon,
13+
Point,
14+
Polygon,
15+
fromstr,
16+
)
817
from django.contrib.gis.measure import Area
918
from django.db import NotSupportedError, connection
1019
from django.db.models import IntegerField, Sum, Value
1120
from django.test import TestCase, skipUnlessDBFeature
1221

1322
from ..utils import FuncTestMixin
14-
from .models import City, Country, CountryWebMercator, ManyPointModel, State, Track
23+
from .models import (
24+
City,
25+
Country,
26+
CountryWebMercator,
27+
Feature,
28+
ManyPointModel,
29+
State,
30+
Track,
31+
)
1532

1633

1734
class GISFunctionsTests(FuncTestMixin, TestCase):
@@ -880,3 +897,48 @@ def test_argument_validation(self):
880897
City.objects.annotate(union=functions.GeoFunc(1, "point")).get(
881898
name="Dallas"
882899
)
900+
901+
@skipUnlessDBFeature("has_GeometryType_function")
902+
def test_geometry_type(self):
903+
Feature.objects.bulk_create(
904+
[
905+
Feature(name="Point", geom=Point(0, 0)),
906+
Feature(name="LineString", geom=LineString((0, 0), (1, 1))),
907+
Feature(name="Polygon", geom=Polygon(((0, 0), (1, 0), (1, 1), (0, 0)))),
908+
Feature(name="MultiPoint", geom=MultiPoint(Point(0, 0), Point(1, 1))),
909+
Feature(
910+
name="MultiLineString",
911+
geom=MultiLineString(
912+
LineString((0, 0), (1, 1)), LineString((1, 1), (2, 2))
913+
),
914+
),
915+
Feature(
916+
name="MultiPolygon",
917+
geom=MultiPolygon(
918+
Polygon(((0, 0), (1, 0), (1, 1), (0, 0))),
919+
Polygon(((1, 1), (2, 1), (2, 2), (1, 1))),
920+
),
921+
),
922+
]
923+
)
924+
925+
expected_results = {
926+
("POINT", Point),
927+
("LINESTRING", LineString),
928+
("POLYGON", Polygon),
929+
("MULTIPOINT", MultiPoint),
930+
("MULTILINESTRING", MultiLineString),
931+
("MULTIPOLYGON", MultiPolygon),
932+
}
933+
934+
for geom_type, geom_class in expected_results:
935+
with self.subTest(geom_type=geom_type):
936+
obj = (
937+
Feature.objects.annotate(
938+
geometry_type=functions.GeometryType("geom")
939+
)
940+
.filter(geom__geom_type=geom_type)
941+
.get()
942+
)
943+
self.assertIsInstance(obj.geom, geom_class)
944+
self.assertEqual(obj.geometry_type, geom_type)

0 commit comments

Comments
 (0)