mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #35705 -- Added Rotate GIS database function to rotate geometries.
This commit is contained in:
parent
20f9f61805
commit
3958387872
@ -62,6 +62,7 @@ class BaseSpatialOperations:
|
|||||||
"Perimeter",
|
"Perimeter",
|
||||||
"PointOnSurface",
|
"PointOnSurface",
|
||||||
"Reverse",
|
"Reverse",
|
||||||
|
"Rotate",
|
||||||
"Scale",
|
"Scale",
|
||||||
"SnapToGrid",
|
"SnapToGrid",
|
||||||
"SymDifference",
|
"SymDifference",
|
||||||
|
@ -103,6 +103,7 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
|
|||||||
"Perimeter",
|
"Perimeter",
|
||||||
"PointOnSurface",
|
"PointOnSurface",
|
||||||
"Reverse",
|
"Reverse",
|
||||||
|
"Rotate",
|
||||||
"Scale",
|
"Scale",
|
||||||
"SnapToGrid",
|
"SnapToGrid",
|
||||||
"Transform",
|
"Transform",
|
||||||
|
@ -129,6 +129,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
|
|||||||
"LineLocatePoint",
|
"LineLocatePoint",
|
||||||
"MakeValid",
|
"MakeValid",
|
||||||
"MemSize",
|
"MemSize",
|
||||||
|
"Rotate",
|
||||||
"Scale",
|
"Scale",
|
||||||
"SnapToGrid",
|
"SnapToGrid",
|
||||||
"Translate",
|
"Translate",
|
||||||
|
@ -82,7 +82,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def unsupported_functions(self):
|
def unsupported_functions(self):
|
||||||
unsupported = {"GeometryDistance", "IsEmpty", "MemSize"}
|
unsupported = {"GeometryDistance", "IsEmpty", "MemSize", "Rotate"}
|
||||||
if not self.geom_lib_version():
|
if not self.geom_lib_version():
|
||||||
unsupported |= {"Azimuth", "GeoHash", "MakeValid"}
|
unsupported |= {"Azimuth", "GeoHash", "MakeValid"}
|
||||||
if self.spatial_version < (5, 1):
|
if self.spatial_version < (5, 1):
|
||||||
|
@ -3,6 +3,7 @@ from decimal import Decimal
|
|||||||
from django.contrib.gis.db.models.fields import BaseSpatialField, GeometryField
|
from django.contrib.gis.db.models.fields import BaseSpatialField, GeometryField
|
||||||
from django.contrib.gis.db.models.sql import AreaField, DistanceField
|
from django.contrib.gis.db.models.sql import AreaField, DistanceField
|
||||||
from django.contrib.gis.geos import GEOSGeometry
|
from django.contrib.gis.geos import GEOSGeometry
|
||||||
|
from django.contrib.gis.geos.point import Point
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.db import NotSupportedError
|
from django.db import NotSupportedError
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
@ -529,6 +530,25 @@ class Reverse(GeoFunc):
|
|||||||
arity = 1
|
arity = 1
|
||||||
|
|
||||||
|
|
||||||
|
class Rotate(GeomOutputGeoFunc):
|
||||||
|
def __init__(self, expression, angle, x=0.0, y=0.0, origin=None, **extra):
|
||||||
|
expressions = [
|
||||||
|
expression,
|
||||||
|
self._handle_param(angle, "angle", NUMERIC_TYPES),
|
||||||
|
]
|
||||||
|
|
||||||
|
if origin is not None:
|
||||||
|
if not isinstance(origin, Point):
|
||||||
|
raise TypeError("Param origin must be of type Point")
|
||||||
|
expressions.append(Value(origin.wkt, output_field=GeometryField()))
|
||||||
|
else:
|
||||||
|
expressions = expressions + [
|
||||||
|
self._handle_param(x, "x", NUMERIC_TYPES),
|
||||||
|
self._handle_param(y, "y", NUMERIC_TYPES),
|
||||||
|
]
|
||||||
|
super().__init__(*expressions, **extra)
|
||||||
|
|
||||||
|
|
||||||
class Scale(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
class Scale(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
||||||
def __init__(self, expression, x, y, z=0.0, **extra):
|
def __init__(self, expression, x, y, z=0.0, **extra):
|
||||||
expressions = [
|
expressions = [
|
||||||
|
@ -576,6 +576,32 @@ SpatiaLite
|
|||||||
Accepts a single geographic field or expression and returns a geometry with
|
Accepts a single geographic field or expression and returns a geometry with
|
||||||
reversed coordinates.
|
reversed coordinates.
|
||||||
|
|
||||||
|
``Rotate``
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. class:: Rotate(expression, angle, x=0.0, y=0.0, origin=None, **extra)
|
||||||
|
|
||||||
|
.. versionadded:: 5.2
|
||||||
|
|
||||||
|
*Availability*: `PostGIS <https://postgis.net/docs/ST_Rotate.html>`__
|
||||||
|
|
||||||
|
Rotates a geometry by a specified ``angle`` around the origin. Optionally,
|
||||||
|
the rotation can be performed around a specified point, defined by either
|
||||||
|
the ``x`` and ``y`` parameters or the ``origin`` parameter.
|
||||||
|
|
||||||
|
:param expression: A geographic field or expression to rotate.
|
||||||
|
:param angle: The angle in radians by which to rotate the geometry.
|
||||||
|
Positive values result in counterclockwise rotation,
|
||||||
|
negative values result in clockwise rotation.
|
||||||
|
:param x: The X-coordinate of the point around which the geometry will be rotated.
|
||||||
|
:param y: The Y-coordinate of the point around which the geometry will be rotated.
|
||||||
|
:param origin: A :class:`~django.contrib.gis.geos.Point` around which
|
||||||
|
to rotate. Takes precedence over ``x`` and ``y``.
|
||||||
|
:param extra: Additional arguments passed to the underlying function.
|
||||||
|
|
||||||
|
:returns: A geometry with coordinates rotated by the specified angle.
|
||||||
|
:rtype: :class:`~django.contrib.gis.geos.GEOSGeometry`
|
||||||
|
|
||||||
``Scale``
|
``Scale``
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
@ -132,6 +132,10 @@ Minor features
|
|||||||
:class:`~django.contrib.gis.db.models.functions.IsValid` database functions
|
:class:`~django.contrib.gis.db.models.functions.IsValid` database functions
|
||||||
are now supported on MariaDB 11.7+.
|
are now supported on MariaDB 11.7+.
|
||||||
|
|
||||||
|
* The new :class:`~django.contrib.gis.db.models.functions.Rotate` database
|
||||||
|
function rotates a geometry by a specified angle around the origin or a
|
||||||
|
specified point.
|
||||||
|
|
||||||
:mod:`django.contrib.messages`
|
:mod:`django.contrib.messages`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -612,6 +612,37 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
|||||||
coords.reverse()
|
coords.reverse()
|
||||||
self.assertEqual(tuple(coords), track.reverse_geom.coords)
|
self.assertEqual(tuple(coords), track.reverse_geom.coords)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("has_Rotate_function")
|
||||||
|
def test_rotate(self):
|
||||||
|
angle = math.pi
|
||||||
|
qs_origin = Country.objects.annotate(
|
||||||
|
rotated=functions.Rotate("mpoly", angle=angle)
|
||||||
|
)
|
||||||
|
qs_xy_params = Country.objects.annotate(
|
||||||
|
rotated=functions.Rotate("mpoly", angle=angle, x=1, y=1)
|
||||||
|
)
|
||||||
|
qs_origin_param = Country.objects.annotate(
|
||||||
|
rotated=functions.Rotate("mpoly", angle=angle, origin=Point(0, 0))
|
||||||
|
)
|
||||||
|
for c_origin, c_xy_params, c_origin_point_param in zip(
|
||||||
|
qs_origin, qs_xy_params, qs_origin_param
|
||||||
|
):
|
||||||
|
for p1, p2 in zip(c_origin.mpoly, c_origin.rotated):
|
||||||
|
for r1, r2 in zip(p1, p2):
|
||||||
|
for c1, c2 in zip(r1.coords, r2.coords):
|
||||||
|
self.assertAlmostEqual(-c1[0], c2[0], 5)
|
||||||
|
self.assertAlmostEqual(-c1[1], c2[1], 5)
|
||||||
|
for p1, p2 in zip(c_xy_params.mpoly, c_xy_params.rotated):
|
||||||
|
for r1, r2 in zip(p1, p2):
|
||||||
|
for c1, c2 in zip(r1.coords, r2.coords):
|
||||||
|
self.assertAlmostEqual(-c1[0] + 2, c2[0], 5)
|
||||||
|
self.assertAlmostEqual(-c1[1] + 2, c2[1], 5)
|
||||||
|
for p1, p2 in zip(c_origin_point_param.mpoly, c_origin_point_param.rotated):
|
||||||
|
for r1, r2 in zip(p1, p2):
|
||||||
|
for c1, c2 in zip(r1.coords, r2.coords):
|
||||||
|
self.assertAlmostEqual(-c1[0], c2[0], 5)
|
||||||
|
self.assertAlmostEqual(-c1[1], c2[1], 5)
|
||||||
|
|
||||||
@skipUnlessDBFeature("has_Scale_function")
|
@skipUnlessDBFeature("has_Scale_function")
|
||||||
def test_scale(self):
|
def test_scale(self):
|
||||||
xfac, yfac = 2, 3
|
xfac, yfac = 2, 3
|
||||||
|
Loading…
Reference in New Issue
Block a user