Fixed #28841 -- Added ForcePolygonCW GIS function and deprecated ForceRHR.

This commit is contained in:
Sergey Fedoseev 2017-11-24 22:30:53 +05:00 committed by Tim Graham
parent 44908d4d93
commit aefe624c62
9 changed files with 73 additions and 20 deletions

View File

@ -57,10 +57,10 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
@cached_property @cached_property
def unsupported_functions(self): def unsupported_functions(self):
unsupported = { unsupported = {
'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle', 'ForceRHR', 'AsGML', 'AsKML', 'AsSVG', 'Azimuth', 'BoundingCircle',
'LineLocatePoint', 'MakeValid', 'MemSize', 'Perimeter', 'ForcePolygonCW', 'ForceRHR', 'LineLocatePoint', 'MakeValid',
'PointOnSurface', 'Reverse', 'Scale', 'SnapToGrid', 'Transform', 'MemSize', 'Perimeter', 'PointOnSurface', 'Reverse', 'Scale',
'Translate', 'SnapToGrid', 'Transform', 'Translate',
} }
if self.connection.mysql_version < (5, 7, 5): if self.connection.mysql_version < (5, 7, 5):
unsupported.update({'AsGeoJSON', 'GeoHash', 'IsValid'}) unsupported.update({'AsGeoJSON', 'GeoHash', 'IsValid'})

View File

@ -105,9 +105,9 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
} }
unsupported_functions = { unsupported_functions = {
'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth', 'Envelope', 'ForceRHR', 'AsGeoJSON', 'AsKML', 'AsSVG', 'Azimuth', 'Envelope',
'GeoHash', 'LineLocatePoint', 'MakeValid', 'MemSize', 'Scale', 'ForcePolygonCW', 'ForceRHR', 'GeoHash', 'LineLocatePoint',
'SnapToGrid', 'Translate', 'MakeValid', 'MemSize', 'Scale', 'SnapToGrid', 'Translate',
} }
def geo_quote_name(self, name): def geo_quote_name(self, name):

View File

@ -158,6 +158,8 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
'LengthSpheroid': 'ST_length_spheroid', 'LengthSpheroid': 'ST_length_spheroid',
'MemSize': 'ST_mem_size', 'MemSize': 'ST_mem_size',
}) })
if self.spatial_version < (2, 4, 0):
function_names['ForcePolygonCW'] = 'ST_ForceRHR'
return function_names return function_names
@cached_property @cached_property

View File

@ -78,7 +78,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
@cached_property @cached_property
def unsupported_functions(self): def unsupported_functions(self):
unsupported = {'BoundingCircle', 'ForceRHR', 'MemSize'} unsupported = {'BoundingCircle', 'ForcePolygonCW', 'ForceRHR', 'MemSize'}
if not self.lwgeom_version(): if not self.lwgeom_version():
unsupported |= {'Azimuth', 'GeoHash', 'IsValid', 'MakeValid'} unsupported |= {'Azimuth', 'GeoHash', 'IsValid', 'MakeValid'}
return unsupported return unsupported

View File

@ -1,3 +1,4 @@
import warnings
from decimal import Decimal from decimal import Decimal
from django.contrib.gis.db.models.fields import BaseSpatialField, GeometryField 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.expressions import Func, Value
from django.db.models.functions import Cast from django.db.models.functions import Cast
from django.db.utils import NotSupportedError from django.db.utils import NotSupportedError
from django.utils.deprecation import RemovedInDjango30Warning
from django.utils.functional import cached_property from django.utils.functional import cached_property
NUMERIC_TYPES = (int, float, Decimal) NUMERIC_TYPES = (int, float, Decimal)
@ -274,9 +276,20 @@ class Envelope(GeomOutputGeoFunc):
arity = 1 arity = 1
class ForcePolygonCW(GeomOutputGeoFunc):
arity = 1
class ForceRHR(GeomOutputGeoFunc): class ForceRHR(GeomOutputGeoFunc):
arity = 1 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): class GeoHash(GeoFunc):
output_field = TextField() output_field = TextField()

View File

@ -29,6 +29,8 @@ details on these changes.
* The ``field_name`` keyword argument of ``QuerySet.earliest()`` and * The ``field_name`` keyword argument of ``QuerySet.earliest()`` and
``latest()`` will be removed. ``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 See the :ref:`Django 2.1 release notes <deprecated-features-2.1>` for more
details on these changes. details on these changes.

View File

@ -20,17 +20,18 @@ get a ``NotImplementedError`` exception.
Function's summary: Function's summary:
================== ======================== ====================== =================== ================== ===================== ================== ======================== ====================== ======================= ================== =====================
Measurement Relationships Operations Editors Output format Miscellaneous Measurement Relationships Operations Editors Output format Miscellaneous
================== ======================== ====================== =================== ================== ===================== ================== ======================== ====================== ======================= ================== =====================
:class:`Area` :class:`Azimuth` :class:`Difference` :class:`ForceRHR` :class:`AsGeoJSON` :class:`IsValid` :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:`Distance` :class:`BoundingCircle` :class:`Intersection` :class:`ForceRHR` :class:`AsGML` :class:`MemSize`
:class:`Length` :class:`Centroid` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`NumGeometries` :class:`Length` :class:`Centroid` :class:`SymDifference` :class:`MakeValid` :class:`AsKML` :class:`NumGeometries`
:class:`Perimeter` :class:`Envelope` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumPoints` :class:`Perimeter` :class:`Envelope` :class:`Union` :class:`Reverse` :class:`AsSVG` :class:`NumPoints`
.. :class:`LineLocatePoint` :class:`SnapToGrid` :class:`GeoHash` .. :class:`LineLocatePoint` :class:`Scale` :class:`GeoHash`
.. :class:`PointOnSurface` :class:`Transform` .. :class:`PointOnSurface` :class:`SnapToGrid`
.. :class:`Transform`
.. :class:`Translate` .. :class:`Translate`
================== ======================== ====================== =================== ================== ===================== ================== ======================== ====================== ======================= ================== =====================
``Area`` ``Area``
======== ========
@ -271,11 +272,29 @@ SpatiaLite
Accepts a single geographic field or expression and returns the geometry Accepts a single geographic field or expression and returns the geometry
representing the bounding box of 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`` ``ForceRHR``
============ ============
.. class:: ForceRHR(expression, **extra) .. class:: ForceRHR(expression, **extra)
.. deprecated:: 2.1
Use :class:`ForcePolygonCW` instead.
*Availability*: `PostGIS <https://postgis.net/docs/ST_ForceRHR.html>`__ *Availability*: `PostGIS <https://postgis.net/docs/ST_ForceRHR.html>`__
Accepts a single geographic field or expression and returns a modified version Accepts a single geographic field or expression and returns a modified version

View File

@ -239,7 +239,8 @@ Features deprecated in 2.1
Miscellaneous 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: .. _removed-features-2.1:

View File

@ -10,7 +10,8 @@ from django.contrib.gis.geos import (
from django.contrib.gis.measure import Area from django.contrib.gis.measure import Area
from django.db import NotSupportedError, connection from django.db import NotSupportedError, connection
from django.db.models import Sum 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 ..utils import FuncTestMixin, mysql, oracle, postgis, spatialite
from .models import City, Country, CountryWebMercator, State, Track from .models import City, Country, CountryWebMercator, State, Track
@ -215,7 +216,22 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
for country in countries: for country in countries:
self.assertIsInstance(country.envelope, Polygon) 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") @skipUnlessDBFeature("has_ForceRHR_function")
@ignore_warnings(category=RemovedInDjango30Warning)
def test_force_rhr(self): def test_force_rhr(self):
rings = ( rings = (
((0, 0), (5, 0), (0, 5), (0, 0)), ((0, 0), (5, 0), (0, 5), (0, 0)),