diff --git a/python/core/auto_generated/qgsogcutils.sip.in b/python/core/auto_generated/qgsogcutils.sip.in index ea0881f9c7bc..46bea48f91aa 100644 --- a/python/core/auto_generated/qgsogcutils.sip.in +++ b/python/core/auto_generated/qgsogcutils.sip.in @@ -25,6 +25,17 @@ Currently supported standards: %End public: + struct Context + { + + Context( const QgsMapLayer *layer = 0, const QgsCoordinateTransformContext &transformContext = QgsCoordinateTransformContext() ); +%Docstring +Constructs a Context from ``layer`` and ``transformContext`` +%End + const QgsMapLayer *layer; + QgsCoordinateTransformContext transformContext; + }; + enum GMLVersion { GML_2_1_2, @@ -32,16 +43,17 @@ Currently supported standards: GML_3_2_1, }; - static QgsGeometry geometryFromGML( const QString &xmlString ); + static QgsGeometry geometryFromGML( const QString &xmlString, const Context &context = Context() ); %Docstring Static method that creates geometry from GML :param xmlString: xml representation of the geometry. GML elements are expected to be in default namespace (\verbatim {... \endverbatim) or in "gml" namespace (\verbatim ... \endverbatim) +:param context: QgsOgcUtils context %End - static QgsGeometry geometryFromGML( const QDomNode &geometryNode ); + static QgsGeometry geometryFromGML( const QDomNode &geometryNode, const Context &context = Context() ); %Docstring Static method that creates geometry from GML %End diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 7df781317a89..a905f6da9e92 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -3122,10 +3122,20 @@ static QVariant fcnGeomFromWKB( const QVariantList &values, const QgsExpressionC return !geom.isNull() ? QVariant::fromValue( geom ) : QVariant(); } -static QVariant fcnGeomFromGML( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +static QVariant fcnGeomFromGML( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction * ) { QString gml = QgsExpressionUtils::getStringValue( values.at( 0 ), parent ); - QgsGeometry geom = QgsOgcUtils::geometryFromGML( gml ); + QgsOgcUtils::Context ogcContext; + if ( context ) + { + QgsWeakMapLayerPointer mapLayerPtr {context->variable( QStringLiteral( "layer" ) ).value() }; + if ( mapLayerPtr ) + { + ogcContext.layer = mapLayerPtr.data(); + ogcContext.transformContext = context->variable( QStringLiteral( "_project_transform_context" ) ).value(); + } + } + QgsGeometry geom = QgsOgcUtils::geometryFromGML( gml, ogcContext ); QVariant result = !geom.isNull() ? QVariant::fromValue( geom ) : QVariant(); return result; } diff --git a/src/core/qgsogcutils.cpp b/src/core/qgsogcutils.cpp index 986e262d5b1e..ebbd64f66a55 100644 --- a/src/core/qgsogcutils.cpp +++ b/src/core/qgsogcutils.cpp @@ -24,6 +24,7 @@ #include "qgsrectangle.h" #include "qgsvectorlayer.h" #include "qgsexpressioncontextutils.h" +#include "qgslogger.h" #include #include @@ -72,10 +73,11 @@ QgsOgcUtilsExprToFilter::QgsOgcUtilsExprToFilter( QDomDocument &doc, } } -QgsGeometry QgsOgcUtils::geometryFromGML( const QDomNode &geometryNode ) +QgsGeometry QgsOgcUtils::geometryFromGML( const QDomNode &geometryNode, const Context &context ) { QDomElement geometryTypeElement = geometryNode.toElement(); QString geomType = geometryTypeElement.tagName(); + QgsGeometry geometry; if ( !( geomType == QLatin1String( "Point" ) || geomType == QLatin1String( "LineString" ) || geomType == QLatin1String( "Polygon" ) || geomType == QLatin1String( "MultiPoint" ) || geomType == QLatin1String( "MultiLineString" ) || geomType == QLatin1String( "MultiPolygon" ) || @@ -84,7 +86,7 @@ QgsGeometry QgsOgcUtils::geometryFromGML( const QDomNode &geometryNode ) QDomNode geometryChild = geometryNode.firstChild(); if ( geometryChild.isNull() ) { - return QgsGeometry(); + return geometry; } geometryTypeElement = geometryChild.toElement(); geomType = geometryTypeElement.tagName(); @@ -97,43 +99,72 @@ QgsGeometry QgsOgcUtils::geometryFromGML( const QDomNode &geometryNode ) if ( geomType == QLatin1String( "Point" ) ) { - return geometryFromGMLPoint( geometryTypeElement ); + geometry = geometryFromGMLPoint( geometryTypeElement ); } else if ( geomType == QLatin1String( "LineString" ) ) { - return geometryFromGMLLineString( geometryTypeElement ); + geometry = geometryFromGMLLineString( geometryTypeElement ); } else if ( geomType == QLatin1String( "Polygon" ) ) { - return geometryFromGMLPolygon( geometryTypeElement ); + geometry = geometryFromGMLPolygon( geometryTypeElement ); } else if ( geomType == QLatin1String( "MultiPoint" ) ) { - return geometryFromGMLMultiPoint( geometryTypeElement ); + geometry = geometryFromGMLMultiPoint( geometryTypeElement ); } else if ( geomType == QLatin1String( "MultiLineString" ) ) { - return geometryFromGMLMultiLineString( geometryTypeElement ); + geometry = geometryFromGMLMultiLineString( geometryTypeElement ); } else if ( geomType == QLatin1String( "MultiPolygon" ) ) { - return geometryFromGMLMultiPolygon( geometryTypeElement ); + geometry = geometryFromGMLMultiPolygon( geometryTypeElement ); } else if ( geomType == QLatin1String( "Box" ) ) { - return QgsGeometry::fromRect( rectangleFromGMLBox( geometryTypeElement ) ); + geometry = QgsGeometry::fromRect( rectangleFromGMLBox( geometryTypeElement ) ); } else if ( geomType == QLatin1String( "Envelope" ) ) { - return QgsGeometry::fromRect( rectangleFromGMLEnvelope( geometryTypeElement ) ); + geometry = QgsGeometry::fromRect( rectangleFromGMLEnvelope( geometryTypeElement ) ); } else //unknown type { - return QgsGeometry(); + return geometry; } + + // Handle srsName if context has information about the layer and the transformation context + if ( context.layer ) + { + QgsCoordinateReferenceSystem geomSrs; + + if ( geometryTypeElement.hasAttribute( QStringLiteral( "srsName" ) ) ) + { + geomSrs.createFromString( geometryTypeElement.attribute( QStringLiteral( "srsName" ) ) ); + if ( geomSrs.isValid() && geomSrs != context.layer->crs() ) + { + const QgsCoordinateTransform transformer { geomSrs, context.layer->crs(), context.transformContext }; + try + { + const QgsGeometry::OperationResult result = geometry.transform( transformer ); + if ( result != QgsGeometry::OperationResult::Success ) + { + QgsDebugMsgLevel( QStringLiteral( "Error transforming geometry: %1" ).arg( result ), 2 ); + } + } + catch ( QgsCsException & ) + { + QgsDebugMsgLevel( QStringLiteral( "CS error transforming geometry" ), 2 ); + } + } + } + } + + return geometry; } -QgsGeometry QgsOgcUtils::geometryFromGML( const QString &xmlString ) +QgsGeometry QgsOgcUtils::geometryFromGML( const QString &xmlString, const Context &context ) { // wrap the string into a root tag to have "gml" namespace (and also as a default namespace) QString xml = QStringLiteral( "%2" ).arg( GML_NAMESPACE, xmlString ); @@ -141,7 +172,7 @@ QgsGeometry QgsOgcUtils::geometryFromGML( const QString &xmlString ) if ( !doc.setContent( xml, true ) ) return QgsGeometry(); - return geometryFromGML( doc.documentElement().firstChildElement() ); + return geometryFromGML( doc.documentElement().firstChildElement(), context ); } @@ -328,7 +359,8 @@ QgsGeometry QgsOgcUtils::geometryFromGMLPolygon( const QDomElement &geometryElem { return QgsGeometry(); } - if ( readGMLPositions( interiorPointList, posElement ) != 0 ) + // Note: readGMLPositions returns true on errors and false on success + if ( readGMLPositions( interiorPointList, posElement ) ) { return QgsGeometry(); } diff --git a/src/core/qgsogcutils.h b/src/core/qgsogcutils.h index 89cb7164603c..75958ca0353e 100644 --- a/src/core/qgsogcutils.h +++ b/src/core/qgsogcutils.h @@ -50,6 +50,25 @@ class CORE_EXPORT QgsOgcUtils { public: + /** + * The Context struct stores the current layer and coordinate transform context. + * \since QGIS 3.14 + */ + struct Context + { + + /** + * Constructs a Context from \a layer and \a transformContext + */ + Context( const QgsMapLayer *layer = nullptr, const QgsCoordinateTransformContext &transformContext = QgsCoordinateTransformContext() ) + : layer( layer ) + , transformContext( transformContext ) + { + } + const QgsMapLayer *layer = nullptr; + QgsCoordinateTransformContext transformContext; + }; + /** *GML version */ @@ -62,16 +81,17 @@ class CORE_EXPORT QgsOgcUtils /** * Static method that creates geometry from GML - \param xmlString xml representation of the geometry. GML elements are expected to be - in default namespace (\verbatim {... \endverbatim) or in - "gml" namespace (\verbatim ... \endverbatim) + * \param xmlString xml representation of the geometry. GML elements are expected to be + * in default namespace (\verbatim {... \endverbatim) or in + * "gml" namespace (\verbatim ... \endverbatim) + * \param context QgsOgcUtils context */ - static QgsGeometry geometryFromGML( const QString &xmlString ); + static QgsGeometry geometryFromGML( const QString &xmlString, const Context &context = Context() ); /** * Static method that creates geometry from GML */ - static QgsGeometry geometryFromGML( const QDomNode &geometryNode ); + static QgsGeometry geometryFromGML( const QDomNode &geometryNode, const Context &context = Context() ); //! Read rectangle from GML2 Box static QgsRectangle rectangleFromGMLBox( const QDomNode &boxNode ); @@ -279,7 +299,8 @@ class CORE_EXPORT QgsOgcUtils * Reads the \verbatim \endverbatim element and extracts the coordinates as points \param coords list where the found coordinates are appended \param elem the \verbatim \endverbatim element - \returns boolean for success*/ + \returns boolean FALSE on success + */ static bool readGMLCoordinates( QgsPolylineXY &coords, const QDomElement &elem ); /** @@ -288,7 +309,8 @@ class CORE_EXPORT QgsOgcUtils \param coords list where the found coordinates are appended \param elem the \verbatim \endverbatim or \verbatim \endverbatim element - \returns boolean for success*/ + \returns boolean FALSE on success + */ static bool readGMLPositions( QgsPolylineXY &coords, const QDomElement &elem ); diff --git a/src/server/services/wfs/qgswfsutils.cpp b/src/server/services/wfs/qgswfsutils.cpp index 2f6b67c85e30..bd10df1d34fc 100644 --- a/src/server/services/wfs/qgswfsutils.cpp +++ b/src/server/services/wfs/qgswfsutils.cpp @@ -100,14 +100,14 @@ namespace QgsWfs return nullptr; } - QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, const QgsProject *project ) + QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QgsProject *project ) { // Get the server feature ids in filter element QStringList collectedServerFids; return parseFilterElement( typeName, filterElem, collectedServerFids, project ); } - QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QStringList &serverFids, const QgsProject *project ) + QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QStringList &serverFids, const QgsProject *project, const QgsMapLayer *layer ) { QgsFeatureRequest request; @@ -147,7 +147,7 @@ namespace QgsWfs } else if ( !goidNodes.isEmpty() ) { - // Get the server feature idsin filter element + // Get the server feature ids in filter element QStringList collectedServerFids; QDomElement goidElem; for ( int f = 0; f < goidNodes.size(); f++ ) @@ -192,7 +192,8 @@ namespace QgsWfs } else if ( childElem.tagName() != QLatin1String( "PropertyName" ) ) { - QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem ); + QgsOgcUtils::Context ctx { layer, project ? project->transformContext() : QgsCoordinateTransformContext() }; + QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem, ctx ); request.setFilterRect( geom.boundingBox() ); } childElem = childElem.nextSiblingElement(); diff --git a/src/server/services/wfs/qgswfsutils.h b/src/server/services/wfs/qgswfsutils.h index 009169199ec6..76b8c2636f4a 100644 --- a/src/server/services/wfs/qgswfsutils.h +++ b/src/server/services/wfs/qgswfsutils.h @@ -60,12 +60,12 @@ namespace QgsWfs /** * Transform a Filter element to a feature request */ - QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, const QgsProject *project = nullptr ); + QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QgsProject *project = nullptr ); /** * Transform a Filter element to a feature request and update server feature ids */ - QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QStringList &serverFids, const QgsProject *project = nullptr ); + QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QStringList &serverFids, const QgsProject *project = nullptr, const QgsMapLayer *layer = nullptr ); // Define namespaces used in WFS documents const QString WFS_NAMESPACE = QStringLiteral( "http://www.opengis.net/wfs" ); diff --git a/tests/src/python/test_qgsserver_wfs.py b/tests/src/python/test_qgsserver_wfs.py index 7dd0bbaf480f..210f1b8c29c2 100644 --- a/tests/src/python/test_qgsserver_wfs.py +++ b/tests/src/python/test_qgsserver_wfs.py @@ -257,6 +257,62 @@ def test_getfeature_post(self): """ tests.append(('srsname_post', srsTemplate.format(""))) + # Issue https://github.com/qgis/QGIS/issues/36398 + # Check get feature within polygon having srsName=EPSG:4326 (same as the project/layer) + within4326FilterTemplate = """ + + + + + geometry + + + + + 8.20344131 44.90137909 + 8.20347748 44.90137909 + 8.20347748 44.90141005 + 8.20344131 44.90141005 + 8.20344131 44.90137909 + + + + + + + + +""" + tests.append(('within4326FilterTemplate_post', within4326FilterTemplate.format(""))) + + # Check get feature within polygon having srsName=EPSG:3857 (different from the project/layer) + # The coordinates are converted from the one in 4326 + within3857FilterTemplate = """ + + + + + geometry + + + + + 913202.90938171 5606008.98136456 + 913206.93580769 5606008.98136456 + 913206.93580769 5606013.84701639 + 913202.90938171 5606013.84701639 + 913202.90938171 5606008.98136456 + + + + + + + + +""" + tests.append(('within3857FilterTemplate_post', within3857FilterTemplate.format(""))) + srsTwoLayersTemplate = """