mirror of
https://github.com/django/django.git
synced 2025-08-21 01:09: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",
|
||||
"GeoHash",
|
||||
"GeometryDistance",
|
||||
"GeometryType",
|
||||
"Intersection",
|
||||
"IsEmpty",
|
||||
"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.measure import Distance
|
||||
from django.db.backends.oracle.operations import DatabaseOperations
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
DEFAULT_TOLERANCE = "0.05"
|
||||
|
||||
@ -117,23 +118,28 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
"dwithin": SDODWithin(),
|
||||
}
|
||||
|
||||
unsupported_functions = {
|
||||
"AsKML",
|
||||
"AsSVG",
|
||||
"Azimuth",
|
||||
"ClosestPoint",
|
||||
"ForcePolygonCW",
|
||||
"GeoHash",
|
||||
"GeometryDistance",
|
||||
"IsEmpty",
|
||||
"LineLocatePoint",
|
||||
"MakeValid",
|
||||
"MemSize",
|
||||
"Rotate",
|
||||
"Scale",
|
||||
"SnapToGrid",
|
||||
"Translate",
|
||||
}
|
||||
@cached_property
|
||||
def unsupported_functions(self):
|
||||
unsupported = {
|
||||
"AsKML",
|
||||
"AsSVG",
|
||||
"Azimuth",
|
||||
"ClosestPoint",
|
||||
"ForcePolygonCW",
|
||||
"GeoHash",
|
||||
"GeometryDistance",
|
||||
"IsEmpty",
|
||||
"LineLocatePoint",
|
||||
"MakeValid",
|
||||
"MemSize",
|
||||
"Rotate",
|
||||
"Scale",
|
||||
"SnapToGrid",
|
||||
"Translate",
|
||||
}
|
||||
if self.connection.oracle_version < (23,):
|
||||
unsupported.add("GeometryType")
|
||||
return unsupported
|
||||
|
||||
def geo_quote_name(self, name):
|
||||
return super().geo_quote_name(name).upper()
|
||||
|
@ -177,6 +177,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
"FromWKB": "ST_GeomFromWKB",
|
||||
"FromWKT": "ST_GeomFromText",
|
||||
"NumPoints": "ST_NPoints",
|
||||
"GeometryType": "GeometryType",
|
||||
}
|
||||
return function_names
|
||||
|
||||
|
@ -9,6 +9,7 @@ from django.db import NotSupportedError
|
||||
from django.db.models import (
|
||||
BinaryField,
|
||||
BooleanField,
|
||||
CharField,
|
||||
FloatField,
|
||||
Func,
|
||||
IntegerField,
|
||||
@ -422,6 +423,29 @@ class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||
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
|
||||
class IsEmpty(GeoFuncMixin, Transform):
|
||||
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``,
|
||||
and geometry conversion support ``C``.
|
||||
|
||||
================================= ========= ======== ============ ============ ========== ========
|
||||
Lookup Type PostGIS Oracle MariaDB MySQL [#]_ SpatiaLite PGRaster
|
||||
================================= ========= ======== ============ ============ ========== ========
|
||||
:lookup:`bbcontains` X X X X N
|
||||
:lookup:`bboverlaps` X X X X N
|
||||
:lookup:`contained` X X X X N
|
||||
:lookup:`contains <gis-contains>` X X X X X B
|
||||
:lookup:`contains_properly` X B
|
||||
:lookup:`coveredby` X X X (≥ 12.0.1) X X B
|
||||
:lookup:`covers` X X X X B
|
||||
:lookup:`crosses` X X X X C
|
||||
:lookup:`disjoint` X X X X X B
|
||||
:lookup:`distance_gt` 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_lte` X X X X X N
|
||||
:lookup:`dwithin` X X X B
|
||||
: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 Type PostGIS Oracle MariaDB MySQL [#]_ SpatiaLite PGRaster
|
||||
================================= ========= ========= ============ ============ ========== ========
|
||||
:lookup:`bbcontains` X X X X N
|
||||
:lookup:`bboverlaps` X X X X N
|
||||
:lookup:`contained` X X X X N
|
||||
:lookup:`contains <gis-contains>` X X X X X B
|
||||
:lookup:`contains_properly` X B
|
||||
:lookup:`coveredby` X X X (≥ 12.0.1) X X B
|
||||
:lookup:`covers` X X X X B
|
||||
:lookup:`crosses` X X X X C
|
||||
:lookup:`disjoint` X X X X X B
|
||||
:lookup:`distance_gt` 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_lte` X X X X X N
|
||||
:lookup:`dwithin` X X X B
|
||||
:lookup:`equals` X X X X X C
|
||||
:lookup:`exact <same_as>` 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:`isvalid` X X X (≥ 12.0.1) 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
|
||||
:lookup:`touches` X X X X X B
|
||||
:lookup:`within` X X X X X B
|
||||
:lookup:`left` X C
|
||||
:lookup:`right` X C
|
||||
:lookup:`overlaps_left` X B
|
||||
:lookup:`overlaps_right` X B
|
||||
:lookup:`overlaps_above` X C
|
||||
:lookup:`overlaps_below` X C
|
||||
:lookup:`strictly_above` X C
|
||||
:lookup:`strictly_below` X C
|
||||
================================= ========= ======== ============ ============ ========== ========
|
||||
:lookup:`isvalid` X X X (≥ 12.0.1) 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
|
||||
:lookup:`touches` X X X X X B
|
||||
:lookup:`within` X X X X X B
|
||||
:lookup:`left` X C
|
||||
:lookup:`right` X C
|
||||
:lookup:`overlaps_left` X B
|
||||
:lookup:`overlaps_right` X B
|
||||
:lookup:`overlaps_above` X C
|
||||
:lookup:`overlaps_below` X C
|
||||
:lookup:`strictly_above` X C
|
||||
:lookup:`strictly_below` X C
|
||||
================================= ========= ========= ============ ============ ========== ========
|
||||
|
||||
.. _database-functions-compatibility:
|
||||
|
||||
@ -408,6 +409,7 @@ Function PostGIS Oracle MariaDB MySQL
|
||||
:class:`FromWKT` X X X X X
|
||||
:class:`GeoHash` X X (≥ 12.0.1) X X (LWGEOM/RTTOPO)
|
||||
:class:`GeometryDistance` X
|
||||
:class:`GeometryType` X X (≥ 23c) X X X
|
||||
:class:`Intersection` X X X X X
|
||||
:class:`IsEmpty` 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.
|
||||
|
||||
``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``
|
||||
-----------
|
||||
|
||||
|
@ -399,6 +399,32 @@ Oracle ``SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(p
|
||||
|
||||
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
|
||||
|
||||
``overlaps``
|
||||
|
@ -119,6 +119,10 @@ Minor features
|
||||
:class:`~django.contrib.gis.db.models.functions.IsValid` database functions
|
||||
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`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -4,14 +4,31 @@ import re
|
||||
from decimal import Decimal
|
||||
|
||||
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.db import NotSupportedError, connection
|
||||
from django.db.models import IntegerField, Sum, Value
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
|
||||
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):
|
||||
@ -880,3 +897,48 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
City.objects.annotate(union=functions.GeoFunc(1, "point")).get(
|
||||
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