Skip to content

Commit

Permalink
[needs-docs][processing] Add option to check validity alg to ignore s…
Browse files Browse the repository at this point in the history
…elf-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)
  • Loading branch information
nyalldawson committed Mar 5, 2019
1 parent 25a42a9 commit 850905e
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 4 deletions.
2 changes: 2 additions & 0 deletions python/plugins/processing/algs/help/qgis.yaml
Expand Up @@ -51,6 +51,8 @@ qgis:checkvalidity: >

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.

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.

qgis:clip: >
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.

Expand Down
15 changes: 11 additions & 4 deletions python/plugins/processing/algs/qgis/CheckValidity.py
Expand Up @@ -45,7 +45,8 @@
QgsProcessingParameterFeatureSource,
QgsProcessingParameterEnum,
QgsProcessingParameterFeatureSink,
QgsProcessingOutputNumber)
QgsProcessingOutputNumber,
QgsProcessingParameterBoolean)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm

settings_method_key = "/qgis/digitizing/validate_geometries"
Expand All @@ -62,6 +63,7 @@ class CheckValidity(QgisAlgorithm):
INVALID_COUNT = 'INVALID_COUNT'
ERROR_OUTPUT = 'ERROR_OUTPUT'
ERROR_COUNT = 'ERROR_COUNT'
IGNORE_RING_SELF_INTERSECTION = 'IGNORE_RING_SELF_INTERSECTION'

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

self.addParameter(QgsProcessingParameterBoolean(self.IGNORE_RING_SELF_INTERSECTION,
self.tr('Ignore ring self intersections'), defaultValue=False))

self.addParameter(QgsProcessingParameterFeatureSink(self.VALID_OUTPUT, self.tr('Valid output'), QgsProcessing.TypeVectorAnyGeometry, '', True))
self.addOutput(QgsProcessingOutputNumber(self.VALID_COUNT, self.tr('Count of valid features')))

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

def processAlgorithm(self, parameters, context, feedback):
ignore_ring_self_intersection = self.parameterAsBool(parameters, self.IGNORE_RING_SELF_INTERSECTION, context)
method_param = self.parameterAsEnum(parameters, self.METHOD, context)
if method_param == 0:
settings = QgsSettings()
Expand All @@ -121,10 +127,11 @@ def processAlgorithm(self, parameters, context, feedback):
else:
method = method_param - 1

results = self.doCheck(method, parameters, context, feedback)
results = self.doCheck(method, parameters, context, feedback, ignore_ring_self_intersection)
return results

def doCheck(self, method, parameters, context, feedback):
def doCheck(self, method, parameters, context, feedback, ignore_ring_self_intersection):
flags = QgsGeometry.FlagAllowSelfTouchingHoles if ignore_ring_self_intersection else QgsGeometry.ValidityFlags()
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
if source is None:
raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT_LAYER))
Expand Down Expand Up @@ -155,7 +162,7 @@ def doCheck(self, method, parameters, context, feedback):

valid = True
if not geom.isNull() and not geom.isEmpty():
errors = list(geom.validateGeometry(method))
errors = list(geom.validateGeometry(method, flags))
if errors:
valid = False
reasons = []
Expand Down
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ poly_ring_self_intersection_error_ignore_self.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>

</ogr:FeatureCollection>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="poly_ring_self_intersection_error_ignore_self" type="ogr:poly_ring_self_intersection_error_ignore_self_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="poly_ring_self_intersection_error_ignore_self_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="message" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="255"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ poly_ring_self_intersection_invalid_ignore_self.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>

</ogr:FeatureCollection>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="poly_ring_self_intersection_invalid_ignore_self" type="ogr:poly_ring_self_intersection_invalid_ignore_self_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="poly_ring_self_intersection_invalid_ignore_self_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="_errors" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="255"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ poly_ring_self_intersection_valid_ignore_self.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>200</gml:X><gml:Y>200</gml:Y></gml:coord>
<gml:coord><gml:X>400</gml:X><gml:Y>400</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:poly_ring_self_intersection_valid_ignore_self fid="poly_ring_self_intersection.0">
<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>
</ogr:poly_ring_self_intersection_valid_ignore_self>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="poly_ring_self_intersection_valid_ignore_self" type="ogr:poly_ring_self_intersection_valid_ignore_self_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="poly_ring_self_intersection_valid_ignore_self_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
19 changes: 19 additions & 0 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -3764,6 +3764,25 @@ tests:
name: expected/poly_ring_self_intersection_valid.gml
type: vector

- algorithm: qgis:checkvalidity
name: Check validity polygon ring self intersection, ignoring self intersections
params:
IGNORE_RING_SELF_INTERSECTION: true
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_ignore_self.gml
type: vector
INVALID_OUTPUT:
name: expected/poly_ring_self_intersection_invalid_ignore_self.gml
type: vector
VALID_OUTPUT:
name: expected/poly_ring_self_intersection_valid_ignore_self.gml
type: vector

- algorithm: qgis:polygonize
name: Polygonize
params:
Expand Down

0 comments on commit 850905e

Please sign in to comment.