diff --git a/python/core/geometry/qgsgeometry.sip b/python/core/geometry/qgsgeometry.sip index 7431f87c8be..7ed7a6a09de 100644 --- a/python/core/geometry/qgsgeometry.sip +++ b/python/core/geometry/qgsgeometry.sip @@ -188,6 +188,19 @@ Returns true if WKB of the geometry is of WKBMulti* type :rtype: bool %End + bool isSimple() const; +%Docstring + Determines whether the geometry is simple (according to OGC definition), + i.e. it has no anomalous geometric points, such as self-intersection or self-tangency. + Uses GEOS library for the test. +.. note:: + + This is useful mainly for linestrings and linear rings. Polygons are simple by definition, + for checking anomalies in polygon geometries one can use isGeosValid(). +.. versionadded:: 3.0 + :rtype: bool +%End + double area() const; %Docstring Returns the area of the geometry using GEOS diff --git a/python/core/geometry/qgsgeometryengine.sip b/python/core/geometry/qgsgeometryengine.sip index 89c9d309704..045eefec1ef 100644 --- a/python/core/geometry/qgsgeometryengine.sip +++ b/python/core/geometry/qgsgeometryengine.sip @@ -155,6 +155,13 @@ class QgsGeometryEngine :rtype: bool %End + virtual bool isSimple( QString *errorMsg = 0 ) const = 0; +%Docstring + Determines whether the geometry is simple (according to OGC definition). +.. versionadded:: 3.0 + :rtype: bool +%End + virtual int splitGeometry( const QgsLineString &splitLine, QList &newGeometries, bool topological, diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index f5a479aaaa2..acf41d1cbe8 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -1980,6 +1980,15 @@ bool QgsGeometry::isGeosValid() const return geos.isValid(); } +bool QgsGeometry::isSimple() const +{ + if ( !d->geometry ) + return false; + + QgsGeos geos( d->geometry ); + return geos.isSimple(); +} + bool QgsGeometry::isGeosEqual( const QgsGeometry &g ) const { if ( !d->geometry || !g.d->geometry ) diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 958b19805d2..23f8510ab2b 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -213,6 +213,15 @@ class CORE_EXPORT QgsGeometry */ bool isGeosValid() const; + /** Determines whether the geometry is simple (according to OGC definition), + * i.e. it has no anomalous geometric points, such as self-intersection or self-tangency. + * Uses GEOS library for the test. + * \note This is useful mainly for linestrings and linear rings. Polygons are simple by definition, + * for checking anomalies in polygon geometries one can use isGeosValid(). + * \since QGIS 3.0 + */ + bool isSimple() const; + /** Returns the area of the geometry using GEOS \since QGIS 1.5 */ diff --git a/src/core/geometry/qgsgeometryengine.h b/src/core/geometry/qgsgeometryengine.h index da98ecbd1cd..708f8fc1749 100644 --- a/src/core/geometry/qgsgeometryengine.h +++ b/src/core/geometry/qgsgeometryengine.h @@ -83,6 +83,11 @@ class CORE_EXPORT QgsGeometryEngine virtual bool isEqual( const QgsAbstractGeometry &geom, QString *errorMsg = nullptr ) const = 0; virtual bool isEmpty( QString *errorMsg ) const = 0; + /** Determines whether the geometry is simple (according to OGC definition). + * \since QGIS 3.0 + */ + virtual bool isSimple( QString *errorMsg = nullptr ) const = 0; + virtual int splitGeometry( const QgsLineString &splitLine, QList &newGeometries, bool topological, diff --git a/src/core/geometry/qgsgeos.cpp b/src/core/geometry/qgsgeos.cpp index c281c31eb7c..d1c8ceb4433 100644 --- a/src/core/geometry/qgsgeos.cpp +++ b/src/core/geometry/qgsgeos.cpp @@ -1494,6 +1494,20 @@ bool QgsGeos::isEmpty( QString *errorMsg ) const CATCH_GEOS_WITH_ERRMSG( false ); } +bool QgsGeos::isSimple( QString *errorMsg ) const +{ + if ( !mGeos ) + { + return false; + } + + try + { + return GEOSisSimple_r( geosinit.ctxt, mGeos ); + } + CATCH_GEOS_WITH_ERRMSG( false ); +} + GEOSCoordSequence *QgsGeos::createCoordinateSequence( const QgsCurve *curve, double precision, bool forceClose ) { bool segmentize = false; diff --git a/src/core/geometry/qgsgeos.h b/src/core/geometry/qgsgeos.h index 6eeb9e69545..4c23c2addca 100644 --- a/src/core/geometry/qgsgeos.h +++ b/src/core/geometry/qgsgeos.h @@ -71,6 +71,7 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine bool isValid( QString *errorMsg = nullptr ) 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; /** Splits this geometry according to a given line. \param splitLine the line that splits the geometry diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index 24f10f76a96..91c2b069ad7 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -124,6 +124,8 @@ class TestQgsGeometry : public QObject void makeValid(); + void isSimple(); + private: //! A helper method to do a render check to see if the geometry op is as expected bool renderCheck( const QString &testName, const QString &comment = QLatin1String( QLatin1String( "" ) ), int mismatchCount = 0 ); @@ -5423,5 +5425,32 @@ void TestQgsGeometry::makeValid() } } +void TestQgsGeometry::isSimple() +{ + typedef QPair InputWktAndExpectedResult; + QList geoms; + geoms << qMakePair( QString( "LINESTRING(0 0, 1 0, 1 1)" ), true ); + geoms << qMakePair( QString( "LINESTRING(0 0, 1 0, 1 1, 0 0)" ), true ); // may be closed (linear ring) + geoms << qMakePair( QString( "LINESTRING(0 0, 1 0, 1 1, 0 -1)" ), false ); // self-intersection + geoms << qMakePair( QString( "LINESTRING(0 0, 1 0, 1 1, 0.5 0, 0 1)" ), false ); // self-tangency + geoms << qMakePair( QString( "POINT(1 1)" ), true ); // points are simple + geoms << qMakePair( QString( "POLYGON((0 0, 1 1, 1 1, 0 0))" ), true ); // polygons are always simple, even if they are invalid + geoms << qMakePair( QString( "MULTIPOINT((1 1), (2 2))" ), true ); + geoms << qMakePair( QString( "MULTIPOINT((1 1), (1 1))" ), false ); // must not contain the same point twice + geoms << qMakePair( QString( "MULTILINESTRING((0 0, 1 0), (0 1, 1 1))" ), true ); + geoms << qMakePair( QString( "MULTILINESTRING((0 0, 1 0), (0 0, 1 0))" ), true ); // may be touching at endpoints + geoms << qMakePair( QString( "MULTILINESTRING((0 0, 1 1), (0 1, 1 0))" ), false ); // must not intersect each other + geoms << qMakePair( QString( "MULTIPOLYGON(((0 0, 1 1, 1 1, 0 0)),((0 0, 1 1, 1 1, 0 0)))" ), true ); // multi-polygons are always simple + + Q_FOREACH ( const InputWktAndExpectedResult &pair, geoms ) + { + QgsGeometry gInput = QgsGeometry::fromWkt( pair.first ); + QVERIFY( !gInput.isNull() ); + + bool res = gInput.isSimple(); + QCOMPARE( res, pair.second ); + } +} + QGSTEST_MAIN( TestQgsGeometry ) #include "testqgsgeometry.moc"