QGIS API Documentation 3.27.0-Master (f261cc1f8b)
qgswfsutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgswfssutils.cpp
3 -------------------------
4 begin : December 20 , 2016
5 copyright : (C) 2007 by Marco Hugentobler ( parts from qgswmshandler)
6 (C) 2012 by René-Luc D'Hont ( parts from qgswmshandler)
7 (C) 2014 by Alessandro Pasotti ( parts from qgswmshandler)
8 (C) 2017 by David Marteau
9 email : marco dot hugentobler at karto dot baug dot ethz dot ch
10 a dot pasotti at itopen dot it
11 david dot marteau at 3liz dot com
12 ***************************************************************************/
13
14/***************************************************************************
15 * *
16 * This program is free software; you can redistribute it and/or modify *
17 * it under the terms of the GNU General Public License as published by *
18 * the Free Software Foundation; either version 2 of the License, or *
19 * (at your option) any later version. *
20 * *
21 ***************************************************************************/
22
23#include "qgswfsutils.h"
24#include "qgsogcutils.h"
26#include "qgswfsparameters.h"
27#include "qgsvectorlayer.h"
28#include "qgsproject.h"
29
30namespace QgsWfs
31{
33 {
34 return QStringLiteral( "1.1.0" );
35 }
36
37 QString serviceUrl( const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings )
38 {
39 QUrl href;
40 href.setUrl( QgsServerProjectUtils::wfsServiceUrl( project ? *project : *QgsProject::instance(), request, settings ) );
41
42 // Build default url
43 if ( href.isEmpty() )
44 {
45
46 static QSet<QString> sFilter
47 {
48 QStringLiteral( "REQUEST" ),
49 QStringLiteral( "VERSION" ),
50 QStringLiteral( "SERVICE" ),
51 };
52
53 href = request.originalUrl();
54 QUrlQuery q( href );
55
56 const auto constQueryItems = q.queryItems();
57 for ( const auto &param : constQueryItems )
58 {
59 if ( sFilter.contains( param.first.toUpper() ) )
60 q.removeAllQueryItems( param.first );
61 }
62
63 href.setQuery( q );
64 }
65
66 return href.toString();
67 }
68
69 QString layerTypeName( const QgsMapLayer *layer )
70 {
71 QString name = layer->name();
72 if ( !layer->shortName().isEmpty() )
73 name = layer->shortName();
74 name = name.replace( ' ', '_' ).replace( ':', '-' );
75 return name;
76 }
77
78 QgsVectorLayer *layerByTypeName( const QgsProject *project, const QString &typeName )
79 {
80 QStringList layerIds = QgsServerProjectUtils::wfsLayerIds( *project );
81 for ( const QString &layerId : std::as_const( layerIds ) )
82 {
83 QgsMapLayer *layer = project->mapLayer( layerId );
84 if ( !layer )
85 {
86 continue;
87 }
88 if ( layer->type() != QgsMapLayerType::VectorLayer )
89 {
90 continue;
91 }
92
93 if ( layerTypeName( layer ) == typeName )
94 {
95 return qobject_cast<QgsVectorLayer *>( layer );
96 }
97 }
98 return nullptr;
99 }
100
101 QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QgsProject *project )
102 {
103 // Get the server feature ids in filter element
104 QStringList collectedServerFids;
105 return parseFilterElement( typeName, filterElem, collectedServerFids, project );
106 }
107
108 QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QStringList &serverFids, const QgsProject *project, const QgsMapLayer *layer )
109 {
110 QgsFeatureRequest request;
111
112 QDomNodeList fidNodes = filterElem.elementsByTagName( QStringLiteral( "FeatureId" ) );
113 QDomNodeList goidNodes = filterElem.elementsByTagName( QStringLiteral( "GmlObjectId" ) );
114 if ( !fidNodes.isEmpty() )
115 {
116 // Get the server feature ids in filter element
117 QStringList collectedServerFids;
118 QDomElement fidElem;
119 for ( int f = 0; f < fidNodes.size(); f++ )
120 {
121 fidElem = fidNodes.at( f ).toElement();
122 if ( !fidElem.hasAttribute( QStringLiteral( "fid" ) ) )
123 {
124 throw QgsRequestNotWellFormedException( "FeatureId element without fid attribute" );
125 }
126
127 QString serverFid = fidElem.attribute( QStringLiteral( "fid" ) );
128 if ( serverFid.contains( QLatin1String( "." ) ) )
129 {
130 if ( serverFid.section( QStringLiteral( "." ), 0, 0 ) != typeName )
131 continue;
132 serverFid = serverFid.section( QStringLiteral( "." ), 1, 1 );
133 }
134 collectedServerFids << serverFid;
135 }
136 // No server feature ids found
137 if ( collectedServerFids.isEmpty() )
138 {
139 throw QgsRequestNotWellFormedException( QStringLiteral( "No FeatureId element correctly parse against typeName '%1'" ).arg( typeName ) );
140 }
141 // update server feature ids
142 serverFids.append( collectedServerFids );
144 return request;
145 }
146 else if ( !goidNodes.isEmpty() )
147 {
148 // Get the server feature ids in filter element
149 QStringList collectedServerFids;
150 QDomElement goidElem;
151 for ( int f = 0; f < goidNodes.size(); f++ )
152 {
153 goidElem = goidNodes.at( f ).toElement();
154 if ( !goidElem.hasAttribute( QStringLiteral( "id" ) ) && !goidElem.hasAttribute( QStringLiteral( "gml:id" ) ) )
155 {
156 throw QgsRequestNotWellFormedException( "GmlObjectId element without gml:id attribute" );
157 }
158
159 QString serverFid = goidElem.attribute( QStringLiteral( "id" ) );
160 if ( serverFid.isEmpty() )
161 serverFid = goidElem.attribute( QStringLiteral( "gml:id" ) );
162 if ( serverFid.contains( QLatin1String( "." ) ) )
163 {
164 if ( serverFid.section( QStringLiteral( "." ), 0, 0 ) != typeName )
165 continue;
166 serverFid = serverFid.section( QStringLiteral( "." ), 1, 1 );
167 }
168 collectedServerFids << serverFid;
169 }
170 // No server feature ids found
171 if ( collectedServerFids.isEmpty() )
172 {
173 throw QgsRequestNotWellFormedException( QStringLiteral( "No GmlObjectId element correctly parse against typeName '%1'" ).arg( typeName ) );
174 }
175 // update server feature ids
176 serverFids.append( collectedServerFids );
178 return request;
179 }
180 else if ( filterElem.firstChildElement().tagName() == QLatin1String( "BBOX" ) )
181 {
182 QDomElement bboxElem = filterElem.firstChildElement();
183 QDomElement childElem = bboxElem.firstChildElement();
184
185 while ( !childElem.isNull() )
186 {
187 if ( childElem.tagName() == QLatin1String( "Box" ) )
188 {
189 request.setFilterRect( QgsOgcUtils::rectangleFromGMLBox( childElem ) );
190 }
191 else if ( childElem.tagName() != QLatin1String( "PropertyName" ) )
192 {
193 QgsOgcUtils::Context ctx { layer, project ? project->transformContext() : QgsCoordinateTransformContext() };
194 QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem, ctx );
195 request.setFilterRect( geom.boundingBox() );
196 }
197 childElem = childElem.nextSiblingElement();
198 }
199
201 return request;
202 }
203 // Apply BBOX through filterRect even inside an And to use spatial index
204 else if ( filterElem.firstChildElement().tagName() == QLatin1String( "And" ) &&
205 !filterElem.firstChildElement().firstChildElement( QLatin1String( "BBOX" ) ).isNull() )
206 {
207 int nbChildElem = filterElem.firstChildElement().childNodes().size();
208
209 // Create a filter element to parse And child not BBOX
210 QDomElement childFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
211 if ( nbChildElem > 2 )
212 {
213 QDomElement childAndElement = filterElem.ownerDocument().createElement( QLatin1String( "And" ) );
214 childFilterElement.appendChild( childAndElement );
215 }
216
217 // Create a filter element to parse BBOX
218 QDomElement bboxFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
219
220 QDomElement childElem = filterElem.firstChildElement().firstChildElement();
221 while ( !childElem.isNull() )
222 {
223 // Update request based on BBOX
224 if ( childElem.tagName() == QLatin1String( "BBOX" ) )
225 {
226 // Clone BBOX
227 bboxFilterElement.appendChild( childElem.cloneNode( true ) );
228 }
229 else
230 {
231 // Clone And child
232 if ( nbChildElem > 2 )
233 {
234 childFilterElement.firstChildElement().appendChild( childElem.cloneNode( true ) );
235 }
236 else
237 {
238 childFilterElement.appendChild( childElem.cloneNode( true ) );
239 }
240 }
241 childElem = childElem.nextSiblingElement();
242 }
243
244 // Parse the filter element with the cloned BBOX
245 QStringList collectedServerFids;
246 QgsFeatureRequest bboxRequest = parseFilterElement( typeName, bboxFilterElement, collectedServerFids, project );
247
248 // Update request based on BBOX
249 if ( request.filterRect().isEmpty() )
250 {
251 request.setFilterRect( bboxRequest.filterRect() );
252 }
253 else
254 {
255 request.setFilterRect( request.filterRect().intersect( bboxRequest.filterRect() ) );
256 }
257
258 // Parse the filter element with the cloned And child
259 QgsFeatureRequest childRequest = parseFilterElement( typeName, childFilterElement, collectedServerFids, project );
260
261 // Update server feature ids
262 if ( !collectedServerFids.isEmpty() )
263 {
264 serverFids.append( collectedServerFids );
265 }
266
267 // Update expression
268 request.setFilterExpression( childRequest.filterExpression()->expression() );
269
271 return request;
272 }
273 else
274 {
275 QgsVectorLayer *layer = nullptr;
276 if ( project != nullptr )
277 {
278 layer = layerByTypeName( project, typeName );
279 }
280 std::shared_ptr<QgsExpression> filter( QgsOgcUtils::expressionFromOgcFilter( filterElem, layer ) );
281 if ( filter )
282 {
283 if ( filter->hasParserError() || !filter->parserErrorString().isEmpty() )
284 {
285 throw QgsRequestNotWellFormedException( filter->parserErrorString() );
286 }
287
288 if ( filter->needsGeometry() )
289 {
291 }
292 request.setFilterExpression( filter->expression() );
293 return request;
294 }
295 }
296 return request;
297 }
298
299} // namespace QgsWfs
300
301
Contains information about the context in which a coordinate transform is executed.
QString expression() const
Returns the original, unmodified expression string.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsRectangle filterRect() const
Returns the rectangle from which features will be taken.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsExpression * filterExpression() const
Returns the filter expression (if set).
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QString name
Definition: qgsmaplayer.h:76
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
static QgsRectangle rectangleFromGMLBox(const QDomNode &boxNode)
Read rectangle from GML2 Box.
static QgsGeometry geometryFromGML(const QString &xmlString, const QgsOgcUtils::Context &context=QgsOgcUtils::Context())
Static method that creates geometry from GML.
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:104
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:110
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:333
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
QUrl originalUrl() const
Returns the request url as seen by the web server, by default this is equal to the url seen by QGIS s...
Provides a way to retrieve settings by prioritizing according to environment variables,...
Represents a vector layer which manages a vector based data sets.
Exception thrown in case of malformed request.
@ VectorLayer
Vector layer.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
SERVER_EXPORT QString wfsServiceUrl(const QgsProject &project, const QgsServerRequest &request=QgsServerRequest(), const QgsServerSettings &settings=QgsServerSettings())
Returns the WFS service url.
WMS implementation.
Definition: qgswfs.cpp:36
QString layerTypeName(const QgsMapLayer *layer)
Returns typename from vector layer.
Definition: qgswfsutils.cpp:69
QString implementationVersion()
Returns the highest version supported by this implementation.
Definition: qgswfsutils.cpp:32
QgsVectorLayer * layerByTypeName(const QgsProject *project, const QString &typeName)
Retrieve a layer by typename.
Definition: qgswfsutils.cpp:78
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Service URL string.
Definition: qgswfsutils.cpp:37
QgsFeatureRequest parseFilterElement(const QString &typeName, QDomElement &filterElem, QgsProject *project)
Transform a Filter element to a feature request.
const QString & typeName
The Context struct stores the current layer and coordinate transform context.
Definition: qgsogcutils.h:60