From 38ec9efe5ebcd0680c06bc805f197ede204b269a Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea <andreaerdna@libero.it> Date: Sat, 11 Jan 2025 17:23:28 +0100 Subject: [PATCH] [WFS] Fix filtering by QGIS expression in order to support "@geometry" (Fix #60094) (#60105) Fix #60094 --- .../vector/qgsvectorlayer.sip.in | 4 +- .../vector/qgsvectorlayer.sip.in | 4 +- src/core/qgsogcutils.cpp | 4 +- src/core/vector/qgsvectorlayer.h | 4 +- tests/src/core/testqgsogcutils.cpp | 135 ++++++++++++++---- 5 files changed, 119 insertions(+), 32 deletions(-) diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in index 2cf57cdf6761..c9924db14036 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -145,8 +145,8 @@ Also note: - You can use various functions available in the QGIS Expression list, however the function must exist server side and have the same name and arguments to work. -- Use the special $geometry parameter to provide the layer geometry column as input - into the spatial binary operators e.g intersects($geometry, geomFromWKT('POINT (5 6)')) +- Use the special ``@geometry`` parameter to provide the layer geometry column as input + into the spatial binary operators e.g ``intersects(@geometry, geomFromWKT('POINT (5 6)'))`` OGC API Features data provider (oapif) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index 2cf57cdf6761..c9924db14036 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -145,8 +145,8 @@ Also note: - You can use various functions available in the QGIS Expression list, however the function must exist server side and have the same name and arguments to work. -- Use the special $geometry parameter to provide the layer geometry column as input - into the spatial binary operators e.g intersects($geometry, geomFromWKT('POINT (5 6)')) +- Use the special ``@geometry`` parameter to provide the layer geometry column as input + into the spatial binary operators e.g ``intersects(@geometry, geomFromWKT('POINT (5 6)'))`` OGC API Features data provider (oapif) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/core/qgsogcutils.cpp b/src/core/qgsogcutils.cpp index a68a730f9ba2..16a3d25f3e1f 100644 --- a/src/core/qgsogcutils.cpp +++ b/src/core/qgsogcutils.cpp @@ -2318,7 +2318,7 @@ static bool isGeometryColumn( const QgsExpressionNode *node ) const QgsExpressionNodeFunction *fn = static_cast<const QgsExpressionNodeFunction *>( node ); QgsExpressionFunction *fd = QgsExpression::Functions()[fn->fnIndex()]; - return fd->name() == QLatin1String( "$geometry" ); + return fd->name() == QLatin1String( "$geometry" ) || ( fd->name() == QLatin1String( "var" ) && fn->referencedVariables().contains( QLatin1String( "geometry" ) ) ); } static QgsGeometry geometryFromConstExpr( const QgsExpressionNode *node ) @@ -2382,7 +2382,7 @@ QDomElement QgsOgcUtilsExprToFilter::expressionFunctionToOgcFilter( const QgsExp } else { - mErrorMessage = QObject::tr( "<BBOX> is currently supported only in form: bbox($geometry, geomFromWKT('…'))" ); + mErrorMessage = QObject::tr( "<BBOX> is currently supported only in form: bbox(@geometry, geomFromWKT('…'))" ); return QDomElement(); } } diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 4e3f5ba6145d..725e53fc6e19 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -216,8 +216,8 @@ typedef QSet<int> QgsAttributeIds; * * - You can use various functions available in the QGIS Expression list, * however the function must exist server side and have the same name and arguments to work. - * - Use the special $geometry parameter to provide the layer geometry column as input - * into the spatial binary operators e.g intersects($geometry, geomFromWKT('POINT (5 6)')) + * - Use the special ``@geometry`` parameter to provide the layer geometry column as input + * into the spatial binary operators e.g ``intersects(@geometry, geomFromWKT('POINT (5 6)'))`` * * \subsection oapif OGC API Features data provider (oapif) * diff --git a/tests/src/core/testqgsogcutils.cpp b/tests/src/core/testqgsogcutils.cpp index 6237a903b7a4..17b67212dbb4 100644 --- a/tests/src/core/testqgsogcutils.cpp +++ b/tests/src/core/testqgsogcutils.cpp @@ -733,26 +733,47 @@ void TestQgsOgcUtils::testExpressionToOgcFilter_data() "</ogc:Not>" "</ogc:Filter>" ); - QTest::newRow( "intersects_bbox" ) << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" - "<ogc:BBOX>" - "<ogc:PropertyName>geometry</ogc:PropertyName>" - "<gml:Box><gml:coordinates ts=\" \" cs=\",\">5,6 5,6</gml:coordinates></gml:Box>" - "</ogc:BBOX>" - "</ogc:Filter>" ); - - QTest::newRow( "intersects + wkt" ) << QStringLiteral( "intersects($geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" - "<ogc:Intersects>" - "<ogc:PropertyName>geometry</ogc:PropertyName>" - "<gml:Point><gml:coordinates ts=\" \" cs=\",\">5,6</gml:coordinates></gml:Point>" - "</ogc:Intersects>" - "</ogc:Filter>" ); - - QTest::newRow( "contains + gml" ) << QStringLiteral( "contains($geometry, geomFromGML('<Point><coordinates cs=\",\" ts=\" \">5,6</coordinates></Point>'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" - "<ogc:Contains>" - "<ogc:PropertyName>geometry</ogc:PropertyName>" - "<Point><coordinates ts=\" \" cs=\",\">5,6</coordinates></Point>" - "</ogc:Contains>" - "</ogc:Filter>" ); + QTest::newRow( "intersects_bbox $geometry" ) << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" + "<ogc:BBOX>" + "<ogc:PropertyName>geometry</ogc:PropertyName>" + "<gml:Box><gml:coordinates ts=\" \" cs=\",\">5,6 5,6</gml:coordinates></gml:Box>" + "</ogc:BBOX>" + "</ogc:Filter>" ); + + QTest::newRow( "intersects + wkt $geometry" ) << QStringLiteral( "intersects($geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" + "<ogc:Intersects>" + "<ogc:PropertyName>geometry</ogc:PropertyName>" + "<gml:Point><gml:coordinates ts=\" \" cs=\",\">5,6</gml:coordinates></gml:Point>" + "</ogc:Intersects>" + "</ogc:Filter>" ); + + QTest::newRow( "contains + gml $geometry" ) << QStringLiteral( "contains($geometry, geomFromGML('<Point><coordinates cs=\",\" ts=\" \">5,6</coordinates></Point>'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" + "<ogc:Contains>" + "<ogc:PropertyName>geometry</ogc:PropertyName>" + "<Point><coordinates ts=\" \" cs=\",\">5,6</coordinates></Point>" + "</ogc:Contains>" + "</ogc:Filter>" ); + + QTest::newRow( "intersects_bbox @geometry" ) << QStringLiteral( "intersects_bbox(@geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" + "<ogc:BBOX>" + "<ogc:PropertyName>geometry</ogc:PropertyName>" + "<gml:Box><gml:coordinates ts=\" \" cs=\",\">5,6 5,6</gml:coordinates></gml:Box>" + "</ogc:BBOX>" + "</ogc:Filter>" ); + + QTest::newRow( "intersects + wkt @geometry" ) << QStringLiteral( "intersects(@geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" + "<ogc:Intersects>" + "<ogc:PropertyName>geometry</ogc:PropertyName>" + "<gml:Point><gml:coordinates ts=\" \" cs=\",\">5,6</gml:coordinates></gml:Point>" + "</ogc:Intersects>" + "</ogc:Filter>" ); + + QTest::newRow( "contains + gml @geometry" ) << QStringLiteral( "contains(@geometry, geomFromGML('<Point><coordinates cs=\",\" ts=\" \">5,6</coordinates></Point>'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" + "<ogc:Contains>" + "<ogc:PropertyName>geometry</ogc:PropertyName>" + "<Point><coordinates ts=\" \" cs=\",\">5,6</coordinates></Point>" + "</ogc:Contains>" + "</ogc:Filter>" ); } void TestQgsOgcUtils::testExpressionToOgcFilterWFS11() @@ -791,7 +812,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS11_data() QTest::addColumn<QString>( "srsName" ); QTest::addColumn<QString>( "xmlText" ); - QTest::newRow( "bbox" ) + QTest::newRow( "bbox $geometry" ) << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" ) << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" ) << QString( @@ -805,6 +826,21 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS11_data() "</ogc:BBOX>" "</ogc:Filter>" ); + + QTest::newRow( "bbox @geometry" ) + << QStringLiteral( "intersects_bbox(@geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" ) + << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" ) + << QString( + "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">" + "<ogc:BBOX>" + "<ogc:PropertyName>my_geometry_name</ogc:PropertyName>" + "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">" + "<gml:lowerCorner>49 2</gml:lowerCorner>" + "<gml:upperCorner>50 3</gml:upperCorner>" + "</gml:Envelope>" + "</ogc:BBOX>" + "</ogc:Filter>" + ); } void TestQgsOgcUtils::testExpressionToOgcFilterWFS20() @@ -860,7 +896,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data() "</fes:PropertyIsEqualTo></fes:Filter>" ) << QStringLiteral( "myns" ) << QStringLiteral( "http://example.com/myns" ); - QTest::newRow( "bbox" ) + QTest::newRow( "bbox $geometry" ) << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" ) << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" ) << QString( @@ -876,7 +912,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data() ) << QString() << QString(); - QTest::newRow( "bbox with namespace" ) + QTest::newRow( "bbox with namespace $geometry" ) << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" ) << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" ) << QString( @@ -892,7 +928,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data() ) << QStringLiteral( "myns" ) << QStringLiteral( "http://example.com/myns" ); - QTest::newRow( "intersects" ) + QTest::newRow( "intersects $geometry" ) << QStringLiteral( "intersects($geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" ) << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" ) << QString( @@ -910,6 +946,57 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data() "</fes:Filter>" ) << QString() << QString(); + + QTest::newRow( "bbox @geometry" ) + << QStringLiteral( "intersects_bbox(@geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" ) + << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" ) + << QString( + "<fes:Filter xmlns:fes=\"http://www.opengis.net/fes/2.0\" xmlns:gml=\"http://www.opengis.net/gml/3.2\">" + "<fes:BBOX>" + "<fes:ValueReference>my_geometry_name</fes:ValueReference>" + "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">" + "<gml:lowerCorner>49 2</gml:lowerCorner>" + "<gml:upperCorner>50 3</gml:upperCorner>" + "</gml:Envelope>" + "</fes:BBOX>" + "</fes:Filter>" + ) + << QString() << QString(); + + QTest::newRow( "bbox with namespace @geometry" ) + << QStringLiteral( "intersects_bbox(@geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" ) + << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" ) + << QString( + "<fes:Filter xmlns:fes=\"http://www.opengis.net/fes/2.0\" xmlns:gml=\"http://www.opengis.net/gml/3.2\" xmlns:myns=\"http://example.com/myns\">" + "<fes:BBOX>" + "<fes:ValueReference>myns:my_geometry_name</fes:ValueReference>" + "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">" + "<gml:lowerCorner>49 2</gml:lowerCorner>" + "<gml:upperCorner>50 3</gml:upperCorner>" + "</gml:Envelope>" + "</fes:BBOX>" + "</fes:Filter>" + ) + << QStringLiteral( "myns" ) << QStringLiteral( "http://example.com/myns" ); + + QTest::newRow( "intersects @geometry" ) + << QStringLiteral( "intersects(@geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" ) + << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" ) + << QString( + "<fes:Filter xmlns:fes=\"http://www.opengis.net/fes/2.0\" xmlns:gml=\"http://www.opengis.net/gml/3.2\">" + "<fes:Intersects>" + "<fes:ValueReference>my_geometry_name</fes:ValueReference>" + "<gml:Polygon gml:id=\"qgis_id_geom_1\" srsName=\"urn:ogc:def:crs:EPSG::4326\">" + "<gml:exterior>" + "<gml:LinearRing>" + "<gml:posList srsDimension=\"2\">49 2 50 2 50 3 49 3 49 2</gml:posList>" + "</gml:LinearRing>" + "</gml:exterior>" + "</gml:Polygon>" + "</fes:Intersects>" + "</fes:Filter>" + ) + << QString() << QString(); } Q_DECLARE_METATYPE( QgsOgcUtils::GMLVersion )