diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index 06537053428..bb80b599ecc 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -1803,14 +1803,12 @@ True if the location available from :py:func:`where` is valid. ValidatorGeos, }; - void validateGeometry( QVector &errors /Out/, ValidationMethod method = ValidatorQgisInternal ) const; + void validateGeometry( QVector &errors /Out/, ValidationMethod method = ValidatorQgisInternal, QgsGeometry::ValidityFlags flags = 0 ) const; %Docstring Validates geometry and produces a list of geometry errors. The ``method`` argument dictates which validator to utilize. -.. note:: - - Available in Python bindings since QGIS 1.6 +The ``flags`` parameter indicates optional flags which control the type of validity checking performed. .. versionadded:: 1.5 %End diff --git a/python/plugins/processing/tests/testdata/custom/circular_strings.gpkg b/python/plugins/processing/tests/testdata/custom/circular_strings.gpkg index ed375763f4c..38e2738816d 100644 Binary files a/python/plugins/processing/tests/testdata/custom/circular_strings.gpkg and b/python/plugins/processing/tests/testdata/custom/circular_strings.gpkg differ diff --git a/python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.gml b/python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.gml new file mode 100644 index 00000000000..7485250bb7e --- /dev/null +++ b/python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.gml @@ -0,0 +1,19 @@ + + + + + 200200 + 400400 + + + + + + 200,400 400,400 400,200 300,200 350,250 250,250 300,200 200,200 200,400 + + + diff --git a/python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.xsd b/python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.xsd new file mode 100644 index 00000000000..e1dc7b178d8 --- /dev/null +++ b/python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.gml b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.gml new file mode 100644 index 00000000000..17dee243884 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.gml @@ -0,0 +1,20 @@ + + + + + 300200 + 300200 + + + + + + 300,200 + Ring self-intersection + + + diff --git a/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.xsd b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.xsd new file mode 100644 index 00000000000..2d5a7d459a2 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.gml b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.gml new file mode 100644 index 00000000000..b516c20866a --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.gml @@ -0,0 +1,20 @@ + + + + + 200200 + 400400 + + + + + + 200,400 400,400 400,200 300,200 350,250 250,250 300,200 200,200 200,400 + Ring self-intersection + + + diff --git a/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.xsd b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.xsd new file mode 100644 index 00000000000..bd8733d74fb --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.gml b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.gml new file mode 100644 index 00000000000..aa47a711d34 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.gml @@ -0,0 +1,9 @@ + + + missing + + diff --git a/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.xsd b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.xsd new file mode 100644 index 00000000000..765cb5c1d98 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 869b8a5a3f7..a813ff0a9d4 100755 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -3746,6 +3746,24 @@ tests: name: expected/valid.gml type: vector + - algorithm: qgis:checkvalidity + name: Check validity polygon ring self intersection + params: + INPUT_LAYER: + name: custom/poly_ring_self_intersection.gml|layername=poly_ring_self_intersection + type: vector + METHOD: 2 + results: + ERROR_OUTPUT: + name: expected/poly_ring_self_intersection_error.gml + type: vector + INVALID_OUTPUT: + name: expected/poly_ring_self_intersection_invalid.gml + type: vector + VALID_OUTPUT: + name: expected/poly_ring_self_intersection_valid.gml + type: vector + - algorithm: qgis:polygonize name: Polygonize params: diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index df77c0bb740..100623980bc 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -2457,7 +2457,7 @@ QgsGeometry QgsGeometry::forceRHR() const } -void QgsGeometry::validateGeometry( QVector &errors, ValidationMethod method ) const +void QgsGeometry::validateGeometry( QVector &errors, const ValidationMethod method, const QgsGeometry::ValidityFlags flags ) const { errors.clear(); if ( !d->geometry ) @@ -2480,7 +2480,7 @@ void QgsGeometry::validateGeometry( QVector &errors, Validat QgsGeos geos( d->geometry.get() ); QString error; QgsGeometry errorLoc; - if ( !geos.isValid( &error, true, &errorLoc ) ) + if ( !geos.isValid( &error, flags & FlagAllowSelfTouchingHoles, &errorLoc ) ) { if ( errorLoc.isNull() ) { diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 5c299e02510..82d094bad8c 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -1875,10 +1875,12 @@ class CORE_EXPORT QgsGeometry /** * Validates geometry and produces a list of geometry errors. * The \a method argument dictates which validator to utilize. - * \note Available in Python bindings since QGIS 1.6 + * + * The \a flags parameter indicates optional flags which control the type of validity checking performed. + * * \since QGIS 1.5 */ - void validateGeometry( QVector &errors SIP_OUT, ValidationMethod method = ValidatorQgisInternal ) const; + void validateGeometry( QVector &errors SIP_OUT, ValidationMethod method = ValidatorQgisInternal, QgsGeometry::ValidityFlags flags = nullptr ) const; /** * Compute the unary union on a list of \a geometries. May be faster than an iterative union on a set of geometries. diff --git a/tests/src/python/test_qgsgeometry.py b/tests/src/python/test_qgsgeometry.py index d9f3749d7ac..bcd0ca7d1ed 100644 --- a/tests/src/python/test_qgsgeometry.py +++ b/tests/src/python/test_qgsgeometry.py @@ -5064,23 +5064,27 @@ class TestQgsGeometry(unittest.TestCase): def testValidateGeometry(self): tests = [ - ["", []], - ["Point (100 100)", [], []], - ["MultiPoint (100 100, 100 200)", [], []], - ["LINESTRING (0 0, 0 100, 100 100)", [], []], - ["POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))", [], []], - ["MULTIPOLYGON(Polygon((-1 -1, 4 0, 4 2, 0 2, -1 -1)),Polygon((100 100, 200 100, 200 200, 100 200, 100 100)))", [], []], - ['MultiPolygon (((159865.14786298031685874 6768656.31838363595306873, 159858.97975336571107619 6769211.44824895076453686, 160486.07089751763851382 6769211.44824895076453686, 160481.95882444124436006 6768658.37442017439752817, 160163.27316101978067309 6768658.37442017439752817, 160222.89822062765597366 6769116.87056819349527359, 160132.43261294672265649 6769120.98264127038419247, 160163.27316101978067309 6768658.37442017439752817, 159865.14786298031685874 6768656.31838363595306873)))', [], []], - ['Polygon((0 3, 3 0, 3 3, 0 0, 0 3))', [QgsGeometry.Error('Self-intersection', QgsPointXY(1.5, 1.5))], [QgsGeometry.Error('segments 0 and 2 of line 0 intersect at 1.5, 1.5', QgsPointXY(1.5, 1.5))]], + ["", [], [], []], + ["Point (100 100)", [], [], []], + ["MultiPoint (100 100, 100 200)", [], [], []], + ["LINESTRING (0 0, 0 100, 100 100)", [], [], []], + ["POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))", [], [], []], + ["MULTIPOLYGON(Polygon((-1 -1, 4 0, 4 2, 0 2, -1 -1)),Polygon((100 100, 200 100, 200 200, 100 200, 100 100)))", [], [], []], + ['POLYGON ((200 400, 400 400, 400 200, 300 200, 350 250, 250 250, 300 200, 200 200, 200 400))', [QgsGeometry.Error('Ring self-intersection', QgsPointXY(300, 200))], [], []], + ['MultiPolygon (((159865.14786298031685874 6768656.31838363595306873, 159858.97975336571107619 6769211.44824895076453686, 160486.07089751763851382 6769211.44824895076453686, 160481.95882444124436006 6768658.37442017439752817, 160163.27316101978067309 6768658.37442017439752817, 160222.89822062765597366 6769116.87056819349527359, 160132.43261294672265649 6769120.98264127038419247, 160163.27316101978067309 6768658.37442017439752817, 159865.14786298031685874 6768656.31838363595306873)))', [QgsGeometry.Error('Ring self-intersection', QgsPointXY(160163.27316101978067309, 6768658.37442017439752817))], [], []], + ['Polygon((0 3, 3 0, 3 3, 0 0, 0 3))', [QgsGeometry.Error('Self-intersection', QgsPointXY(1.5, 1.5))], [QgsGeometry.Error('Self-intersection', QgsPointXY(1.5, 1.5))], [QgsGeometry.Error('segments 0 and 2 of line 0 intersect at 1.5, 1.5', QgsPointXY(1.5, 1.5))]], ] for t in tests: g1 = QgsGeometry.fromWkt(t[0]) res = g1.validateGeometry(QgsGeometry.ValidatorGeos) self.assertEqual(res, t[1], "mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], res[0].where() if res else '')) - res = g1.validateGeometry(QgsGeometry.ValidatorQgisInternal) + res = g1.validateGeometry(QgsGeometry.ValidatorGeos, QgsGeometry.FlagAllowSelfTouchingHoles) self.assertEqual(res, t[2], "mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[2], res[0].where() if res else '')) + res = g1.validateGeometry(QgsGeometry.ValidatorQgisInternal) + self.assertEqual(res, t[3], + "mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[3], res[0].where() if res else '')) def renderGeometry(self, geom, use_pen, as_polygon=False, as_painter_path=False): image = QImage(200, 200, QImage.Format_RGB32)