Skip to content

Commit

Permalink
Fix OGC utils filter srsName
Browse files Browse the repository at this point in the history
Adds a layer + coordinateTransform context to pass
to the ogc utils functions that read the GML geometry
so that it can be transformed to layer's CRS when
constructing the feature filter.

Fixes #36398
  • Loading branch information
elpaso committed Jun 9, 2020
1 parent 0f3525d commit 8cd7f0d
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 31 deletions.
16 changes: 14 additions & 2 deletions python/core/auto_generated/qgsogcutils.sip.in
Expand Up @@ -25,23 +25,35 @@ 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,
GML_3_1_0,
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 {<Point>...</Point> \endverbatim) or in
"gml" namespace (\verbatim <gml:Point>...</gml:Point> \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
Expand Down
14 changes: 12 additions & 2 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -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<QgsWeakMapLayerPointer>() };
if ( mapLayerPtr )
{
ogcContext.layer = mapLayerPtr.data();
ogcContext.transformContext = context->variable( QStringLiteral( "_project_transform_context" ) ).value<QgsCoordinateTransformContext>();
}
}
QgsGeometry geom = QgsOgcUtils::geometryFromGML( gml, ogcContext );
QVariant result = !geom.isNull() ? QVariant::fromValue( geom ) : QVariant();
return result;
}
Expand Down
60 changes: 46 additions & 14 deletions src/core/qgsogcutils.cpp
Expand Up @@ -24,6 +24,7 @@
#include "qgsrectangle.h"
#include "qgsvectorlayer.h"
#include "qgsexpressioncontextutils.h"
#include "qgslogger.h"

#include <QColor>
#include <QStringList>
Expand Down Expand Up @@ -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" ) ||
Expand All @@ -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();
Expand All @@ -97,51 +99,80 @@ 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( "<tmp xmlns=\"%1\" xmlns:gml=\"%1\">%2</tmp>" ).arg( GML_NAMESPACE, xmlString );
QDomDocument doc;
if ( !doc.setContent( xml, true ) )
return QgsGeometry();

return geometryFromGML( doc.documentElement().firstChildElement() );
return geometryFromGML( doc.documentElement().firstChildElement(), context );
}


Expand Down Expand Up @@ -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();
}
Expand Down
36 changes: 29 additions & 7 deletions src/core/qgsogcutils.h
Expand Up @@ -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
*/
Expand All @@ -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 {<Point>...</Point> \endverbatim) or in
"gml" namespace (\verbatim <gml:Point>...</gml:Point> \endverbatim)
* \param xmlString xml representation of the geometry. GML elements are expected to be
* in default namespace (\verbatim {<Point>...</Point> \endverbatim) or in
* "gml" namespace (\verbatim <gml:Point>...</gml:Point> \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 );
Expand Down Expand Up @@ -279,7 +299,8 @@ class CORE_EXPORT QgsOgcUtils
* Reads the \verbatim <gml:coordinates> \endverbatim element and extracts the coordinates as points
\param coords list where the found coordinates are appended
\param elem the \verbatim <gml:coordinates> \endverbatim element
\returns boolean for success*/
\returns boolean FALSE on success
*/
static bool readGMLCoordinates( QgsPolylineXY &coords, const QDomElement &elem );

/**
Expand All @@ -288,7 +309,8 @@ class CORE_EXPORT QgsOgcUtils
\param coords list where the found coordinates are appended
\param elem the \verbatim <gml:pos> \endverbatim or
\verbatim <gml:posList> \endverbatim element
\returns boolean for success*/
\returns boolean FALSE on success
*/
static bool readGMLPositions( QgsPolylineXY &coords, const QDomElement &elem );


Expand Down
9 changes: 5 additions & 4 deletions src/server/services/wfs/qgswfsutils.cpp
Expand Up @@ -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;

Expand Down Expand Up @@ -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++ )
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions src/server/services/wfs/qgswfsutils.h
Expand Up @@ -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" );
Expand Down
56 changes: 56 additions & 0 deletions tests/src/python/test_qgsserver_wfs.py
Expand Up @@ -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 = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<wfs:Query typeName="testlayer" srsName="EPSG:4326" xmlns:feature="http://www.qgis.org/gml">
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<Within>
<PropertyName>geometry</PropertyName>
<Polygon xmlns="http://www.opengis.net/gml" srsName="EPSG:4326">
<exterior>
<LinearRing>
<posList srsDimension="2">
8.20344131 44.90137909
8.20347748 44.90137909
8.20347748 44.90141005
8.20344131 44.90141005
8.20344131 44.90137909
</posList>
</LinearRing>
</exterior>
</Polygon>
</Within>
</ogc:Filter>
</wfs:Query>
</wfs:GetFeature>
"""
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 = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<wfs:Query typeName="testlayer" srsName="EPSG:3857" xmlns:feature="http://www.qgis.org/gml">
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<Within>
<PropertyName>geometry</PropertyName>
<Polygon xmlns="http://www.opengis.net/gml" srsName="EPSG:3857">
<exterior>
<LinearRing>
<posList srsDimension="2">
913202.90938171 5606008.98136456
913206.93580769 5606008.98136456
913206.93580769 5606013.84701639
913202.90938171 5606013.84701639
913202.90938171 5606008.98136456
</posList>
</LinearRing>
</exterior>
</Polygon>
</Within>
</ogc:Filter>
</wfs:Query>
</wfs:GetFeature>
"""
tests.append(('within3857FilterTemplate_post', within3857FilterTemplate.format("")))

srsTwoLayersTemplate = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<wfs:Query typeName="testlayer" srsName="EPSG:3857" xmlns:feature="http://www.qgis.org/gml">
Expand Down

0 comments on commit 8cd7f0d

Please sign in to comment.