summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Fedoseev <fedoseev.sergey@gmail.com>2017-11-24 22:30:53 +0500
committerTim Graham <timograham@gmail.com>2017-12-27 19:26:14 -0500
commitaefe624c62639c189845803f0935fc29e20b5fb8 (patch)
tree01c1dd7922645a1ac68f0eb3ca4f45dc9ec68834
parent44908d4d9360f72b9511aa0a878458285ba326d4 (diff)
Fixed #28841 -- Added ForcePolygonCW GIS function and deprecated ForceRHR.
-rw-r--r--django/contrib/gis/db/backends/mysql/operations.py8
-rw-r--r--django/contrib/gis/db/backends/oracle/operations.py6
-rw-r--r--django/contrib/gis/db/backends/postgis/operations.py2
-rw-r--r--django/contrib/gis/db/backends/spatialite/operations.py2
-rw-r--r--django/contrib/gis/db/models/functions.py13
-rw-r--r--docs/internals/deprecation.txt2
-rw-r--r--docs/ref/contrib/gis/functions.txt39
-rw-r--r--docs/releases/2.1.txt3
-rw-r--r--tests/gis_tests/geoapp/test_functions.py18
9 files changed, 73 insertions, 20 deletions
diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py
index f336ea1fb7..3432444b1d 100644
--- a/django/contrib/gis/db/backends/mysql/operations.py
+++ b/django/contrib/gis/db/backends/mysql/operations.py
@@ -57,10 +57,10 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
@cached_property
def unsupported_functions(self):
unsupported = {
- 'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle', 'ForceRHR',
- 'LineLocatePoint', 'MakeValid', 'MemSize', 'Perimeter',
- 'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid', 'Transform',
- 'Translate',
+ 'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle',
+ 'ForcePolygonCW', 'ForceRHR', 'LineLocatePoint', 'MakeValid',
+ 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale',
+ 'SnapToGrid', 'Transform', 'Translate',
}
if self.connection.mysql_version < (5, 7, 5):
unsupported.update({'AsGeoJSON', 'GeoHash', 'IsValid'})
diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py
index a57ee16161..f6b05bdd74 100644
--- a/django/contrib/gis/db/backends/oracle/operations.py
+++ b/django/contrib/gis/db/backends/oracle/operations.py
@@ -105,9 +105,9 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
}
unsupported_functions = {
- 'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth', 'Envelope', 'ForceRHR',
- 'GeoHash', 'LineLocatePoint', 'MakeValid', 'MemSize', 'Scale',
- 'SnapToGrid', 'Translate',
+ 'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth', 'Envelope',
+ 'ForcePolygonCW', 'ForceRHR', 'GeoHash', 'LineLocatePoint',
+ 'MakeValid', 'MemSize', 'Scale', 'SnapToGrid', 'Translate',
}
def geo_quote_name(self, name):
diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py
index 4fb9e61ceb..dd3fdb37b0 100644
--- a/django/contrib/gis/db/backends/postgis/operations.py
+++ b/django/contrib/gis/db/backends/postgis/operations.py
@@ -158,6 +158,8 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
'LengthSpheroid': 'ST_length_spheroid',
'MemSize': 'ST_mem_size',
})
+ if self.spatial_version < (2, 4, 0):
+ function_names['ForcePolygonCW'] = 'ST_ForceRHR'
return function_names
@cached_property
diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py
index 38abe72a65..ad64c76887 100644
--- a/django/contrib/gis/db/backends/spatialite/operations.py
+++ b/django/contrib/gis/db/backends/spatialite/operations.py
@@ -78,7 +78,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
@cached_property
def unsupported_functions(self):
- unsupported = {'BoundingCircle', 'ForceRHR', 'MemSize'}
+ unsupported = {'BoundingCircle', 'ForcePolygonCW', 'ForceRHR', '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 b3c8d521ca..45ce8193ee 100644
--- a/django/contrib/gis/db/models/functions.py
+++ b/django/contrib/gis/db/models/functions.py
@@ -1,3 +1,4 @@
+import warnings
from decimal import Decimal
from django.contrib.gis.db.models.fields import BaseSpatialField, GeometryField
@@ -10,6 +11,7 @@ from django.db.models import (
from django.db.models.expressions import Func, Value
from django.db.models.functions import Cast
from django.db.utils import NotSupportedError
+from django.utils.deprecation import RemovedInDjango30Warning
from django.utils.functional import cached_property
NUMERIC_TYPES = (int, float, Decimal)
@@ -274,9 +276,20 @@ class Envelope(GeomOutputGeoFunc):
arity = 1
+class ForcePolygonCW(GeomOutputGeoFunc):
+ arity = 1
+
+
class ForceRHR(GeomOutputGeoFunc):
arity = 1
+ def __init__(self, *args, **kwargs):
+ warnings.warn(
+ 'ForceRHR is deprecated in favor of ForcePolygonCW.',
+ RemovedInDjango30Warning, stacklevel=2,
+ )
+ super().__init__(*args, **kwargs)
+
class GeoHash(GeoFunc):
output_field = TextField()
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 1b7d5f73a9..248a0c642e 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -29,6 +29,8 @@ details on these changes.
* The ``field_name`` keyword argument of ``QuerySet.earliest()`` and
``latest()`` will be removed.
+* ``django.contrib.gis.db.models.functions.ForceRHR`` will be removed.
+
See the :ref:`Django 2.1 release notes <deprecated-features-2.1>` for more
details on these changes.
diff --git a/docs/ref/contrib/gis/functions.txt b/docs/ref/contrib/gis/functions.txt
index 66099c3317..fe8239d467 100644
--- a/docs/ref/contrib/gis/functions.txt
+++ b/docs/ref/contrib/gis/functions.txt
@@ -20,17 +20,18 @@ get a ``NotImplementedError`` exception.
Function's summary:
-================== ======================== ====================== =================== ================== =====================
-Measurement Relationships Operations Editors Output format Miscellaneous
-================== ======================== ====================== =================== ================== =====================
-: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:`LineLocatePoint` :class:`SnapToGrid` :class:`GeoHash`
-.. :class:`PointOnSurface` :class:`Transform`
+================== ======================== ====================== ======================= ================== =====================
+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:`ForceRHR` :class:`AsGML` :class:`MemSize`
+:class:`Length` :class:`Centroid` :class:`SymDifference` :class:`MakeValid` :class:`AsKML` :class:`NumGeometries`
+:class:`Perimeter` :class:`Envelope` :class:`Union` :class:`Reverse` :class:`AsSVG` :class:`NumPoints`
+.. :class:`LineLocatePoint` :class:`Scale` :class:`GeoHash`
+.. :class:`PointOnSurface` :class:`SnapToGrid`
+.. :class:`Transform`
.. :class:`Translate`
-================== ======================== ====================== =================== ================== =====================
+================== ======================== ====================== ======================= ================== =====================
``Area``
========
@@ -271,11 +272,29 @@ SpatiaLite
Accepts a single geographic field or expression and returns the geometry
representing the bounding box of the geometry.
+``ForcePolygonCW``
+==================
+
+.. class:: ForcePolygonCW(expression, **extra)
+
+.. versionadded:: 2.1
+
+*Availability*: `PostGIS <https://postgis.net/docs/ST_ForcePolygonCW.html>`__
+
+Accepts a single geographic field or expression and returns a modified version
+of the polygon/multipolygon in which all exterior rings are oriented clockwise
+and all interior rings are oriented counterclockwise. Non-polygonal geometries
+are returned unchanged.
+
``ForceRHR``
============
.. class:: ForceRHR(expression, **extra)
+.. deprecated:: 2.1
+
+ Use :class:`ForcePolygonCW` instead.
+
*Availability*: `PostGIS <https://postgis.net/docs/ST_ForceRHR.html>`__
Accepts a single geographic field or expression and returns a modified version
diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt
index 9d090b8244..bf69a59762 100644
--- a/docs/releases/2.1.txt
+++ b/docs/releases/2.1.txt
@@ -239,7 +239,8 @@ Features deprecated in 2.1
Miscellaneous
-------------
-* ...
+* The ``ForceRHR`` GIS function is deprecated in favor of the new
+ :class:`~django.contrib.gis.db.models.functions.ForcePolygonCW` function.
.. _removed-features-2.1:
diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py
index 33fe139fb0..2449650567 100644
--- a/tests/gis_tests/geoapp/test_functions.py
+++ b/tests/gis_tests/geoapp/test_functions.py
@@ -10,7 +10,8 @@ from django.contrib.gis.geos import (
from django.contrib.gis.measure import Area
from django.db import NotSupportedError, connection
from django.db.models import Sum
-from django.test import TestCase, skipUnlessDBFeature
+from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
+from django.utils.deprecation import RemovedInDjango30Warning
from ..utils import FuncTestMixin, mysql, oracle, postgis, spatialite
from .models import City, Country, CountryWebMercator, State, Track
@@ -215,7 +216,22 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
for country in countries:
self.assertIsInstance(country.envelope, Polygon)
+ @skipUnlessDBFeature("has_ForcePolygonCW_function")
+ def test_force_polygon_cw(self):
+ rings = (
+ ((0, 0), (5, 0), (0, 5), (0, 0)),
+ ((1, 1), (1, 3), (3, 1), (1, 1)),
+ )
+ rhr_rings = (
+ ((0, 0), (0, 5), (5, 0), (0, 0)),
+ ((1, 1), (3, 1), (1, 3), (1, 1)),
+ )
+ State.objects.create(name='Foo', poly=Polygon(*rings))
+ st = State.objects.annotate(force_polygon_cw=functions.ForcePolygonCW('poly')).get(name='Foo')
+ self.assertEqual(rhr_rings, st.force_polygon_cw.coords)
+
@skipUnlessDBFeature("has_ForceRHR_function")
+ @ignore_warnings(category=RemovedInDjango30Warning)
def test_force_rhr(self):
rings = (
((0, 0), (5, 0), (0, 5), (0, 0)),