mirror of
https://github.com/django/django.git
synced 2025-08-21 17:29:13 +00:00
Fixed #28696 -- Added GeometryType GIS database function and __geom_type lookup.
Co-Authored-By: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
parent
6aa05fd232
commit
a5b0a618c3
@ -54,6 +54,7 @@ class BaseSpatialOperations:
|
|||||||
"FromWKT",
|
"FromWKT",
|
||||||
"GeoHash",
|
"GeoHash",
|
||||||
"GeometryDistance",
|
"GeometryDistance",
|
||||||
|
"GeometryType",
|
||||||
"Intersection",
|
"Intersection",
|
||||||
"IsEmpty",
|
"IsEmpty",
|
||||||
"IsValid",
|
"IsValid",
|
||||||
|
@ -18,6 +18,7 @@ from django.contrib.gis.geos.geometry import GEOSGeometry, GEOSGeometryBase
|
|||||||
from django.contrib.gis.geos.prototypes.io import wkb_r
|
from django.contrib.gis.geos.prototypes.io import wkb_r
|
||||||
from django.contrib.gis.measure import Distance
|
from django.contrib.gis.measure import Distance
|
||||||
from django.db.backends.oracle.operations import DatabaseOperations
|
from django.db.backends.oracle.operations import DatabaseOperations
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
DEFAULT_TOLERANCE = "0.05"
|
DEFAULT_TOLERANCE = "0.05"
|
||||||
|
|
||||||
@ -117,23 +118,28 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
|
|||||||
"dwithin": SDODWithin(),
|
"dwithin": SDODWithin(),
|
||||||
}
|
}
|
||||||
|
|
||||||
unsupported_functions = {
|
@cached_property
|
||||||
"AsKML",
|
def unsupported_functions(self):
|
||||||
"AsSVG",
|
unsupported = {
|
||||||
"Azimuth",
|
"AsKML",
|
||||||
"ClosestPoint",
|
"AsSVG",
|
||||||
"ForcePolygonCW",
|
"Azimuth",
|
||||||
"GeoHash",
|
"ClosestPoint",
|
||||||
"GeometryDistance",
|
"ForcePolygonCW",
|
||||||
"IsEmpty",
|
"GeoHash",
|
||||||
"LineLocatePoint",
|
"GeometryDistance",
|
||||||
"MakeValid",
|
"IsEmpty",
|
||||||
"MemSize",
|
"LineLocatePoint",
|
||||||
"Rotate",
|
"MakeValid",
|
||||||
"Scale",
|
"MemSize",
|
||||||
"SnapToGrid",
|
"Rotate",
|
||||||
"Translate",
|
"Scale",
|
||||||
}
|
"SnapToGrid",
|
||||||
|
"Translate",
|
||||||
|
}
|
||||||
|
if self.connection.oracle_version < (23,):
|
||||||
|
unsupported.add("GeometryType")
|
||||||
|
return unsupported
|
||||||
|
|
||||||
def geo_quote_name(self, name):
|
def geo_quote_name(self, name):
|
||||||
return super().geo_quote_name(name).upper()
|
return super().geo_quote_name(name).upper()
|
||||||
|
@ -177,6 +177,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
|
|||||||
"FromWKB": "ST_GeomFromWKB",
|
"FromWKB": "ST_GeomFromWKB",
|
||||||
"FromWKT": "ST_GeomFromText",
|
"FromWKT": "ST_GeomFromText",
|
||||||
"NumPoints": "ST_NPoints",
|
"NumPoints": "ST_NPoints",
|
||||||
|
"GeometryType": "GeometryType",
|
||||||
}
|
}
|
||||||
return function_names
|
return function_names
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ from django.db import NotSupportedError
|
|||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
BinaryField,
|
BinaryField,
|
||||||
BooleanField,
|
BooleanField,
|
||||||
|
CharField,
|
||||||
FloatField,
|
FloatField,
|
||||||
Func,
|
Func,
|
||||||
IntegerField,
|
IntegerField,
|
||||||
@ -422,6 +423,29 @@ class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
|||||||
geom_param_pos = (0, 1)
|
geom_param_pos = (0, 1)
|
||||||
|
|
||||||
|
|
||||||
|
@BaseSpatialField.register_lookup
|
||||||
|
class GeometryType(GeoFuncMixin, Transform):
|
||||||
|
output_field = CharField()
|
||||||
|
lookup_name = "geom_type"
|
||||||
|
|
||||||
|
def as_oracle(self, compiler, connection, **extra_context):
|
||||||
|
lhs, params = compiler.compile(self.lhs)
|
||||||
|
sql = (
|
||||||
|
"(SELECT DECODE("
|
||||||
|
f"SDO_GEOMETRY.GET_GTYPE({lhs}),"
|
||||||
|
"1, 'POINT',"
|
||||||
|
"2, 'LINESTRING',"
|
||||||
|
"3, 'POLYGON',"
|
||||||
|
"4, 'COLLECTION',"
|
||||||
|
"5, 'MULTIPOINT',"
|
||||||
|
"6, 'MULTILINESTRING',"
|
||||||
|
"7, 'MULTIPOLYGON',"
|
||||||
|
"8, 'SOLID',"
|
||||||
|
"'UNKNOWN'))"
|
||||||
|
)
|
||||||
|
return sql, params
|
||||||
|
|
||||||
|
|
||||||
@BaseSpatialField.register_lookup
|
@BaseSpatialField.register_lookup
|
||||||
class IsEmpty(GeoFuncMixin, Transform):
|
class IsEmpty(GeoFuncMixin, Transform):
|
||||||
lookup_name = "isempty"
|
lookup_name = "isempty"
|
||||||
|
@ -339,42 +339,43 @@ divided into the three categories described in the :ref:`raster lookup details
|
|||||||
<spatial-lookup-raster>`: native support ``N``, bilateral native support ``B``,
|
<spatial-lookup-raster>`: native support ``N``, bilateral native support ``B``,
|
||||||
and geometry conversion support ``C``.
|
and geometry conversion support ``C``.
|
||||||
|
|
||||||
================================= ========= ======== ============ ============ ========== ========
|
================================= ========= ========= ============ ============ ========== ========
|
||||||
Lookup Type PostGIS Oracle MariaDB MySQL [#]_ SpatiaLite PGRaster
|
Lookup Type PostGIS Oracle MariaDB MySQL [#]_ SpatiaLite PGRaster
|
||||||
================================= ========= ======== ============ ============ ========== ========
|
================================= ========= ========= ============ ============ ========== ========
|
||||||
:lookup:`bbcontains` X X X X N
|
:lookup:`bbcontains` X X X X N
|
||||||
:lookup:`bboverlaps` X X X X N
|
:lookup:`bboverlaps` X X X X N
|
||||||
:lookup:`contained` X X X X N
|
:lookup:`contained` X X X X N
|
||||||
:lookup:`contains <gis-contains>` X X X X X B
|
:lookup:`contains <gis-contains>` X X X X X B
|
||||||
:lookup:`contains_properly` X B
|
:lookup:`contains_properly` X B
|
||||||
:lookup:`coveredby` X X X (≥ 12.0.1) X X B
|
:lookup:`coveredby` X X X (≥ 12.0.1) X X B
|
||||||
:lookup:`covers` X X X X B
|
:lookup:`covers` X X X X B
|
||||||
:lookup:`crosses` X X X X C
|
:lookup:`crosses` X X X X C
|
||||||
:lookup:`disjoint` X X X X X B
|
:lookup:`disjoint` X X X X X B
|
||||||
:lookup:`distance_gt` X X X X X N
|
:lookup:`distance_gt` X X X X X N
|
||||||
:lookup:`distance_gte` X X X X X N
|
:lookup:`distance_gte` X X X X X N
|
||||||
:lookup:`distance_lt` X X X X X N
|
:lookup:`distance_lt` X X X X X N
|
||||||
:lookup:`distance_lte` X X X X X N
|
:lookup:`distance_lte` X X X X X N
|
||||||
:lookup:`dwithin` X X X B
|
:lookup:`dwithin` X X X B
|
||||||
:lookup:`equals` X X X X X C
|
:lookup:`equals` X X X X X C
|
||||||
:lookup:`exact <same_as>` X X X X X B
|
:lookup:`exact <same_as>` X X X X X B
|
||||||
:lookup:`intersects` X X X X X B
|
:lookup:`geom_type` X X (≥ 23c) X X X
|
||||||
|
:lookup:`intersects` X X X X X B
|
||||||
:lookup:`isempty` X
|
:lookup:`isempty` X
|
||||||
:lookup:`isvalid` X X X (≥ 12.0.1) X X
|
:lookup:`isvalid` X X X (≥ 12.0.1) X X
|
||||||
:lookup:`overlaps` X X X X X B
|
:lookup:`overlaps` X X X X X B
|
||||||
:lookup:`relate` X X X X C
|
:lookup:`relate` X X X X C
|
||||||
:lookup:`same_as` X X X X X B
|
:lookup:`same_as` X X X X X B
|
||||||
:lookup:`touches` X X X X X B
|
:lookup:`touches` X X X X X B
|
||||||
:lookup:`within` X X X X X B
|
:lookup:`within` X X X X X B
|
||||||
:lookup:`left` X C
|
:lookup:`left` X C
|
||||||
:lookup:`right` X C
|
:lookup:`right` X C
|
||||||
:lookup:`overlaps_left` X B
|
:lookup:`overlaps_left` X B
|
||||||
:lookup:`overlaps_right` X B
|
:lookup:`overlaps_right` X B
|
||||||
:lookup:`overlaps_above` X C
|
:lookup:`overlaps_above` X C
|
||||||
:lookup:`overlaps_below` X C
|
:lookup:`overlaps_below` X C
|
||||||
:lookup:`strictly_above` X C
|
:lookup:`strictly_above` X C
|
||||||
:lookup:`strictly_below` X C
|
:lookup:`strictly_below` X C
|
||||||
================================= ========= ======== ============ ============ ========== ========
|
================================= ========= ========= ============ ============ ========== ========
|
||||||
|
|
||||||
.. _database-functions-compatibility:
|
.. _database-functions-compatibility:
|
||||||
|
|
||||||
@ -408,6 +409,7 @@ Function PostGIS Oracle MariaDB MySQL
|
|||||||
:class:`FromWKT` X X X X X
|
:class:`FromWKT` X X X X X
|
||||||
:class:`GeoHash` X X (≥ 12.0.1) X X (LWGEOM/RTTOPO)
|
:class:`GeoHash` X X (≥ 12.0.1) X X (LWGEOM/RTTOPO)
|
||||||
:class:`GeometryDistance` X
|
:class:`GeometryDistance` X
|
||||||
|
:class:`GeometryType` X X (≥ 23c) X X X
|
||||||
:class:`Intersection` X X X X X
|
:class:`Intersection` X X X X X
|
||||||
:class:`IsEmpty` X
|
:class:`IsEmpty` X
|
||||||
:class:`IsValid` X X X (≥ 12.0.1) X X
|
:class:`IsValid` X X X (≥ 12.0.1) X X
|
||||||
|
@ -635,6 +635,18 @@ Returns ``True`` if its value is a valid geometry and ``False`` otherwise.
|
|||||||
|
|
||||||
MariaDB 12.0.1+ support was added.
|
MariaDB 12.0.1+ support was added.
|
||||||
|
|
||||||
|
``GeometryType``
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. versionadded:: 6.0
|
||||||
|
|
||||||
|
.. class:: GeometryType(expr)
|
||||||
|
|
||||||
|
*Availability*: `PostGIS <https://postgis.net/docs/GeometryType.html>`__,
|
||||||
|
Oracle 23c+, MariaDB, MySQL, SpatiaLite
|
||||||
|
|
||||||
|
Accepts a geographic field or expression and returns its geometry type.
|
||||||
|
|
||||||
``MemSize``
|
``MemSize``
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -399,6 +399,32 @@ Oracle ``SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(p
|
|||||||
|
|
||||||
MariaDB 12.0.1+ support was added.
|
MariaDB 12.0.1+ support was added.
|
||||||
|
|
||||||
|
.. fieldlookup:: geom_type
|
||||||
|
|
||||||
|
``geom_type``
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. versionadded:: 6.0
|
||||||
|
|
||||||
|
*Availability*: `PostGIS <https://postgis.net/docs/GeometryType.html>`__,
|
||||||
|
Oracle 23c+, MariaDB, MySQL, SpatiaLite
|
||||||
|
|
||||||
|
Returns the geometry type of the geometry field.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
Zipcode.objects.filter(poly__geom_type="POLYGON")
|
||||||
|
|
||||||
|
========== ==========================
|
||||||
|
Backend SQL Equivalent
|
||||||
|
========== ==========================
|
||||||
|
PostGIS ``GeometryType(geom)``
|
||||||
|
MariaDB ``ST_GeometryType(geom)``
|
||||||
|
MySQL ``ST_GeometryType(geom)``
|
||||||
|
Oracle ``SDO_GEOMETRY.GET_GTYPE(geom)``
|
||||||
|
SpatiaLite ``GeometryType(geom)``
|
||||||
|
========== ==========================
|
||||||
|
|
||||||
.. fieldlookup:: overlaps
|
.. fieldlookup:: overlaps
|
||||||
|
|
||||||
``overlaps``
|
``overlaps``
|
||||||
|
@ -119,6 +119,10 @@ Minor features
|
|||||||
:class:`~django.contrib.gis.db.models.functions.IsValid` database functions
|
:class:`~django.contrib.gis.db.models.functions.IsValid` database functions
|
||||||
are now supported on MariaDB 12.0.1+.
|
are now supported on MariaDB 12.0.1+.
|
||||||
|
|
||||||
|
* The new :lookup:`geom_type` lookup and
|
||||||
|
:class:`GeometryType() <django.contrib.gis.db.models.functions.GeometryType>`
|
||||||
|
database function allow filtering geometries by their types.
|
||||||
|
|
||||||
:mod:`django.contrib.messages`
|
:mod:`django.contrib.messages`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -4,14 +4,31 @@ import re
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.contrib.gis.db.models import GeometryField, PolygonField, functions
|
from django.contrib.gis.db.models import GeometryField, PolygonField, functions
|
||||||
from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon, fromstr
|
from django.contrib.gis.geos import (
|
||||||
|
GEOSGeometry,
|
||||||
|
LineString,
|
||||||
|
MultiLineString,
|
||||||
|
MultiPoint,
|
||||||
|
MultiPolygon,
|
||||||
|
Point,
|
||||||
|
Polygon,
|
||||||
|
fromstr,
|
||||||
|
)
|
||||||
from django.contrib.gis.measure import Area
|
from django.contrib.gis.measure import Area
|
||||||
from django.db import NotSupportedError, connection
|
from django.db import NotSupportedError, connection
|
||||||
from django.db.models import IntegerField, Sum, Value
|
from django.db.models import IntegerField, Sum, Value
|
||||||
from django.test import TestCase, skipUnlessDBFeature
|
from django.test import TestCase, skipUnlessDBFeature
|
||||||
|
|
||||||
from ..utils import FuncTestMixin
|
from ..utils import FuncTestMixin
|
||||||
from .models import City, Country, CountryWebMercator, ManyPointModel, State, Track
|
from .models import (
|
||||||
|
City,
|
||||||
|
Country,
|
||||||
|
CountryWebMercator,
|
||||||
|
Feature,
|
||||||
|
ManyPointModel,
|
||||||
|
State,
|
||||||
|
Track,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GISFunctionsTests(FuncTestMixin, TestCase):
|
class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||||
@ -880,3 +897,48 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
|||||||
City.objects.annotate(union=functions.GeoFunc(1, "point")).get(
|
City.objects.annotate(union=functions.GeoFunc(1, "point")).get(
|
||||||
name="Dallas"
|
name="Dallas"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("has_GeometryType_function")
|
||||||
|
def test_geometry_type(self):
|
||||||
|
Feature.objects.bulk_create(
|
||||||
|
[
|
||||||
|
Feature(name="Point", geom=Point(0, 0)),
|
||||||
|
Feature(name="LineString", geom=LineString((0, 0), (1, 1))),
|
||||||
|
Feature(name="Polygon", geom=Polygon(((0, 0), (1, 0), (1, 1), (0, 0)))),
|
||||||
|
Feature(name="MultiPoint", geom=MultiPoint(Point(0, 0), Point(1, 1))),
|
||||||
|
Feature(
|
||||||
|
name="MultiLineString",
|
||||||
|
geom=MultiLineString(
|
||||||
|
LineString((0, 0), (1, 1)), LineString((1, 1), (2, 2))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Feature(
|
||||||
|
name="MultiPolygon",
|
||||||
|
geom=MultiPolygon(
|
||||||
|
Polygon(((0, 0), (1, 0), (1, 1), (0, 0))),
|
||||||
|
Polygon(((1, 1), (2, 1), (2, 2), (1, 1))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_results = {
|
||||||
|
("POINT", Point),
|
||||||
|
("LINESTRING", LineString),
|
||||||
|
("POLYGON", Polygon),
|
||||||
|
("MULTIPOINT", MultiPoint),
|
||||||
|
("MULTILINESTRING", MultiLineString),
|
||||||
|
("MULTIPOLYGON", MultiPolygon),
|
||||||
|
}
|
||||||
|
|
||||||
|
for geom_type, geom_class in expected_results:
|
||||||
|
with self.subTest(geom_type=geom_type):
|
||||||
|
obj = (
|
||||||
|
Feature.objects.annotate(
|
||||||
|
geometry_type=functions.GeometryType("geom")
|
||||||
|
)
|
||||||
|
.filter(geom__geom_type=geom_type)
|
||||||
|
.get()
|
||||||
|
)
|
||||||
|
self.assertIsInstance(obj.geom, geom_class)
|
||||||
|
self.assertEqual(obj.geometry_type, geom_type)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user