Преглед на файлове

Fixed #33136 -- Added GEOSGeometry.make_valid() method.

Claude Paroz преди 3 години
родител
ревизия
4ffada3609

+ 10 - 1
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):

+ 3 - 3
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

+ 1 - 0
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')

+ 10 - 0
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::

+ 2 - 1
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`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ 19 - 0
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)