From da942233e73226c1cd28f49e4b99dda323ce93c1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 4 Nov 2015 16:03:55 +1100 Subject: [PATCH] Add DE-9IM variant which tests against a specified pattern (available in PyQGIS/expression engine) --- python/core/geometry/qgsgeometryengine.sip | 10 +++++++ resources/function_help/json/relate | 27 +++++++++++++------ src/core/geometry/qgsgeometryengine.h | 10 +++++++ src/core/geometry/qgsgeos.cpp | 31 +++++++++++++++++++++- src/core/geometry/qgsgeos.h | 1 + src/core/qgsexpression.cpp | 23 ++++++++++++---- tests/src/core/testqgsexpression.cpp | 2 ++ 7 files changed, 90 insertions(+), 14 deletions(-) diff --git a/python/core/geometry/qgsgeometryengine.sip b/python/core/geometry/qgsgeometryengine.sip index c1ae57d108a..c0ffbd9f8dd 100644 --- a/python/core/geometry/qgsgeometryengine.sip +++ b/python/core/geometry/qgsgeometryengine.sip @@ -42,6 +42,16 @@ class QgsGeometryEngine */ virtual QString relate( const QgsAbstractGeometryV2& geom, QString* errorMsg = 0 ) const = 0; + /** Tests whether two geometries are related by a specified Dimensional Extended 9 Intersection Model (DE-9IM) + * pattern. + * @param geom geometry to relate to + * @param pattern DE-9IM pattern for match + * @param errorMsg destination storage for any error message + * @returns true if geometry relationship matches with pattern + * @note added in QGIS 2.14 + */ + virtual bool relatePattern( const QgsAbstractGeometryV2& geom, const QString& pattern, QString* errorMsg = 0 ) const = 0; + virtual double area( QString* errorMsg = 0 ) const = 0; virtual double length( QString* errorMsg = 0 ) const = 0; virtual bool isValid( QString* errorMsg = 0 ) const = 0; diff --git a/resources/function_help/json/relate b/resources/function_help/json/relate index 39177e81c0c..486a29825a1 100644 --- a/resources/function_help/json/relate +++ b/resources/function_help/json/relate @@ -1,12 +1,23 @@ { "name": "relate", "type": "function", - "description": "Returns the Dimensional Extended 9 Intersection Model (DE-9IM) representation of the relationship between two geometries.", - "arguments": [ - {"arg":"geometry","description":"a geometry"}, - {"arg":"geometry","description":"a geometry"} - ], - "examples": [ - { "expression":"relate( geom_from_wkt( 'LINESTRING(40 40,120 120)' ), geom_from_wkt( 'LINESTRING(40 40,60 120)' ) )", "returns":"'FF1F00102'"} - ] + "description": "Tests the Dimensional Extended 9 Intersection Model (DE-9IM) representation of the relationship between two geometries.", + "variants": [ + { "variant": "Relationship variant", + "variant_description": "Returns the Dimensional Extended 9 Intersection Model (DE-9IM) representation of the relationship between two geometries.", + "arguments": [ + {"arg":"geometry","description":"a geometry"}, + {"arg":"geometry","description":"a geometry"} + ], + "examples": [ { "expression":"relate( geom_from_wkt( 'LINESTRING(40 40,120 120)' ), geom_from_wkt( 'LINESTRING(40 40,60 120)' ) )", "returns":"'FF1F00102'" } ] }, + { + "variant": "Pattern match variant", + "variant_description": "Tests whether the DE-9IM relationship between two geometries matches a specified pattern.", + "arguments": [ + {"arg":"geometry","description":"a geometry"}, + {"arg":"geometry","description":"a geometry"}, + {"arg":"pattern","description":"DE-9IM pattern to match"} + ], + "examples": [ { "expression":"relate( geom_from_wkt( 'LINESTRING(40 40,120 120)' ), geom_from_wkt( 'LINESTRING(40 40,60 120)' ), '**1F001**' )", "returns":true}] + }] } diff --git a/src/core/geometry/qgsgeometryengine.h b/src/core/geometry/qgsgeometryengine.h index 01dd183701c..53d15c54ab2 100644 --- a/src/core/geometry/qgsgeometryengine.h +++ b/src/core/geometry/qgsgeometryengine.h @@ -69,6 +69,16 @@ class CORE_EXPORT QgsGeometryEngine */ virtual QString relate( const QgsAbstractGeometryV2& geom, QString* errorMsg = 0 ) const = 0; + /** Tests whether two geometries are related by a specified Dimensional Extended 9 Intersection Model (DE-9IM) + * pattern. + * @param geom geometry to relate to + * @param pattern DE-9IM pattern for match + * @param errorMsg destination storage for any error message + * @returns true if geometry relationship matches with pattern + * @note added in QGIS 2.14 + */ + virtual bool relatePattern( const QgsAbstractGeometryV2& geom, const QString& pattern, QString* errorMsg = 0 ) const = 0; + virtual double area( QString* errorMsg = 0 ) const = 0; virtual double length( QString* errorMsg = 0 ) const = 0; virtual bool isValid( QString* errorMsg = 0 ) const = 0; diff --git a/src/core/geometry/qgsgeos.cpp b/src/core/geometry/qgsgeos.cpp index 850202d606b..fcc673846b8 100644 --- a/src/core/geometry/qgsgeos.cpp +++ b/src/core/geometry/qgsgeos.cpp @@ -300,6 +300,35 @@ QString QgsGeos::relate( const QgsAbstractGeometryV2& geom, QString* errorMsg ) return result; } +bool QgsGeos::relatePattern( const QgsAbstractGeometryV2& geom, const QString& pattern, QString* errorMsg ) const +{ + if ( !mGeos ) + { + return false; + } + + GEOSGeomScopedPtr geosGeom( asGeos( &geom, mPrecision ) ); + if ( !geosGeom ) + { + return false; + } + + bool result = false; + try + { + result = ( GEOSRelatePattern_r( geosinit.ctxt, mGeos, geosGeom.get(), pattern.toLocal8Bit().constData() ) == 1 ); + } + catch ( GEOSException &e ) + { + if ( errorMsg ) + { + *errorMsg = e.what(); + } + } + + return result; +} + double QgsGeos::area( QString* errorMsg ) const { double area = -1.0; @@ -1527,7 +1556,7 @@ GEOSGeometry* QgsGeos::createGeosPoint( const QgsAbstractGeometryV2* point, int } } #if 0 //disabled until geos supports m-coordinates - if (pt->isMeasure() ) + if ( pt->isMeasure() ) { GEOSCoordSeq_setOrdinate_r( geosinit.ctxt, coordSeq, 0, 3, pt->m() ); } diff --git a/src/core/geometry/qgsgeos.h b/src/core/geometry/qgsgeos.h index 685648e7613..2d00a6037a4 100644 --- a/src/core/geometry/qgsgeos.h +++ b/src/core/geometry/qgsgeos.h @@ -62,6 +62,7 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine bool contains( const QgsAbstractGeometryV2& geom, QString* errorMsg = 0 ) const override; bool disjoint( const QgsAbstractGeometryV2& geom, QString* errorMsg = 0 ) const override; QString relate( const QgsAbstractGeometryV2& geom, QString* errorMsg = 0 ) const override; + bool relatePattern( const QgsAbstractGeometryV2& geom, const QString& pattern, QString* errorMsg = 0 ) const override; double area( QString* errorMsg = 0 ) const override; double length( QString* errorMsg = 0 ) const override; bool isValid( QString* errorMsg = 0 ) const override; diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp index a1e0fbd54a3..49d54a203c9 100644 --- a/src/core/qgsexpression.cpp +++ b/src/core/qgsexpression.cpp @@ -1477,17 +1477,30 @@ static QVariant fcnYMax( const QVariantList& values, const QgsExpressionContext* static QVariant fcnRelate( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) { + if ( values.length() < 2 || values.length() > 3 ) + return QVariant(); + QgsGeometry fGeom = getGeometry( values.at( 0 ), parent ); QgsGeometry sGeom = getGeometry( values.at( 1 ), parent ); if ( fGeom.isEmpty() || sGeom.isEmpty() ) return QVariant(); - QgsGeometryEngine* engine = QgsGeometry::createGeometryEngine( fGeom.geometry() ); - QString result = engine->relate( *sGeom.geometry() ); - delete engine; + QScopedPointer engine( QgsGeometry::createGeometryEngine( fGeom.geometry() ) ); - return QVariant::fromValue( result ); + if ( values.length() == 2 ) + { + //two geometry arguments, return relation + QString result = engine->relate( *sGeom.geometry() ); + return QVariant::fromValue( result ); + } + else + { + //three arguments, test pattern + QString pattern = getStringValue( values.at( 2 ), parent ); + bool result = engine->relatePattern( *sGeom.geometry(), pattern ); + return QVariant::fromValue( result ); + } } static QVariant fcnBbox( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent ) @@ -2309,7 +2322,7 @@ const QList& QgsExpression::Functions() << new StaticFunction( "y_max", 1, fcnYMax, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "ymax" ) << new StaticFunction( "geom_from_wkt", 1, fcnGeomFromWKT, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "geomFromWKT" ) << new StaticFunction( "geom_from_gml", 1, fcnGeomFromGML, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "geomFromGML" ) - << new StaticFunction( "relate", 2, fcnRelate, "GeometryGroup" ) + << new StaticFunction( "relate", -1, fcnRelate, "GeometryGroup" ) << new StaticFunction( "intersects_bbox", 2, fcnBbox, "GeometryGroup", QString(), false, QStringList(), false, QStringList() << "bbox" ) << new StaticFunction( "disjoint", 2, fcnDisjoint, "GeometryGroup" ) << new StaticFunction( "intersects", 2, fcnIntersects, "GeometryGroup" ) diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index d78b462810c..864f2f39ad2 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -452,6 +452,8 @@ class TestQgsExpression: public QObject QTest::newRow( "relate valid" ) << "relate(geom_from_wkt('POINT(110 120)'),geom_from_wkt('POLYGON((60 120,60 40,160 40,160 120,60 120))'))" << false << QVariant( "F0FFFF212" ); QTest::newRow( "relate bad 1" ) << "relate(geom_from_wkt(''),geom_from_wkt('POLYGON((60 120,60 40,160 40,160 120,60 120))'))" << false << QVariant(); QTest::newRow( "relate bad 2" ) << "relate(geom_from_wkt('POINT(110 120)'),geom_from_wkt(''))" << false << QVariant(); + QTest::newRow( "relate pattern true" ) << "relate( geom_from_wkt( 'LINESTRING(40 40,120 120)' ), geom_from_wkt( 'LINESTRING(40 40,60 120)' ), '**1F001**' )" << false << QVariant( true ); + QTest::newRow( "relate pattern false" ) << "relate( geom_from_wkt( 'LINESTRING(40 40,120 120)' ), geom_from_wkt( 'LINESTRING(40 40,60 120)' ), '**1F002**' )" << false << QVariant( false ); // string functions QTest::newRow( "lower" ) << "lower('HeLLo')" << false << QVariant( "hello" );