summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Fedoseev <fedoseev.sergey@gmail.com>2017-04-02 23:24:06 +0500
committerTim Graham <timograham@gmail.com>2017-04-02 14:24:06 -0400
commit5e710cf4a5dba1fc30e3b6775b83c2218e57cd36 (patch)
tree7c6f74edfb1861d6305178e6ddce5e9807ca1702
parent4f99ba84b3efd4b33721288cbbe1a605c7f937fd (diff)
Fixed #11854 -- Added Azimuth GIS function. (#8286)
-rw-r--r--django/contrib/gis/db/backends/base/operations.py2
-rw-r--r--django/contrib/gis/db/backends/mysql/operations.py2
-rw-r--r--django/contrib/gis/db/backends/oracle/operations.py6
-rw-r--r--django/contrib/gis/db/backends/spatialite/operations.py2
-rw-r--r--django/contrib/gis/db/models/functions.py6
-rw-r--r--docs/ref/contrib/gis/db-api.txt1
-rw-r--r--docs/ref/contrib/gis/functions.txt25
-rw-r--r--docs/releases/2.0.txt5
-rw-r--r--tests/gis_tests/geoapp/test_functions.py10
9 files changed, 46 insertions, 13 deletions
diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py
index ba3d3bdf67..0732d517c6 100644
--- a/django/contrib/gis/db/backends/base/operations.py
+++ b/django/contrib/gis/db/backends/base/operations.py
@@ -25,7 +25,7 @@ class BaseSpatialOperations:
# Blacklist/set of known unsupported functions of the backend
unsupported_functions = {
- 'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG',
+ 'Area', 'AsGeoJSON', 'AsGML', 'AsKML', 'AsSVG', 'Azimuth',
'BoundingCircle', 'Centroid', 'Difference', 'Distance', 'Envelope',
'ForceRHR', 'GeoHash', 'Intersection', 'IsValid', 'Length',
'LineLocatePoint', 'MakeValid', 'MemSize', 'NumGeometries',
diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py
index 33f148f5f6..0ea9f8f274 100644
--- a/django/contrib/gis/db/backends/mysql/operations.py
+++ b/django/contrib/gis/db/backends/mysql/operations.py
@@ -72,7 +72,7 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
@cached_property
def unsupported_functions(self):
unsupported = {
- 'AsGML', 'AsKML', 'AsSVG', 'BoundingCircle', 'ForceRHR',
+ 'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle', 'ForceRHR',
'LineLocatePoint', 'MakeValid', 'MemSize', 'Perimeter',
'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid', 'Transform',
'Translate',
diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py
index 649449b160..cbe90d6f53 100644
--- a/django/contrib/gis/db/backends/oracle/operations.py
+++ b/django/contrib/gis/db/backends/oracle/operations.py
@@ -111,9 +111,9 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
}
unsupported_functions = {
- 'AsGeoJSON', 'AsKML', 'AsSVG', 'Envelope', 'ForceRHR', 'GeoHash',
- 'LineLocatePoint', 'MakeValid', 'MemSize', 'Scale', 'SnapToGrid',
- 'Translate',
+ 'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth', 'Envelope', 'ForceRHR',
+ 'GeoHash', '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 23b38694f8..42b079bbfa 100644
--- a/django/contrib/gis/db/backends/spatialite/operations.py
+++ b/django/contrib/gis/db/backends/spatialite/operations.py
@@ -93,7 +93,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
def unsupported_functions(self):
unsupported = {'BoundingCircle', 'ForceRHR', 'MemSize'}
if not self.lwgeom_version():
- unsupported |= {'GeoHash', 'IsValid', 'MakeValid'}
+ unsupported |= {'Azimuth', 'GeoHash', 'IsValid', 'MakeValid'}
return unsupported
@cached_property
diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py
index ee2df7cfb4..3dd8efaa22 100644
--- a/django/contrib/gis/db/models/functions.py
+++ b/django/contrib/gis/db/models/functions.py
@@ -162,6 +162,12 @@ class Area(OracleToleranceMixin, GeoFunc):
return self.as_sql(compiler, connection, **extra_context)
+class Azimuth(GeoFunc):
+ output_field_class = FloatField
+ arity = 2
+ geom_param_pos = (0, 1)
+
+
class AsGeoJSON(GeoFunc):
output_field_class = TextField
diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt
index c3d7003636..cce38a3711 100644
--- a/docs/ref/contrib/gis/db-api.txt
+++ b/docs/ref/contrib/gis/db-api.txt
@@ -376,6 +376,7 @@ Function PostGIS Oracle MySQL Spat
:class:`AsGML` X X X
:class:`AsKML` X X
:class:`AsSVG` X X
+:class:`Azimuth` X X (LWGEOM)
:class:`BoundingCircle` X X
:class:`Centroid` X X X X
:class:`Difference` X X X (≥ 5.6.1) X
diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt
index 4bf1994ea6..6b3dc28593 100644
--- a/docs/ref/contrib/gis/functions.txt
+++ b/docs/ref/contrib/gis/functions.txt
@@ -23,11 +23,11 @@ Function's summary:
================== ======================= ====================== =================== ================== =====================
Measurement Relationships Operations Editors Output format Miscellaneous
================== ======================= ====================== =================== ================== =====================
-:class:`Area` :class:`BoundingCircle` :class:`Difference` :class:`ForceRHR` :class:`AsGeoJSON` :class:`IsValid`
-:class:`Distance` :class:`Centroid` :class:`Intersection` :class:`MakeValid` :class:`AsGML` :class:`MemSize`
-:class:`Length` :class:`Envelope` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`NumGeometries`
-:class:`Perimeter` :class:`PointOnSurface` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumPoints`
-.. :class:`SnapToGrid` :class:`GeoHash`
+:class:`Area` :class:`Azimuth` :class:`Difference` :class:`ForceRHR` :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:`PointOnSurface` :class:`SnapToGrid` :class:`GeoHash`
.. :class:`Transform`
.. :class:`Translate`
================== ======================= ====================== =================== ================== =====================
@@ -173,6 +173,21 @@ Keyword Argument Description
__ http://www.w3.org/Graphics/SVG/
+``Azimuth``
+===========
+
+.. class:: Azimuth(point_a, point_b, **extra)
+
+.. versionadded:: 2.0
+
+*Availability*: `PostGIS <https://postgis.net/docs/ST_Azimuth.html>`__,
+SpatiaLite (LWGEOM)
+
+Returns the azimuth in radians of the segment defined by the given point
+geometries, or ``None`` if the two points are coincident. The azimuth is angle
+referenced from north and is positive clockwise: north = ``0``; east = ``π/2``;
+south = ``π``; west = ``3π/2``.
+
``BoundingCircle``
==================
diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt
index bff9e76c88..f21d53cd8a 100644
--- a/docs/releases/2.0.txt
+++ b/docs/releases/2.0.txt
@@ -68,8 +68,9 @@ Minor features
:class:`~django.contrib.gis.db.models.functions.IsValid` function, and
:lookup:`isvalid` lookup.
-* Added the :class:`~django.contrib.gis.db.models.functions.LineLocatePoint`
- function, supported on PostGIS and SpatiaLite.
+* Added the :class:`~django.contrib.gis.db.models.functions.Azimuth` and
+ :class:`~django.contrib.gis.db.models.functions.LineLocatePoint` functions,
+ supported on PostGIS and SpatiaLite.
* Any :class:`~django.contrib.gis.geos.GEOSGeometry` imported from GeoJSON now
has its SRID set.
diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py
index ff6d1db9a8..6e4a87f212 100644
--- a/tests/gis_tests/geoapp/test_functions.py
+++ b/tests/gis_tests/geoapp/test_functions.py
@@ -1,4 +1,5 @@
import json
+import math
import re
from decimal import Decimal
@@ -145,6 +146,15 @@ class GISFunctionsTests(TestCase):
self.assertEqual(svg1, City.objects.annotate(svg=functions.AsSVG('point')).get(name='Pueblo').svg)
self.assertEqual(svg2, City.objects.annotate(svg=functions.AsSVG('point', relative=5)).get(name='Pueblo').svg)
+ @skipUnlessDBFeature("has_Azimuth_function")
+ def test_azimuth(self):
+ # Returns the azimuth in radians.
+ azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(1, 1, srid=4326))
+ self.assertAlmostEqual(City.objects.annotate(azimuth=azimuth_expr).first().azimuth, math.pi / 4)
+ # Returns None if the two points are coincident.
+ azimuth_expr = functions.Azimuth(Point(0, 0, srid=4326), Point(0, 0, srid=4326))
+ self.assertIsNone(City.objects.annotate(azimuth=azimuth_expr).first().azimuth)
+
@skipUnlessDBFeature("has_BoundingCircle_function")
def test_bounding_circle(self):
def circle_num_points(num_seg):