summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2021-09-24 10:15:23 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-09-27 09:58:28 +0200
commit4ffada36097ceccfd6f0d358217ac3c40d2a729c (patch)
tree314864f4c59f246d46ec4fd2b47d2d04ea7d85bb
parentfb05ca420da1341c0d39cf1f0e2fb659be836c92 (diff)
Fixed #33136 -- Added GEOSGeometry.make_valid() method.
-rw-r--r--django/contrib/gis/geos/geometry.py11
-rw-r--r--django/contrib/gis/geos/prototypes/__init__.py6
-rw-r--r--django/contrib/gis/geos/prototypes/geom.py1
-rw-r--r--docs/ref/contrib/gis/geos.txt10
-rw-r--r--docs/releases/4.1.txt3
-rw-r--r--tests/gis_tests/geos_tests/test_geos.py19
6 files changed, 45 insertions, 5 deletions
diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py
index bdb3868ac5..36f6895224 100644
--- a/django/contrib/gis/geos/geometry.py
+++ b/django/contrib/gis/geos/geometry.py
@@ -11,7 +11,7 @@ from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos.base import GEOSBase
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
from django.contrib.gis.geos.error import GEOSException
-from django.contrib.gis.geos.libgeos import GEOM_PTR
+from django.contrib.gis.geos.libgeos import GEOM_PTR, geos_version_tuple
from django.contrib.gis.geos.mutable_list import ListMixin
from django.contrib.gis.geos.prepared import PreparedGeometry
from django.contrib.gis.geos.prototypes.io import (
@@ -219,6 +219,15 @@ class GEOSGeometryBase(GEOSBase):
"Convert this Geometry to normal form (or canonical form)."
capi.geos_normalize(self.ptr)
+ def make_valid(self):
+ """
+ Attempt to create a valid representation of a given invalid geometry
+ without losing any of the input vertices.
+ """
+ if geos_version_tuple() < (3, 8):
+ raise GEOSException('GEOSGeometry.make_valid() requires GEOS >= 3.8.0.')
+ return GEOSGeometry(capi.geos_makevalid(self.ptr), srid=self.srid)
+
# #### Unary predicates ####
@property
def empty(self):
diff --git a/django/contrib/gis/geos/prototypes/__init__.py b/django/contrib/gis/geos/prototypes/__init__.py
index 844ae4cda7..a79533fa14 100644
--- a/django/contrib/gis/geos/prototypes/__init__.py
+++ b/django/contrib/gis/geos/prototypes/__init__.py
@@ -12,9 +12,9 @@ from django.contrib.gis.geos.prototypes.coordseq import ( # NOQA
from django.contrib.gis.geos.prototypes.geom import ( # NOQA
create_collection, create_empty_polygon, create_linearring,
create_linestring, create_point, create_polygon, destroy_geom, geom_clone,
- geos_get_srid, geos_normalize, geos_set_srid, geos_type, geos_typeid,
- get_dims, get_extring, get_geomn, get_intring, get_nrings, get_num_coords,
- get_num_geoms,
+ geos_get_srid, geos_makevalid, geos_normalize, geos_set_srid, geos_type,
+ geos_typeid, get_dims, get_extring, get_geomn, get_intring, get_nrings,
+ get_num_coords, get_num_geoms,
)
from django.contrib.gis.geos.prototypes.misc import * # NOQA
from django.contrib.gis.geos.prototypes.predicates import ( # NOQA
diff --git a/django/contrib/gis/geos/prototypes/geom.py b/django/contrib/gis/geos/prototypes/geom.py
index a84f710900..4cd34504db 100644
--- a/django/contrib/gis/geos/prototypes/geom.py
+++ b/django/contrib/gis/geos/prototypes/geom.py
@@ -44,6 +44,7 @@ class StringFromGeom(GEOSFuncFactory):
# ### ctypes prototypes ###
# The GEOS geometry type, typeid, num_coordinates and number of geometries
+geos_makevalid = GeomOutput('GEOSMakeValid', argtypes=[GEOM_PTR])
geos_normalize = IntFromGeom('GEOSNormalize')
geos_type = StringFromGeom('GEOSGeomType')
geos_typeid = IntFromGeom('GEOSGeomTypeId')
diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt
index 84de1094fc..e8a88014cc 100644
--- a/docs/ref/contrib/gis/geos.txt
+++ b/docs/ref/contrib/gis/geos.txt
@@ -655,6 +655,16 @@ Other Properties & Methods
doesn't impose any constraints on the geometry's SRID if called with a
:class:`~django.contrib.gis.gdal.CoordTransform` object.
+.. method:: GEOSGeometry.make_valid()
+
+ .. versionadded:: 4.1
+
+ Returns a valid :class:`GEOSGeometry` equivalent, trying not to lose any of
+ the input vertices. If the geometry is already valid, it is returned
+ untouched. This is similar to the
+ :class:`~django.contrib.gis.db.models.functions.MakeValid` database
+ function. Requires GEOS 3.8.
+
.. method:: GEOSGeometry.normalize()
Converts this geometry to canonical form::
diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt
index 28dd574af5..8401103767 100644
--- a/docs/releases/4.1.txt
+++ b/docs/releases/4.1.txt
@@ -53,7 +53,8 @@ Minor features
:mod:`django.contrib.gis`
~~~~~~~~~~~~~~~~~~~~~~~~~
-* ...
+* The new :meth:`.GEOSGeometry.make_valid()` method allows converting invalid
+ geometries to valid ones.
: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 ab809b6630..27208e005d 100644
--- a/tests/gis_tests/geos_tests/test_geos.py
+++ b/tests/gis_tests/geos_tests/test_geos.py
@@ -1429,6 +1429,25 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
self.assertIsNone(g.normalize())
self.assertTrue(g.equals_exact(MultiPoint(Point(2, 2), Point(1, 1), Point(0, 0))))
+ @skipIf(geos_version_tuple() < (3, 8), 'GEOS >= 3.8.0 is required')
+ def test_make_valid(self):
+ poly = GEOSGeometry('POLYGON((0 0, 0 23, 23 0, 23 23, 0 0))')
+ self.assertIs(poly.valid, False)
+ valid_poly = poly.make_valid()
+ self.assertIs(valid_poly.valid, True)
+ self.assertNotEqual(valid_poly, poly)
+
+ valid_poly2 = valid_poly.make_valid()
+ self.assertIs(valid_poly2.valid, True)
+ self.assertEqual(valid_poly, valid_poly2)
+
+ @mock.patch('django.contrib.gis.geos.libgeos.geos_version', lambda: b'3.7.3')
+ def test_make_valid_geos_version(self):
+ msg = 'GEOSGeometry.make_valid() requires GEOS >= 3.8.0.'
+ poly = GEOSGeometry('POLYGON((0 0, 0 23, 23 0, 23 23, 0 0))')
+ with self.assertRaisesMessage(GEOSException, msg):
+ poly.make_valid()
+
def test_empty_point(self):
p = Point(srid=4326)
self.assertEqual(p.ogr.ewkt, p.ewkt)