Skip to content

Commit 111f357

Browse files
committed
Add QgsGeometry::isGeosValid option to ignore self-touching rings
(cherry picked from commit 6dbe4ee)
1 parent 29bb9de commit 111f357

File tree

8 files changed

+81
-11
lines changed

8 files changed

+81
-11
lines changed

python/core/auto_generated/geometry/qgsgeometry.sip.in

+13-2
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,18 @@ by equals() instead.
313313
.. versionadded:: 1.5
314314
%End
315315

316-
bool isGeosValid() const;
316+
enum ValidityFlag
317+
{
318+
FlagAllowSelfTouchingHoles,
319+
};
320+
typedef QFlags<QgsGeometry::ValidityFlag> ValidityFlags;
321+
322+
323+
bool isGeosValid( QgsGeometry::ValidityFlags flags = 0 ) const;
317324
%Docstring
318-
Checks validity of the geometry using GEOS
325+
Checks validity of the geometry using GEOS.
326+
327+
The ``flags`` parameter indicates optional flags which control the type of validity checking performed.
319328

320329
.. versionadded:: 1.5
321330
%End
@@ -2107,6 +2116,8 @@ Downgrades a point list from QgsPoint to :py:class:`QgsPointXY`
21072116

21082117
}; // class QgsGeometry
21092118

2119+
QFlags<QgsGeometry::ValidityFlag> operator|(QgsGeometry::ValidityFlag f1, QFlags<QgsGeometry::ValidityFlag> f2);
2120+
21102121

21112122

21122123
/************************************************************************

python/core/auto_generated/geometry/qgsgeometryengine.sip.in

+11-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,17 @@ pattern.
207207

208208
virtual double area( QString *errorMsg = 0 ) const = 0;
209209
virtual double length( QString *errorMsg = 0 ) const = 0;
210-
virtual bool isValid( QString *errorMsg = 0 ) const = 0;
210+
211+
virtual bool isValid( QString *errorMsg = 0, bool allowSelfTouchingHoles = false ) const = 0;
212+
%Docstring
213+
Returns true if the geometry is valid.
214+
215+
If the geometry is invalid, ``errorMsg`` will be filled with the reported geometry error.
216+
217+
The ``allowSelfTouchingHoles`` argument specifies whether self-touching holes are permitted.
218+
OGC validity states that self-touching holes are NOT permitted, whilst other vendor
219+
validity checks (e.g. ESRI) permit self-touching holes.
220+
%End
211221

212222
virtual bool isEqual( const QgsAbstractGeometry *geom, QString *errorMsg = 0 ) const = 0;
213223
%Docstring

src/core/geometry/qgsgeometry.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -2462,7 +2462,7 @@ void QgsGeometry::validateGeometry( QVector<QgsGeometry::Error> &errors, Validat
24622462
QgsGeometryValidator::validateGeometry( *this, errors, method );
24632463
}
24642464

2465-
bool QgsGeometry::isGeosValid() const
2465+
bool QgsGeometry::isGeosValid( const QgsGeometry::ValidityFlags flags ) const
24662466
{
24672467
if ( !d->geometry )
24682468
{
@@ -2477,7 +2477,7 @@ bool QgsGeometry::isGeosValid() const
24772477

24782478
QgsGeos geos( d->geometry.get() );
24792479
mLastError.clear();
2480-
return geos.isValid( &mLastError );
2480+
return geos.isValid( &mLastError, flags & FlagAllowSelfTouchingHoles );
24812481
}
24822482

24832483
bool QgsGeometry::isSimple() const

src/core/geometry/qgsgeometry.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -345,11 +345,21 @@ class CORE_EXPORT QgsGeometry
345345
*/
346346
bool isGeosEqual( const QgsGeometry & ) const;
347347

348+
//! Validity check flags
349+
enum ValidityFlag
350+
{
351+
FlagAllowSelfTouchingHoles = 1 << 0, //!< Indicates that self-touching holes are permitted. OGC validity states that self-touching holes are NOT permitted, whilst other vendor validity checks (e.g. ESRI) permit self-touching holes.
352+
};
353+
Q_DECLARE_FLAGS( ValidityFlags, ValidityFlag )
354+
348355
/**
349-
* Checks validity of the geometry using GEOS
356+
* Checks validity of the geometry using GEOS.
357+
*
358+
* The \a flags parameter indicates optional flags which control the type of validity checking performed.
359+
*
350360
* \since QGIS 1.5
351361
*/
352-
bool isGeosValid() const;
362+
bool isGeosValid( QgsGeometry::ValidityFlags flags = nullptr ) const;
353363

354364
/**
355365
* Determines whether the geometry is simple (according to OGC definition),
@@ -2270,6 +2280,7 @@ class CORE_EXPORT QgsGeometry
22702280
}; // class QgsGeometry
22712281

22722282
Q_DECLARE_METATYPE( QgsGeometry )
2283+
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsGeometry::ValidityFlags )
22732284

22742285
//! Writes the geometry to stream out. QGIS version compatibility is not guaranteed.
22752286
CORE_EXPORT QDataStream &operator<<( QDataStream &out, const QgsGeometry &geometry );

src/core/geometry/qgsgeometryengine.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,17 @@ class CORE_EXPORT QgsGeometryEngine
217217

218218
virtual double area( QString *errorMsg = nullptr ) const = 0;
219219
virtual double length( QString *errorMsg = nullptr ) const = 0;
220-
virtual bool isValid( QString *errorMsg = nullptr ) const = 0;
220+
221+
/**
222+
* Returns true if the geometry is valid.
223+
*
224+
* If the geometry is invalid, \a errorMsg will be filled with the reported geometry error.
225+
*
226+
* The \a allowSelfTouchingHoles argument specifies whether self-touching holes are permitted.
227+
* OGC validity states that self-touching holes are NOT permitted, whilst other vendor
228+
* validity checks (e.g. ESRI) permit self-touching holes.
229+
*/
230+
virtual bool isValid( QString *errorMsg = nullptr, bool allowSelfTouchingHoles = false ) const = 0;
221231

222232
/**
223233
* Checks if this is equal to \a geom.

src/core/geometry/qgsgeos.cpp

+9-2
Original file line numberDiff line numberDiff line change
@@ -1658,7 +1658,7 @@ QgsAbstractGeometry *QgsGeos::convexHull( QString *errorMsg ) const
16581658
CATCH_GEOS_WITH_ERRMSG( nullptr );
16591659
}
16601660

1661-
bool QgsGeos::isValid( QString *errorMsg ) const
1661+
bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles ) const
16621662
{
16631663
if ( !mGeos )
16641664
{
@@ -1667,7 +1667,14 @@ bool QgsGeos::isValid( QString *errorMsg ) const
16671667

16681668
try
16691669
{
1670-
return GEOSisValid_r( geosinit.ctxt, mGeos.get() );
1670+
GEOSGeometry *g1 = nullptr;
1671+
char *r = nullptr;
1672+
char res = GEOSisValidDetail_r( geosinit.ctxt, mGeos.get(), allowSelfTouchingHoles ? GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE : 0, &r, &g1 );
1673+
const bool invalid = res != 1;
1674+
1675+
if ( invalid && errorMsg )
1676+
*errorMsg = QString( r );
1677+
return !invalid;
16711678
}
16721679
CATCH_GEOS_WITH_ERRMSG( false );
16731680
}

src/core/geometry/qgsgeos.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
218218
bool relatePattern( const QgsAbstractGeometry *geom, const QString &pattern, QString *errorMsg = nullptr ) const override;
219219
double area( QString *errorMsg = nullptr ) const override;
220220
double length( QString *errorMsg = nullptr ) const override;
221-
bool isValid( QString *errorMsg = nullptr ) const override;
221+
bool isValid( QString *errorMsg = nullptr, bool allowSelfTouchingHoles = false ) const override;
222222
bool isEqual( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
223223
bool isEmpty( QString *errorMsg = nullptr ) const override;
224224
bool isSimple( QString *errorMsg = nullptr ) const override;

tests/src/python/test_qgsgeometry.py

+21
Original file line numberDiff line numberDiff line change
@@ -5040,6 +5040,27 @@ def testForceRHR(self):
50405040
self.assertEqual(res.asWkt(1), t[1],
50415041
"mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], res.asWkt(1)))
50425042

5043+
def testIsGeosValid(self):
5044+
tests = [
5045+
["", False, False, ''],
5046+
["Point (100 100)", True, True, ''],
5047+
["MultiPoint (100 100, 100 200)", True, True, ''],
5048+
["LINESTRING (0 0, 0 100, 100 100)", True, True, ''],
5049+
["POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))", True, True, ''],
5050+
["MULTIPOLYGON(Polygon((-1 -1, 4 0, 4 2, 0 2, -1 -1)),Polygon((100 100, 200 100, 200 200, 100 200, 100 100)))", True, True, ''],
5051+
['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)))', False, True, 'Ring Self-intersection']
5052+
]
5053+
for t in tests:
5054+
g1 = QgsGeometry.fromWkt(t[0])
5055+
res = g1.isGeosValid()
5056+
self.assertEqual(res, t[1],
5057+
"mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], res))
5058+
if not res:
5059+
self.assertEqual(g1.lastError(), t[3], t[0])
5060+
res = g1.isGeosValid(QgsGeometry.FlagAllowSelfTouchingHoles)
5061+
self.assertEqual(res, t[2],
5062+
"mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[2], res))
5063+
50435064
def renderGeometry(self, geom, use_pen, as_polygon=False, as_painter_path=False):
50445065
image = QImage(200, 200, QImage.Format_RGB32)
50455066
image.fill(QColor(0, 0, 0))

0 commit comments

Comments
 (0)