Fixed #28738 -- Added the GeometryDistance function.

This commit is contained in:
Francisco Couzo 2019-03-20 13:54:42 -03:00 committed by Tim Graham
parent 638d5ea375
commit 0193bf874f
9 changed files with 62 additions and 22 deletions

View File

@ -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

View File

@ -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

View File

@ -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'})

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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``
================ ================

View File

@ -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`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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)