From 0193bf874f08f21cdec628b8d80d261471bfe5fc Mon Sep 17 00:00:00 2001 From: Francisco Couzo Date: Wed, 20 Mar 2019 13:54:42 -0300 Subject: [PATCH] Fixed #28738 -- Added the GeometryDistance function. --- AUTHORS | 1 + .../gis/db/backends/base/operations.py | 8 ++--- .../gis/db/backends/mysql/operations.py | 6 ++-- .../gis/db/backends/oracle/operations.py | 4 +-- .../gis/db/backends/spatialite/operations.py | 2 +- django/contrib/gis/db/models/functions.py | 10 +++++- docs/ref/contrib/gis/functions.txt | 35 +++++++++++++------ docs/releases/3.0.txt | 3 ++ tests/gis_tests/geoapp/test_functions.py | 15 ++++++++ 9 files changed, 62 insertions(+), 22 deletions(-) diff --git a/AUTHORS b/AUTHORS index e322526a64..8e966b2cf8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -282,6 +282,7 @@ answer newbie questions, and generally made Django that much better: Florian Apolloner Florian Moussous Francisco Albarran Cristobal + Francisco Couzo François Freitag Frank Tegtmeyer Frank Wierzbicki diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py index 860748cc9d..4a2245680f 100644 --- a/django/contrib/gis/db/backends/base/operations.py +++ b/django/contrib/gis/db/backends/base/operations.py @@ -40,10 +40,10 @@ class BaseSpatialOperations: unsupported_functions = { 'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle', 'Centroid', 'Difference', 'Distance', 'Envelope', - 'GeoHash', 'Intersection', 'IsValid', 'Length', 'LineLocatePoint', - 'MakeValid', 'MemSize', 'NumGeometries', 'NumPoints', 'Perimeter', - 'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid', 'SymDifference', - 'Transform', 'Translate', 'Union', + 'GeoHash', 'GeometryDistance', 'Intersection', 'IsValid', 'Length', + 'LineLocatePoint', 'MakeValid', 'MemSize', 'NumGeometries', + 'NumPoints', 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale', + 'SnapToGrid', 'SymDifference', 'Transform', 'Translate', 'Union', } # Constructors diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index 1ac8055071..e765e4d879 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -54,9 +54,9 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations): def unsupported_functions(self): unsupported = { 'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle', - 'ForcePolygonCW', 'LineLocatePoint', 'MakeValid', 'MemSize', - 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid', - 'Transform', 'Translate', + 'ForcePolygonCW', 'GeometryDistance', 'LineLocatePoint', + 'MakeValid', 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse', + 'Scale', 'SnapToGrid', 'Transform', 'Translate', } if self.connection.mysql_is_mariadb: unsupported.update({'GeoHash', 'IsValid'}) diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index df052d74fc..f41ce0ed64 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -107,8 +107,8 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations): unsupported_functions = { 'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth', 'ForcePolygonCW', 'GeoHash', - 'LineLocatePoint', 'MakeValid', 'MemSize', 'Scale', 'SnapToGrid', - 'Translate', + 'GeometryDistance', 'LineLocatePoint', 'MakeValid', 'MemSize', + 'Scale', 'SnapToGrid', 'Translate', } def geo_quote_name(self, name): diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index bbce44b9df..a1c049b562 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -79,7 +79,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): @cached_property def unsupported_functions(self): - unsupported = {'BoundingCircle', 'MemSize'} + unsupported = {'BoundingCircle', 'GeometryDistance', 'MemSize'} if not self.lwgeom_version(): unsupported |= {'Azimuth', 'GeoHash', 'IsValid', 'MakeValid'} return unsupported diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index c1ae4ae445..a79067a45a 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -48,7 +48,7 @@ class GeoFuncMixin: return self.source_expressions[self.geom_param_pos[0]].field 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) 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) +class GeometryDistance(GeoFunc): + output_field = FloatField() + arity = 2 + function = '' + arg_joiner = ' <-> ' + geom_param_pos = (0, 1) + + class Intersection(OracleToleranceMixin, GeomOutputGeoFunc): arity = 2 geom_param_pos = (0, 1) diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt index b1f07ff745..4a4e1b406b 100644 --- a/docs/ref/contrib/gis/functions.txt +++ b/docs/ref/contrib/gis/functions.txt @@ -20,17 +20,17 @@ get a ``NotImplementedError`` exception. Function's summary: -================== ======================== ====================== ======================= ================== ===================== -Measurement Relationships Operations Editors Output format Miscellaneous -================== ======================== ====================== ======================= ================== ===================== -: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:`Length` :class:`Centroid` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`NumGeometries` -:class:`Perimeter` :class:`Envelope` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumPoints` -.. :class:`LineLocatePoint` :class:`SnapToGrid` :class:`GeoHash` -.. :class:`PointOnSurface` :class:`Transform` -.. :class:`Translate` -================== ======================== ====================== ======================= ================== ===================== +========================= ======================== ====================== ======================= ================== ===================== +Measurement Relationships Operations Editors Output format Miscellaneous +========================= ======================== ====================== ======================= ================== ===================== +: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:`GeometryDistance` :class:`Centroid` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`NumGeometries` +:class:`Length` :class:`Envelope` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumPoints` +:class:`Perimeter` :class:`LineLocatePoint` :class:`SnapToGrid` :class:`GeoHash` +.. :class:`PointOnSurface` :class:`Transform` +.. :class:`Translate` +========================= ======================== ====================== ======================= ================== ===================== ``Area`` ======== @@ -308,6 +308,19 @@ result. __ https://en.wikipedia.org/wiki/Geohash +``GeometryDistance`` +==================== + +.. class:: GeometryDistance(expr1, expr2, **extra) + +.. versionadded:: 3.0 + +*Availability*: `PostGIS `__ + +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`` ================ diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt index 376deab359..3433d54183 100644 --- a/docs/releases/3.0.txt +++ b/docs/releases/3.0.txt @@ -67,6 +67,9 @@ Minor features * Allowed MySQL spatial lookup functions to operate on real geometries. 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` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 817e0b2611..4f8cc9e712 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -240,6 +240,21 @@ class GISFunctionsTests(FuncTestMixin, TestCase): self.assertEqual(ref_hash, h1.geohash[:len(ref_hash)]) 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") def test_intersection(self): geom = Point(5, 23, srid=4326)