mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
By default, validity check should treat ring self intersections as invalid
We use the OGC definition of validity to ensure consistent results with PostGIS, GDAL, etc Fixes #16418, fixes #21336
This commit is contained in:
parent
35f613d45e
commit
4e04d02293
@ -1803,14 +1803,12 @@ True if the location available from :py:func:`where` is valid.
|
||||
ValidatorGeos,
|
||||
};
|
||||
|
||||
void validateGeometry( QVector<QgsGeometry::Error> &errors /Out/, ValidationMethod method = ValidatorQgisInternal ) const;
|
||||
void validateGeometry( QVector<QgsGeometry::Error> &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
|
||||
|
Binary file not shown.
19
python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.gml
vendored
Normal file
19
python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.gml
vendored
Normal file
@ -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.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 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>
|
||||
</gml:featureMember>
|
||||
</ogr:FeatureCollection>
|
23
python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.xsd
vendored
Normal file
23
python/plugins/processing/tests/testdata/custom/poly_ring_self_intersection.xsd
vendored
Normal file
@ -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" type="ogr:poly_ring_self_intersection_Type" substitutionGroup="gml:_Feature"/>
|
||||
<xs:complexType name="poly_ring_self_intersection_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>
|
20
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.gml
vendored
Normal file
20
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.gml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?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.xsd"
|
||||
xmlns:ogr="http://ogr.maptools.org/"
|
||||
xmlns:gml="http://www.opengis.net/gml">
|
||||
<gml:boundedBy>
|
||||
<gml:Box>
|
||||
<gml:coord><gml:X>300</gml:X><gml:Y>200</gml:Y></gml:coord>
|
||||
<gml:coord><gml:X>300</gml:X><gml:Y>200</gml:Y></gml:coord>
|
||||
</gml:Box>
|
||||
</gml:boundedBy>
|
||||
|
||||
<gml:featureMember>
|
||||
<ogr:poly_ring_self_intersection_error fid="poly_ring_self_intersection_error.0">
|
||||
<ogr:geometryProperty><gml:Point srsName="EPSG:28356"><gml:coordinates>300,200</gml:coordinates></gml:Point></ogr:geometryProperty>
|
||||
<ogr:message>Ring self-intersection</ogr:message>
|
||||
</ogr:poly_ring_self_intersection_error>
|
||||
</gml:featureMember>
|
||||
</ogr:FeatureCollection>
|
30
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.xsd
vendored
Normal file
30
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_error.xsd
vendored
Normal file
@ -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" type="ogr:poly_ring_self_intersection_error_Type" substitutionGroup="gml:_Feature"/>
|
||||
<xs:complexType name="poly_ring_self_intersection_error_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>
|
20
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.gml
vendored
Normal file
20
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.gml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?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.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_invalid 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:_errors>Ring self-intersection</ogr:_errors>
|
||||
</ogr:poly_ring_self_intersection_invalid>
|
||||
</gml:featureMember>
|
||||
</ogr:FeatureCollection>
|
30
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.xsd
vendored
Normal file
30
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_invalid.xsd
vendored
Normal file
@ -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" type="ogr:poly_ring_self_intersection_invalid_Type" substitutionGroup="gml:_Feature"/>
|
||||
<xs:complexType name="poly_ring_self_intersection_invalid_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>
|
9
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.gml
vendored
Normal file
9
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.gml
vendored
Normal file
@ -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_valid.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>
|
23
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.xsd
vendored
Normal file
23
python/plugins/processing/tests/testdata/expected/poly_ring_self_intersection_valid.xsd
vendored
Normal file
@ -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" type="ogr:poly_ring_self_intersection_valid_Type" substitutionGroup="gml:_Feature"/>
|
||||
<xs:complexType name="poly_ring_self_intersection_valid_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>
|
@ -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:
|
||||
|
@ -2457,7 +2457,7 @@ QgsGeometry QgsGeometry::forceRHR() const
|
||||
}
|
||||
|
||||
|
||||
void QgsGeometry::validateGeometry( QVector<QgsGeometry::Error> &errors, ValidationMethod method ) const
|
||||
void QgsGeometry::validateGeometry( QVector<QgsGeometry::Error> &errors, const ValidationMethod method, const QgsGeometry::ValidityFlags flags ) const
|
||||
{
|
||||
errors.clear();
|
||||
if ( !d->geometry )
|
||||
@ -2480,7 +2480,7 @@ void QgsGeometry::validateGeometry( QVector<QgsGeometry::Error> &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() )
|
||||
{
|
||||
|
@ -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<QgsGeometry::Error> &errors SIP_OUT, ValidationMethod method = ValidatorQgisInternal ) const;
|
||||
void validateGeometry( QVector<QgsGeometry::Error> &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.
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user