summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiccolò Mineo <niccolo@20tab.com>2023-01-13 17:48:27 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-01-20 08:13:43 +0100
commit79c298c9ceb8e386f172330236f04bb0ae4f7fdd (patch)
tree2b251b25196c27ab07e8e0bc84c34b7ea06903aa
parent4b066bde692078b194709d517b27e55defae787c (diff)
Fixed #34266 -- Added ClosestPoint GIS database functions.
-rw-r--r--django/contrib/gis/db/backends/base/operations.py1
-rw-r--r--django/contrib/gis/db/backends/mysql/operations.py1
-rw-r--r--django/contrib/gis/db/backends/oracle/operations.py1
-rw-r--r--django/contrib/gis/db/models/functions.py5
-rw-r--r--docs/ref/contrib/gis/db-api.txt1
-rw-r--r--docs/ref/contrib/gis/functions.txt21
-rw-r--r--docs/releases/5.0.txt5
-rw-r--r--tests/gis_tests/geoapp/test_functions.py12
8 files changed, 42 insertions, 5 deletions
diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py
index 287841b29e..fafdf60743 100644
--- a/django/contrib/gis/db/backends/base/operations.py
+++ b/django/contrib/gis/db/backends/base/operations.py
@@ -42,6 +42,7 @@ class BaseSpatialOperations:
"Azimuth",
"BoundingCircle",
"Centroid",
+ "ClosestPoint",
"Difference",
"Distance",
"Envelope",
diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py
index 0799a6b28f..886db605cd 100644
--- a/django/contrib/gis/db/backends/mysql/operations.py
+++ b/django/contrib/gis/db/backends/mysql/operations.py
@@ -75,6 +75,7 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
"AsSVG",
"Azimuth",
"BoundingCircle",
+ "ClosestPoint",
"ForcePolygonCW",
"GeometryDistance",
"IsEmpty",
diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py
index c191d0b1f7..b58af886de 100644
--- a/django/contrib/gis/db/backends/oracle/operations.py
+++ b/django/contrib/gis/db/backends/oracle/operations.py
@@ -121,6 +121,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
"AsKML",
"AsSVG",
"Azimuth",
+ "ClosestPoint",
"ForcePolygonCW",
"GeoHash",
"GeometryDistance",
diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py
index d886f8916e..19da355d28 100644
--- a/django/contrib/gis/db/models/functions.py
+++ b/django/contrib/gis/db/models/functions.py
@@ -280,6 +280,11 @@ class Centroid(OracleToleranceMixin, GeomOutputGeoFunc):
arity = 1
+class ClosestPoint(GeomOutputGeoFunc):
+ arity = 2
+ geom_param_pos = (0, 1)
+
+
class Difference(OracleToleranceMixin, GeomOutputGeoFunc):
arity = 2
geom_param_pos = (0, 1)
diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt
index d215eb1ecb..f8c0d83960 100644
--- a/docs/ref/contrib/gis/db-api.txt
+++ b/docs/ref/contrib/gis/db-api.txt
@@ -356,6 +356,7 @@ Function PostGIS Oracle MariaDB MySQL
:class:`Azimuth` X X (LWGEOM/RTTOPO)
:class:`BoundingCircle` X X
:class:`Centroid` X X X X X
+:class:`ClosestPoint` X X
:class:`Difference` X X X X X
:class:`Distance` X X X X X
:class:`Envelope` X X X X X
diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt
index a07feebfb7..963e9ed4bd 100644
--- a/docs/ref/contrib/gis/functions.txt
+++ b/docs/ref/contrib/gis/functions.txt
@@ -26,10 +26,10 @@ Measurement Relationships Operations Edi
:class:`Area` :class:`Azimuth` :class:`Difference` :class:`ForcePolygonCW` :class:`AsGeoJSON` :class:`IsEmpty`
:class:`Distance` :class:`BoundingCircle` :class:`Intersection` :class:`MakeValid` :class:`AsGML` :class:`IsValid`
:class:`GeometryDistance` :class:`Centroid` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`MemSize`
-:class:`Length` :class:`Envelope` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumGeometries`
-:class:`Perimeter` :class:`LineLocatePoint` :class:`SnapToGrid` :class:`FromWKB` :class:`AsWKB` :class:`NumPoints`
- :class:`PointOnSurface` :class:`Transform` :class:`FromWKT` :class:`AsWKT`
- :class:`Translate` :class:`GeoHash`
+:class:`Length` :class:`ClosestPoint` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumGeometries`
+:class:`Perimeter` :class:`Envelope` :class:`SnapToGrid` :class:`FromWKB` :class:`AsWKB` :class:`NumPoints`
+ :class:`LineLocatePoint` :class:`Transform` :class:`FromWKT` :class:`AsWKT`
+ :class:`PointOnSurface` :class:`Translate` :class:`GeoHash`
========================= ======================== ====================== ======================= ================== ================== ======================
``Area``
@@ -237,6 +237,19 @@ The ``num_seg`` parameter is used only on PostGIS.
Accepts a single geographic field or expression and returns the ``centroid``
value of the geometry.
+``ClosestPoint``
+================
+
+.. versionadded:: 5.0
+
+.. class:: ClosestPoint(expr1, expr2, **extra)
+
+*Availability*: `PostGIS <https://postgis.net/docs/ST_ClosestPoint.html>`__,
+SpatiaLite
+
+Accepts two geographic fields or expressions and returns the 2-dimensional
+point on geometry A that is closest to geometry B.
+
``Difference``
==============
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt
index a1dc6a1c1c..d1a4cba870 100644
--- a/docs/releases/5.0.txt
+++ b/docs/releases/5.0.txt
@@ -64,7 +64,10 @@ Minor features
:mod:`django.contrib.gis`
~~~~~~~~~~~~~~~~~~~~~~~~~
-* ...
+* The new
+ :class:`ClosestPoint() <django.contrib.gis.db.models.functions.ClosestPoint>`
+ function returns a 2-dimensional point on the geometry that is closest to
+ another geometry.
:mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py
index 59b0410aca..e9e2a5dcae 100644
--- a/tests/gis_tests/geoapp/test_functions.py
+++ b/tests/gis_tests/geoapp/test_functions.py
@@ -456,6 +456,18 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
):
qs.get(area__lt=500000)
+ @skipUnlessDBFeature("has_ClosestPoint_function")
+ def test_closest_point(self):
+ qs = Country.objects.annotate(
+ closest_point=functions.ClosestPoint("mpoly", functions.Centroid("mpoly"))
+ )
+ for country in qs:
+ self.assertIsInstance(country.closest_point, Point)
+ self.assertEqual(
+ country.mpoly.intersection(country.closest_point),
+ country.closest_point,
+ )
+
@skipUnlessDBFeature("has_LineLocatePoint_function")
def test_line_locate_point(self):
pos_expr = functions.LineLocatePoint(