Skip to content

Commit 850905e

Browse files
committed
[needs-docs][processing] Add option to check validity alg to ignore self-intersection
causing rings errors By default the algorithm now uses the strict OGC definition of polygon validity, where a polygon is marked as invalid if a self-intersecting ring causes an interior hole. If the "Ignore ring self intersections" option is checked, then this rule will be ignored and a more lenient validity check will be performed. Refs #16418, refs #21336 (cherry picked from commit 49742c3)
1 parent 25a42a9 commit 850905e

9 files changed

+152
-4
lines changed

python/plugins/processing/algs/help/qgis.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ qgis:checkvalidity: >
5151

5252
The geometries are classified in three groups (valid, invalid and error), and a vector layer is generated with the features in each of these categories.
5353

54+
By default the algorithm uses the strict OGC definition of polygon validity, where a polygon is marked as invalid if a self-intersecting ring causes an interior hole. If the "Ignore ring self intersections" option is checked, then this rule will be ignored and a more lenient validity check will be performed.
55+
5456
qgis:clip: >
5557
This algorithm clips a vector layer using the polygons of an additional polygons layer. Only the parts of the features in the input layer that falls within the polygons of the clipping layer will be added to the resulting layer.
5658

python/plugins/processing/algs/qgis/CheckValidity.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
QgsProcessingParameterFeatureSource,
4646
QgsProcessingParameterEnum,
4747
QgsProcessingParameterFeatureSink,
48-
QgsProcessingOutputNumber)
48+
QgsProcessingOutputNumber,
49+
QgsProcessingParameterBoolean)
4950
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
5051

5152
settings_method_key = "/qgis/digitizing/validate_geometries"
@@ -62,6 +63,7 @@ class CheckValidity(QgisAlgorithm):
6263
INVALID_COUNT = 'INVALID_COUNT'
6364
ERROR_OUTPUT = 'ERROR_OUTPUT'
6465
ERROR_COUNT = 'ERROR_COUNT'
66+
IGNORE_RING_SELF_INTERSECTION = 'IGNORE_RING_SELF_INTERSECTION'
6567

6668
def icon(self):
6769
return QgsApplication.getThemeIcon("/algorithms/mAlgorithmCheckGeometry.svg")
@@ -96,6 +98,9 @@ def initAlgorithm(self, config=None):
9698
'useCheckBoxes': True,
9799
'columns': 3}})
98100

101+
self.addParameter(QgsProcessingParameterBoolean(self.IGNORE_RING_SELF_INTERSECTION,
102+
self.tr('Ignore ring self intersections'), defaultValue=False))
103+
99104
self.addParameter(QgsProcessingParameterFeatureSink(self.VALID_OUTPUT, self.tr('Valid output'), QgsProcessing.TypeVectorAnyGeometry, '', True))
100105
self.addOutput(QgsProcessingOutputNumber(self.VALID_COUNT, self.tr('Count of valid features')))
101106

@@ -112,6 +117,7 @@ def displayName(self):
112117
return self.tr('Check validity')
113118

114119
def processAlgorithm(self, parameters, context, feedback):
120+
ignore_ring_self_intersection = self.parameterAsBool(parameters, self.IGNORE_RING_SELF_INTERSECTION, context)
115121
method_param = self.parameterAsEnum(parameters, self.METHOD, context)
116122
if method_param == 0:
117123
settings = QgsSettings()
@@ -121,10 +127,11 @@ def processAlgorithm(self, parameters, context, feedback):
121127
else:
122128
method = method_param - 1
123129

124-
results = self.doCheck(method, parameters, context, feedback)
130+
results = self.doCheck(method, parameters, context, feedback, ignore_ring_self_intersection)
125131
return results
126132

127-
def doCheck(self, method, parameters, context, feedback):
133+
def doCheck(self, method, parameters, context, feedback, ignore_ring_self_intersection):
134+
flags = QgsGeometry.FlagAllowSelfTouchingHoles if ignore_ring_self_intersection else QgsGeometry.ValidityFlags()
128135
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
129136
if source is None:
130137
raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_LAYER))
@@ -155,7 +162,7 @@ def doCheck(self, method, parameters, context, feedback):
155162

156163
valid = True
157164
if not geom.isNull() and not geom.isEmpty():
158-
errors = list(geom.validateGeometry(method))
165+
errors = list(geom.validateGeometry(method, flags))
159166
if errors:
160167
valid = False
161168
reasons = []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ poly_ring_self_intersection_error_ignore_self.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
8+
9+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="poly_ring_self_intersection_error_ignore_self" type="ogr:poly_ring_self_intersection_error_ignore_self_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="poly_ring_self_intersection_error_ignore_self_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="message" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:string">
22+
<xs:maxLength value="255"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
</xs:sequence>
27+
</xs:extension>
28+
</xs:complexContent>
29+
</xs:complexType>
30+
</xs:schema>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ poly_ring_self_intersection_invalid_ignore_self.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
8+
9+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="poly_ring_self_intersection_invalid_ignore_self" type="ogr:poly_ring_self_intersection_invalid_ignore_self_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="poly_ring_self_intersection_invalid_ignore_self_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="_errors" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:string">
22+
<xs:maxLength value="255"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
</xs:sequence>
27+
</xs:extension>
28+
</xs:complexContent>
29+
</xs:complexType>
30+
</xs:schema>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ poly_ring_self_intersection_valid_ignore_self.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>200</gml:X><gml:Y>200</gml:Y></gml:coord>
10+
<gml:coord><gml:X>400</gml:X><gml:Y>400</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:poly_ring_self_intersection_valid_ignore_self fid="poly_ring_self_intersection.0">
16+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:28356"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>200,400 400,400 400,200 300,200 350,250 250,250 300,200 200,200 200,400</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
17+
</ogr:poly_ring_self_intersection_valid_ignore_self>
18+
</gml:featureMember>
19+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="poly_ring_self_intersection_valid_ignore_self" type="ogr:poly_ring_self_intersection_valid_ignore_self_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="poly_ring_self_intersection_valid_ignore_self_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
</xs:sequence>
20+
</xs:extension>
21+
</xs:complexContent>
22+
</xs:complexType>
23+
</xs:schema>

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

+19
Original file line numberDiff line numberDiff line change
@@ -3764,6 +3764,25 @@ tests:
37643764
name: expected/poly_ring_self_intersection_valid.gml
37653765
type: vector
37663766

3767+
- algorithm: qgis:checkvalidity
3768+
name: Check validity polygon ring self intersection, ignoring self intersections
3769+
params:
3770+
IGNORE_RING_SELF_INTERSECTION: true
3771+
INPUT_LAYER:
3772+
name: custom/poly_ring_self_intersection.gml|layername=poly_ring_self_intersection
3773+
type: vector
3774+
METHOD: 2
3775+
results:
3776+
ERROR_OUTPUT:
3777+
name: expected/poly_ring_self_intersection_error_ignore_self.gml
3778+
type: vector
3779+
INVALID_OUTPUT:
3780+
name: expected/poly_ring_self_intersection_invalid_ignore_self.gml
3781+
type: vector
3782+
VALID_OUTPUT:
3783+
name: expected/poly_ring_self_intersection_valid_ignore_self.gml
3784+
type: vector
3785+
37673786
- algorithm: qgis:polygonize
37683787
name: Polygonize
37693788
params:

0 commit comments

Comments
 (0)