diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 9d22425fab1..bafb99a058a 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -222,8 +222,16 @@ class GEOSGeometryBase(GEOSBase): "Return the dimension of this Geometry (0=point, 1=line, 2=surface)." return capi.get_dims(self.ptr) - def normalize(self): - "Convert this Geometry to normal form (or canonical form)." + def normalize(self, clone=False): + """ + Convert this Geometry to normal form (or canonical form). + If the `clone` keyword is set, then the geometry is not modified and a + normalized clone of the geometry is returned instead. + """ + if clone: + clone = self.clone() + capi.geos_normalize(clone.ptr) + return clone capi.geos_normalize(self.ptr) def make_valid(self): diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 93fe6e98959..a8057dc6319 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -665,9 +665,11 @@ Other Properties & Methods :class:`~django.contrib.gis.db.models.functions.MakeValid` database function. Requires GEOS 3.8. -.. method:: GEOSGeometry.normalize() +.. method:: GEOSGeometry.normalize(clone=False) - Converts this geometry to canonical form:: + Converts this geometry to canonical form. If the ``clone`` keyword is set, + then the geometry is not modified and a normalized clone of the geometry is + returned instead:: >>> g = MultiPoint(Point(0, 0), Point(2, 2), Point(1, 1)) >>> print(g) @@ -676,6 +678,10 @@ Other Properties & Methods >>> print(g) MULTIPOINT (2 2, 1 1, 0 0) + .. versionchanged:: 4.1 + + The ``clone`` argument was added. + ``Point`` --------- diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 38dcdb94e04..dc95a653c3e 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -139,6 +139,9 @@ Minor features * The new :meth:`.GEOSGeometry.make_valid()` method allows converting invalid geometries to valid ones. +* The new ``clone`` argument for :meth:`.GEOSGeometry.normalize` allows + creating a normalized clone of the geometry. + :mod:`django.contrib.messages` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py index 4754f17c9ba..fd3f0b6ebd1 100644 --- a/tests/gis_tests/geos_tests/test_geos.py +++ b/tests/gis_tests/geos_tests/test_geos.py @@ -1528,11 +1528,17 @@ class GEOSTest(SimpleTestCase, TestDataMixin): self.assertEqual(GEOSGeometry("POINT(1.0e-1 1.0e+1)"), Point(0.1, 10)) def test_normalize(self): - g = MultiPoint(Point(0, 0), Point(2, 2), Point(1, 1)) - self.assertIsNone(g.normalize()) - self.assertTrue( - g.equals_exact(MultiPoint(Point(2, 2), Point(1, 1), Point(0, 0))) - ) + multipoint = MultiPoint(Point(0, 0), Point(2, 2), Point(1, 1)) + normalized = MultiPoint(Point(2, 2), Point(1, 1), Point(0, 0)) + # Geometry is normalized in-place and nothing is returned. + multipoint_1 = multipoint.clone() + self.assertIsNone(multipoint_1.normalize()) + self.assertEqual(multipoint_1, normalized) + # If the `clone` keyword is set, then the geometry is not modified and + # a normalized clone of the geometry is returned instead. + multipoint_2 = multipoint.normalize(clone=True) + self.assertEqual(multipoint_2, normalized) + self.assertNotEqual(multipoint, normalized) @skipIf(geos_version_tuple() < (3, 8), "GEOS >= 3.8.0 is required") def test_make_valid(self):