Skip to content

Commit

Permalink
Add QgsGeometry::isGeosValid option to ignore self-touching rings
Browse files Browse the repository at this point in the history
(cherry picked from commit 6dbe4ee)
  • Loading branch information
nyalldawson committed Mar 7, 2019
1 parent 101c2f7 commit ef99da1
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 11 deletions.
15 changes: 13 additions & 2 deletions python/core/auto_generated/geometry/qgsgeometry.sip.in
Expand Up @@ -307,9 +307,18 @@ by equals() instead.
.. versionadded:: 1.5
%End

bool isGeosValid() const;
enum ValidityFlag
{
FlagAllowSelfTouchingHoles,
};
typedef QFlags<QgsGeometry::ValidityFlag> ValidityFlags;


bool isGeosValid( QgsGeometry::ValidityFlags flags = 0 ) const;
%Docstring
Checks validity of the geometry using GEOS
Checks validity of the geometry using GEOS.

The ``flags`` parameter indicates optional flags which control the type of validity checking performed.

.. versionadded:: 1.5
%End
Expand Down Expand Up @@ -2011,6 +2020,8 @@ Downgrades a point list from QgsPoint to :py:class:`QgsPointXY`

}; // class QgsGeometry

QFlags<QgsGeometry::ValidityFlag> operator|(QgsGeometry::ValidityFlag f1, QFlags<QgsGeometry::ValidityFlag> f2);



/************************************************************************
Expand Down
12 changes: 11 additions & 1 deletion python/core/auto_generated/geometry/qgsgeometryengine.sip.in
Expand Up @@ -207,7 +207,17 @@ pattern.

virtual double area( QString *errorMsg = 0 ) const = 0;
virtual double length( QString *errorMsg = 0 ) const = 0;
virtual bool isValid( QString *errorMsg = 0 ) const = 0;

virtual bool isValid( QString *errorMsg = 0, bool allowSelfTouchingHoles = false ) const = 0;
%Docstring
Returns true if the geometry is valid.

If the geometry is invalid, ``errorMsg`` will be filled with the reported geometry error.

The ``allowSelfTouchingHoles`` argument specifies whether 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.
%End

virtual bool isEqual( const QgsAbstractGeometry *geom, QString *errorMsg = 0 ) const = 0;
%Docstring
Expand Down
4 changes: 2 additions & 2 deletions src/core/geometry/qgsgeometry.cpp
Expand Up @@ -2462,7 +2462,7 @@ void QgsGeometry::validateGeometry( QVector<QgsGeometry::Error> &errors, Validat
QgsGeometryValidator::validateGeometry( *this, errors, method );
}

bool QgsGeometry::isGeosValid() const
bool QgsGeometry::isGeosValid( const QgsGeometry::ValidityFlags flags ) const
{
if ( !d->geometry )
{
Expand All @@ -2477,7 +2477,7 @@ bool QgsGeometry::isGeosValid() const

QgsGeos geos( d->geometry.get() );
mLastError.clear();
return geos.isValid( &mLastError );
return geos.isValid( &mLastError, flags & FlagAllowSelfTouchingHoles );
}

bool QgsGeometry::isSimple() const
Expand Down
15 changes: 13 additions & 2 deletions src/core/geometry/qgsgeometry.h
Expand Up @@ -339,11 +339,21 @@ class CORE_EXPORT QgsGeometry
*/
bool isGeosEqual( const QgsGeometry & ) const;

//! Validity check flags
enum ValidityFlag
{
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.
};
Q_DECLARE_FLAGS( ValidityFlags, ValidityFlag )

/**
* Checks validity of the geometry using GEOS
* Checks validity of the geometry using GEOS.
*
* The \a flags parameter indicates optional flags which control the type of validity checking performed.
*
* \since QGIS 1.5
*/
bool isGeosValid() const;
bool isGeosValid( QgsGeometry::ValidityFlags flags = nullptr ) const;

/**
* Determines whether the geometry is simple (according to OGC definition),
Expand Down Expand Up @@ -2150,6 +2160,7 @@ class CORE_EXPORT QgsGeometry
}; // class QgsGeometry

Q_DECLARE_METATYPE( QgsGeometry )
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsGeometry::ValidityFlags )

//! Writes the geometry to stream out. QGIS version compatibility is not guaranteed.
CORE_EXPORT QDataStream &operator<<( QDataStream &out, const QgsGeometry &geometry );
Expand Down
12 changes: 11 additions & 1 deletion src/core/geometry/qgsgeometryengine.h
Expand Up @@ -217,7 +217,17 @@ class CORE_EXPORT QgsGeometryEngine

virtual double area( QString *errorMsg = nullptr ) const = 0;
virtual double length( QString *errorMsg = nullptr ) const = 0;
virtual bool isValid( QString *errorMsg = nullptr ) const = 0;

/**
* Returns true if the geometry is valid.
*
* If the geometry is invalid, \a errorMsg will be filled with the reported geometry error.
*
* The \a allowSelfTouchingHoles argument specifies whether 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.
*/
virtual bool isValid( QString *errorMsg = nullptr, bool allowSelfTouchingHoles = false ) const = 0;

/**
* Checks if this is equal to \a geom.
Expand Down
11 changes: 9 additions & 2 deletions src/core/geometry/qgsgeos.cpp
Expand Up @@ -1658,7 +1658,7 @@ QgsAbstractGeometry *QgsGeos::convexHull( QString *errorMsg ) const
CATCH_GEOS_WITH_ERRMSG( nullptr );
}

bool QgsGeos::isValid( QString *errorMsg ) const
bool QgsGeos::isValid( QString *errorMsg, const bool allowSelfTouchingHoles ) const
{
if ( !mGeos )
{
Expand All @@ -1667,7 +1667,14 @@ bool QgsGeos::isValid( QString *errorMsg ) const

try
{
return GEOSisValid_r( geosinit.ctxt, mGeos.get() );
GEOSGeometry *g1 = nullptr;
char *r = nullptr;
char res = GEOSisValidDetail_r( geosinit.ctxt, mGeos.get(), allowSelfTouchingHoles ? GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE : 0, &r, &g1 );
const bool invalid = res != 1;

if ( invalid && errorMsg )
*errorMsg = QString( r );
return !invalid;
}
CATCH_GEOS_WITH_ERRMSG( false );
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/geometry/qgsgeos.h
Expand Up @@ -218,7 +218,7 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
bool relatePattern( const QgsAbstractGeometry *geom, const QString &pattern, QString *errorMsg = nullptr ) const override;
double area( QString *errorMsg = nullptr ) const override;
double length( QString *errorMsg = nullptr ) const override;
bool isValid( QString *errorMsg = nullptr ) const override;
bool isValid( QString *errorMsg = nullptr, bool allowSelfTouchingHoles = false ) const override;
bool isEqual( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const override;
bool isEmpty( QString *errorMsg = nullptr ) const override;
bool isSimple( QString *errorMsg = nullptr ) const override;
Expand Down
21 changes: 21 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Expand Up @@ -4977,6 +4977,27 @@ def testForceRHR(self):
self.assertEqual(res.asWkt(1), t[1],
"mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], res.asWkt(1)))

def testIsGeosValid(self):
tests = [
["", False, False, ''],
["Point (100 100)", True, True, ''],
["MultiPoint (100 100, 100 200)", True, True, ''],
["LINESTRING (0 0, 0 100, 100 100)", True, True, ''],
["POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))", True, True, ''],
["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, ''],
['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']
]
for t in tests:
g1 = QgsGeometry.fromWkt(t[0])
res = g1.isGeosValid()
self.assertEqual(res, t[1],
"mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], res))
if not res:
self.assertEqual(g1.lastError(), t[3], t[0])
res = g1.isGeosValid(QgsGeometry.FlagAllowSelfTouchingHoles)
self.assertEqual(res, t[2],
"mismatch for {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[2], res))

def renderGeometry(self, geom, use_pen, as_polygon=False, as_painter_path=False):
image = QImage(200, 200, QImage.Format_RGB32)
image.fill(QColor(0, 0, 0))
Expand Down

0 comments on commit ef99da1

Please sign in to comment.