Fixed #28738 -- Added the GeometryDistance function.
This commit is contained in:
parent
638d5ea375
commit
0193bf874f
1
AUTHORS
1
AUTHORS
|
@ -282,6 +282,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Florian Apolloner <florian@apolloner.eu>
|
Florian Apolloner <florian@apolloner.eu>
|
||||||
Florian Moussous <florian.moussous@gmail.com>
|
Florian Moussous <florian.moussous@gmail.com>
|
||||||
Francisco Albarran Cristobal <pahko.xd@gmail.com>
|
Francisco Albarran Cristobal <pahko.xd@gmail.com>
|
||||||
|
Francisco Couzo <franciscouzo@gmail.com>
|
||||||
François Freitag <mail@franek.fr>
|
François Freitag <mail@franek.fr>
|
||||||
Frank Tegtmeyer <fte@fte.to>
|
Frank Tegtmeyer <fte@fte.to>
|
||||||
Frank Wierzbicki
|
Frank Wierzbicki
|
||||||
|
|
|
@ -40,10 +40,10 @@ class BaseSpatialOperations:
|
||||||
unsupported_functions = {
|
unsupported_functions = {
|
||||||
'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'Azimuth',
|
'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'Azimuth',
|
||||||
'BoundingCircle', 'Centroid', 'Difference', 'Distance', 'Envelope',
|
'BoundingCircle', 'Centroid', 'Difference', 'Distance', 'Envelope',
|
||||||
'GeoHash', 'Intersection', 'IsValid', 'Length', 'LineLocatePoint',
|
'GeoHash', 'GeometryDistance', 'Intersection', 'IsValid', 'Length',
|
||||||
'MakeValid', 'MemSize', 'NumGeometries', 'NumPoints', 'Perimeter',
|
'LineLocatePoint', 'MakeValid', 'MemSize', 'NumGeometries',
|
||||||
'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid', 'SymDifference',
|
'NumPoints', 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale',
|
||||||
'Transform', 'Translate', 'Union',
|
'SnapToGrid', 'SymDifference', 'Transform', 'Translate', 'Union',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Constructors
|
# Constructors
|
||||||
|
|
|
@ -54,9 +54,9 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
|
||||||
def unsupported_functions(self):
|
def unsupported_functions(self):
|
||||||
unsupported = {
|
unsupported = {
|
||||||
'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle',
|
'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle',
|
||||||
'ForcePolygonCW', 'LineLocatePoint', 'MakeValid', 'MemSize',
|
'ForcePolygonCW', 'GeometryDistance', 'LineLocatePoint',
|
||||||
'Perimeter', 'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid',
|
'MakeValid', 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse',
|
||||||
'Transform', 'Translate',
|
'Scale', 'SnapToGrid', 'Transform', 'Translate',
|
||||||
}
|
}
|
||||||
if self.connection.mysql_is_mariadb:
|
if self.connection.mysql_is_mariadb:
|
||||||
unsupported.update({'GeoHash', 'IsValid'})
|
unsupported.update({'GeoHash', 'IsValid'})
|
||||||
|
|
|
@ -107,8 +107,8 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
|
||||||
|
|
||||||
unsupported_functions = {
|
unsupported_functions = {
|
||||||
'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth', 'ForcePolygonCW', 'GeoHash',
|
'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth', 'ForcePolygonCW', 'GeoHash',
|
||||||
'LineLocatePoint', 'MakeValid', 'MemSize', 'Scale', 'SnapToGrid',
|
'GeometryDistance', 'LineLocatePoint', 'MakeValid', 'MemSize',
|
||||||
'Translate',
|
'Scale', 'SnapToGrid', 'Translate',
|
||||||
}
|
}
|
||||||
|
|
||||||
def geo_quote_name(self, name):
|
def geo_quote_name(self, name):
|
||||||
|
|
|
@ -79,7 +79,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def unsupported_functions(self):
|
def unsupported_functions(self):
|
||||||
unsupported = {'BoundingCircle', 'MemSize'}
|
unsupported = {'BoundingCircle', 'GeometryDistance', 'MemSize'}
|
||||||
if not self.lwgeom_version():
|
if not self.lwgeom_version():
|
||||||
unsupported |= {'Azimuth', 'GeoHash', 'IsValid', 'MakeValid'}
|
unsupported |= {'Azimuth', 'GeoHash', 'IsValid', 'MakeValid'}
|
||||||
return unsupported
|
return unsupported
|
||||||
|
|
|
@ -48,7 +48,7 @@ class GeoFuncMixin:
|
||||||
return self.source_expressions[self.geom_param_pos[0]].field
|
return self.source_expressions[self.geom_param_pos[0]].field
|
||||||
|
|
||||||
def as_sql(self, compiler, connection, function=None, **extra_context):
|
def as_sql(self, compiler, connection, function=None, **extra_context):
|
||||||
if not self.function and not function:
|
if self.function is None and function is None:
|
||||||
function = connection.ops.spatial_function_name(self.name)
|
function = connection.ops.spatial_function_name(self.name)
|
||||||
return super().as_sql(compiler, connection, function=function, **extra_context)
|
return super().as_sql(compiler, connection, function=function, **extra_context)
|
||||||
|
|
||||||
|
@ -299,6 +299,14 @@ class GeoHash(GeoFunc):
|
||||||
return clone.as_sql(compiler, connection, **extra_context)
|
return clone.as_sql(compiler, connection, **extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class GeometryDistance(GeoFunc):
|
||||||
|
output_field = FloatField()
|
||||||
|
arity = 2
|
||||||
|
function = ''
|
||||||
|
arg_joiner = ' <-> '
|
||||||
|
geom_param_pos = (0, 1)
|
||||||
|
|
||||||
|
|
||||||
class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
class Intersection(OracleToleranceMixin, GeomOutputGeoFunc):
|
||||||
arity = 2
|
arity = 2
|
||||||
geom_param_pos = (0, 1)
|
geom_param_pos = (0, 1)
|
||||||
|
|
|
@ -20,17 +20,17 @@ get a ``NotImplementedError`` exception.
|
||||||
|
|
||||||
Function's summary:
|
Function's summary:
|
||||||
|
|
||||||
================== ======================== ====================== ======================= ================== =====================
|
========================= ======================== ====================== ======================= ================== =====================
|
||||||
Measurement Relationships Operations Editors Output format Miscellaneous
|
Measurement Relationships Operations Editors Output format Miscellaneous
|
||||||
================== ======================== ====================== ======================= ================== =====================
|
========================= ======================== ====================== ======================= ================== =====================
|
||||||
:class:`Area` :class:`Azimuth` :class:`Difference` :class:`ForcePolygonCW` :class:`AsGeoJSON` :class:`IsValid`
|
: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:`Distance` :class:`BoundingCircle` :class:`Intersection` :class:`MakeValid` :class:`AsGML` :class:`MemSize`
|
||||||
:class:`Length` :class:`Centroid` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`NumGeometries`
|
:class:`GeometryDistance` :class:`Centroid` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`NumGeometries`
|
||||||
:class:`Perimeter` :class:`Envelope` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumPoints`
|
:class:`Length` :class:`Envelope` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumPoints`
|
||||||
.. :class:`LineLocatePoint` :class:`SnapToGrid` :class:`GeoHash`
|
:class:`Perimeter` :class:`LineLocatePoint` :class:`SnapToGrid` :class:`GeoHash`
|
||||||
.. :class:`PointOnSurface` :class:`Transform`
|
.. :class:`PointOnSurface` :class:`Transform`
|
||||||
.. :class:`Translate`
|
.. :class:`Translate`
|
||||||
================== ======================== ====================== ======================= ================== =====================
|
========================= ======================== ====================== ======================= ================== =====================
|
||||||
|
|
||||||
``Area``
|
``Area``
|
||||||
========
|
========
|
||||||
|
@ -308,6 +308,19 @@ result.
|
||||||
|
|
||||||
__ https://en.wikipedia.org/wiki/Geohash
|
__ https://en.wikipedia.org/wiki/Geohash
|
||||||
|
|
||||||
|
``GeometryDistance``
|
||||||
|
====================
|
||||||
|
|
||||||
|
.. class:: GeometryDistance(expr1, expr2, **extra)
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
|
*Availability*: `PostGIS <https://postgis.net/docs/geometry_distance_knn.html>`__
|
||||||
|
|
||||||
|
Accepts two geographic fields or expressions and returns the distance between
|
||||||
|
them. When used in an :meth:`~django.db.models.query.QuerySet.order_by` clause,
|
||||||
|
it provides index-assisted nearest-neighbor result sets.
|
||||||
|
|
||||||
``Intersection``
|
``Intersection``
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,9 @@ Minor features
|
||||||
* Allowed MySQL spatial lookup functions to operate on real geometries.
|
* Allowed MySQL spatial lookup functions to operate on real geometries.
|
||||||
Previous support was limited to bounding boxes.
|
Previous support was limited to bounding boxes.
|
||||||
|
|
||||||
|
* Added the :class:`~django.contrib.gis.db.models.functions.GeometryDistance`
|
||||||
|
function, supported on PostGIS.
|
||||||
|
|
||||||
:mod:`django.contrib.messages`
|
:mod:`django.contrib.messages`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -240,6 +240,21 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||||
self.assertEqual(ref_hash, h1.geohash[:len(ref_hash)])
|
self.assertEqual(ref_hash, h1.geohash[:len(ref_hash)])
|
||||||
self.assertEqual(ref_hash[:5], h2.geohash)
|
self.assertEqual(ref_hash[:5], h2.geohash)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('has_GeometryDistance_function')
|
||||||
|
def test_geometry_distance(self):
|
||||||
|
point = Point(-90, 40, srid=4326)
|
||||||
|
qs = City.objects.annotate(distance=functions.GeometryDistance('point', point)).order_by('distance')
|
||||||
|
self.assertEqual([city.distance for city in qs], [
|
||||||
|
2.99091995527296,
|
||||||
|
5.33507274054713,
|
||||||
|
9.33852187483721,
|
||||||
|
9.91769193646233,
|
||||||
|
11.556465744884,
|
||||||
|
14.713098433352,
|
||||||
|
34.3635252198568,
|
||||||
|
276.987855073372,
|
||||||
|
])
|
||||||
|
|
||||||
@skipUnlessDBFeature("has_Intersection_function")
|
@skipUnlessDBFeature("has_Intersection_function")
|
||||||
def test_intersection(self):
|
def test_intersection(self):
|
||||||
geom = Point(5, 23, srid=4326)
|
geom = Point(5, 23, srid=4326)
|
||||||
|
|
Loading…
Reference in New Issue