mirror of
https://github.com/django/django.git
synced 2025-03-12 02:12:38 +00:00
Fixed #35705 -- Added Rotate GIS database function to rotate geometries.
This commit is contained in:
parent
f7017db92c
commit
51cab4ad51
@ -62,6 +62,7 @@ class BaseSpatialOperations:
|
||||
"Perimeter",
|
||||
"PointOnSurface",
|
||||
"Reverse",
|
||||
"Rotate",
|
||||
"Scale",
|
||||
"SnapToGrid",
|
||||
"SymDifference",
|
||||
|
@ -98,6 +98,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,19 @@ class Reverse(GeoFunc):
|
||||
arity = 1
|
||||
|
||||
|
||||
class Rotate(GeomOutputGeoFunc):
|
||||
def __init__(self, expression, angle, origin=None, **extra):
|
||||
expressions = [
|
||||
expression,
|
||||
self._handle_param(angle, "angle", NUMERIC_TYPES),
|
||||
]
|
||||
if origin is not None:
|
||||
if not isinstance(origin, Point):
|
||||
raise TypeError("origin argument must be a Point")
|
||||
expressions.append(Value(origin.wkt, output_field=GeometryField()))
|
||||
super().__init__(*expressions, **extra)
|
||||
|
||||
|
||||
class Scale(SQLiteDecimalToFloatMixin, GeomOutputGeoFunc):
|
||||
def __init__(self, expression, x, y, z=0.0, **extra):
|
||||
expressions = [
|
||||
|
@ -420,6 +420,7 @@ Function PostGIS Oracle MariaDB MySQL
|
||||
:class:`Perimeter` X X X
|
||||
:class:`PointOnSurface` X X X X
|
||||
:class:`Reverse` X X X
|
||||
:class:`Rotate` X
|
||||
:class:`Scale` X X
|
||||
:class:`SnapToGrid` X X
|
||||
:class:`SymDifference` X X X X X
|
||||
|
@ -28,10 +28,11 @@ Measurement Relationships Operations Edi
|
||||
:class:`Area` :class:`Azimuth` :class:`Difference` :class:`ForcePolygonCW` :class:`AsGeoJSON` :class:`IsEmpty`
|
||||
:class:`Distance` :class:`BoundingCircle` :class:`Intersection` :class:`MakeValid` :class:`AsGML` :class:`IsValid`
|
||||
:class:`GeometryDistance` :class:`Centroid` :class:`SymDifference` :class:`Reverse` :class:`AsKML` :class:`MemSize`
|
||||
:class:`Length` :class:`ClosestPoint` :class:`Union` :class:`Scale` :class:`AsSVG` :class:`NumGeometries`
|
||||
:class:`Perimeter` :class:`Envelope` :class:`SnapToGrid` :class:`FromWKB` :class:`AsWKB` :class:`NumPoints`
|
||||
:class:`LineLocatePoint` :class:`Transform` :class:`FromWKT` :class:`AsWKT`
|
||||
:class:`PointOnSurface` :class:`Translate` :class:`GeoHash`
|
||||
:class:`Length` :class:`ClosestPoint` :class:`Union` :class:`Rotate` :class:`AsSVG` :class:`NumGeometries`
|
||||
:class:`Perimeter` :class:`Envelope` :class:`Scale` :class:`FromWKB` :class:`AsWKB` :class:`NumPoints`
|
||||
:class:`LineLocatePoint` :class:`SnapToGrid` :class:`FromWKT` :class:`AsWKT`
|
||||
:class:`PointOnSurface` :class:`Transform` :class:`GeoHash`
|
||||
:class:`Translate`
|
||||
========================= ======================== ====================== ======================= ================== ================== ======================
|
||||
|
||||
``Area``
|
||||
@ -556,6 +557,19 @@ SpatiaLite
|
||||
Accepts a single geographic field or expression and returns a geometry with
|
||||
reversed coordinates.
|
||||
|
||||
``Rotate``
|
||||
==========
|
||||
|
||||
.. versionadded:: 6.0
|
||||
|
||||
.. class:: Rotate(expression, angle, origin=None, **extra)
|
||||
|
||||
*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 point, defined by the ``origin``
|
||||
parameter.
|
||||
|
||||
``Scale``
|
||||
=========
|
||||
|
||||
|
@ -68,6 +68,10 @@ Minor features
|
||||
* The new :attr:`.GEOSGeometry.hasm` property checks whether the geometry has
|
||||
the M dimension.
|
||||
|
||||
* 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,41 @@ 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
|
||||
tests = [
|
||||
{"angle": angle},
|
||||
{"angle": angle, "origin": Point(0, 0)},
|
||||
{"angle": angle, "origin": Point(1, 1)},
|
||||
]
|
||||
for params in tests:
|
||||
with self.subTest(params=params):
|
||||
qs = Country.objects.annotate(
|
||||
rotated=functions.Rotate("mpoly", **params)
|
||||
)
|
||||
for country in qs:
|
||||
for p1, p2 in zip(country.mpoly, country.rotated):
|
||||
for r1, r2 in zip(p1, p2):
|
||||
for c1, c2 in zip(r1.coords, r2.coords):
|
||||
origin = params.get("origin")
|
||||
if origin is None:
|
||||
origin = Point(0, 0)
|
||||
self.assertAlmostEqual(-c1[0] + 2 * origin.x, c2[0], 5)
|
||||
self.assertAlmostEqual(-c1[1] + 2 * origin.y, c2[1], 5)
|
||||
|
||||
@skipUnlessDBFeature("has_Rotate_function")
|
||||
def test_rotate_invalid_params(self):
|
||||
angle = math.pi
|
||||
bad_params_tests = [
|
||||
{"angle": angle, "origin": 0},
|
||||
{"angle": angle, "origin": [0, 0]},
|
||||
]
|
||||
msg = "origin argument must be a Point"
|
||||
for params in bad_params_tests:
|
||||
with self.subTest(params=params), self.assertRaisesMessage(TypeError, msg):
|
||||
functions.Rotate("mpoly", **params)
|
||||
|
||||
@skipUnlessDBFeature("has_Scale_function")
|
||||
def test_scale(self):
|
||||
xfac, yfac = 2, 3
|
||||
|
Loading…
x
Reference in New Issue
Block a user