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",
|
||||
"PointOnSurface",
|
||||
"Reverse",
|
||||
"Rotate",
|
||||
"Scale",
|
||||
"SnapToGrid",
|
||||
"SymDifference",
|
||||
|
@ -103,6 +103,7 @@ class MySQLOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
"Perimeter",
|
||||
"PointOnSurface",
|
||||
"Reverse",
|
||||
"Rotate",
|
||||
"Scale",
|
||||
"SnapToGrid",
|
||||
"Transform",
|
||||
|
@ -129,6 +129,7 @@ class OracleOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
"LineLocatePoint",
|
||||
"MakeValid",
|
||||
"MemSize",
|
||||
"Rotate",
|
||||
"Scale",
|
||||
"SnapToGrid",
|
||||
"Translate",
|
||||
|
@ -82,7 +82,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
|
||||
|
||||
@cached_property
|
||||
def unsupported_functions(self):
|
||||
unsupported = {"GeometryDistance", "IsEmpty", "MemSize"}
|
||||
unsupported = {"GeometryDistance", "IsEmpty", "MemSize", "Rotate"}
|
||||
if not self.geom_lib_version():
|
||||
unsupported |= {"Azimuth", "GeoHash", "MakeValid"}
|
||||
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.sql import AreaField, DistanceField
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
from django.contrib.gis.geos.point import Point
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db import NotSupportedError
|
||||
from django.db.models import (
|
||||
@ -529,6 +530,25 @@ class Reverse(GeoFunc):
|
||||
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):
|
||||
def __init__(self, expression, x, y, z=0.0, **extra):
|
||||
expressions = [
|
||||
|
@ -576,6 +576,32 @@ SpatiaLite
|
||||
Accepts a single geographic field or expression and returns a geometry with
|
||||
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``
|
||||
=========
|
||||
|
||||
|
@ -132,6 +132,10 @@ Minor features
|
||||
:class:`~django.contrib.gis.db.models.functions.IsValid` database functions
|
||||
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`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -612,6 +612,37 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
coords.reverse()
|
||||
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")
|
||||
def test_scale(self):
|
||||
xfac, yfac = 2, 3
|
||||
|
Loading…
Reference in New Issue
Block a user