summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancisco Couzo <franciscouzo@gmail.com>2019-03-20 13:54:42 -0300
committerTim Graham <timograham@gmail.com>2019-03-20 12:54:42 -0400
commit0193bf874f08f21cdec628b8d80d261471bfe5fc (patch)
treee095cbff7cd8c6a0abf98f2378f1d99747daa5dc
parent638d5ea37557a668f59d76bb34ee4e076787b524 (diff)
Fixed #28738 -- Added the GeometryDistance function.
-rw-r--r--AUTHORS1
-rw-r--r--django/contrib/gis/db/backends/base/operations.py8
-rw-r--r--django/contrib/gis/db/backends/mysql/operations.py6
-rw-r--r--django/contrib/gis/db/backends/oracle/operations.py4
-rw-r--r--django/contrib/gis/db/backends/spatialite/operations.py2
-rw-r--r--django/contrib/gis/db/models/functions.py10
-rw-r--r--docs/ref/contrib/gis/functions.txt35
-rw-r--r--docs/releases/3.0.txt3
-rw-r--r--tests/gis_tests/geoapp/test_functions.py15
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@apolloner.eu>
Florian Moussous <florian.moussous@gmail.com>
Francisco Albarran Cristobal <pahko.xd@gmail.com>
+ Francisco Couzo <franciscouzo@gmail.com>
François Freitag <mail@franek.fr>
Frank Tegtmeyer <fte@fte.to>
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 <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``
================
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)