QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 "qgsvectorlayer.h"
27#include "qgsproject.h"
28
29namespace QgsWfs
30{
32 {
33 return QStringLiteral( "1.1.0" );
34 }
35
36 QString serviceUrl( const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings )
37 {
38 QUrl href;
39 href.setUrl( QgsServerProjectUtils::wfsServiceUrl( project ? *project : *QgsProject::instance(), request, settings ) );
40
41 // Build default url
42 if ( href.isEmpty() )
43 {
44
45 static QSet<QString> sFilter
46 {
47 QStringLiteral( "REQUEST" ),
48 QStringLiteral( "VERSION" ),
49 QStringLiteral( "SERVICE" ),
50 };
51
52 href = request.originalUrl();
53 QUrlQuery q( href );
54
55 const auto constQueryItems = q.queryItems();
56 for ( const auto &param : constQueryItems )
57 {
58 if ( sFilter.contains( param.first.toUpper() ) )
59 q.removeAllQueryItems( param.first );
60 }
61
62 href.setQuery( q );
63 }
64
65 return href.toString();
66 }
67
68 QString layerTypeName( const QgsMapLayer *layer )
69 {
70 QString name = layer->name();
71 if ( !layer->shortName().isEmpty() )
72 name = layer->shortName();
73 name = name.replace( ' ', '_' ).replace( ':', '-' );
74 return name;
75 }
76
77 QgsVectorLayer *layerByTypeName( const QgsProject *project, const QString &typeName )
78 {
79 QStringList layerIds = QgsServerProjectUtils::wfsLayerIds( *project );
80 for ( const QString &layerId : std::as_const( layerIds ) )
81 {
82 QgsMapLayer *layer = project->mapLayer( layerId );
83 if ( !layer )
84 {
85 continue;
86 }
87 if ( layer->type() != Qgis::LayerType::Vector )
88 {
89 continue;
90 }
91
92 if ( layerTypeName( layer ) == typeName )
93 {
94 return qobject_cast<QgsVectorLayer *>( layer );
95 }
96 }
97 return nullptr;
98 }
99
100 QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QgsProject *project )
101 {
102 // Get the server feature ids in filter element
103 QStringList collectedServerFids;
104 return parseFilterElement( typeName, filterElem, collectedServerFids, project );
105 }
106
107 QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QStringList &serverFids, const QgsProject *project, const QgsMapLayer *layer )
108 {
109 QgsFeatureRequest request;
110
111 QDomNodeList fidNodes = filterElem.elementsByTagName( QStringLiteral( "FeatureId" ) );
112 QDomNodeList goidNodes = filterElem.elementsByTagName( QStringLiteral( "GmlObjectId" ) );
113 if ( !fidNodes.isEmpty() )
114 {
115 // Get the server feature ids in filter element
116 QStringList collectedServerFids;
117 QDomElement fidElem;
118 for ( int f = 0; f < fidNodes.size(); f++ )
119 {
120 fidElem = fidNodes.at( f ).toElement();
121 if ( !fidElem.hasAttribute( QStringLiteral( "fid" ) ) )
122 {
123 throw QgsRequestNotWellFormedException( "FeatureId element without fid attribute" );
124 }
125
126 QString serverFid = fidElem.attribute( QStringLiteral( "fid" ) );
127 if ( serverFid.contains( QLatin1String( "." ) ) )
128 {
129 if ( serverFid.section( QStringLiteral( "." ), 0, 0 ) != typeName )
130 continue;
131 serverFid = serverFid.section( QStringLiteral( "." ), 1, 1 );
132 }
133 collectedServerFids << serverFid;
134 }
135 // No server feature ids found
136 if ( collectedServerFids.isEmpty() )
137 {
138 throw QgsRequestNotWellFormedException( QStringLiteral( "No FeatureId element correctly parse against typeName '%1'" ).arg( typeName ) );
139 }
140 // update server feature ids
141 serverFids.append( collectedServerFids );
143 return request;
144 }
145 else if ( !goidNodes.isEmpty() )
146 {
147 // Get the server feature ids in filter element
148 QStringList collectedServerFids;
149 QDomElement goidElem;
150 for ( int f = 0; f < goidNodes.size(); f++ )
151 {
152 goidElem = goidNodes.at( f ).toElement();
153 if ( !goidElem.hasAttribute( QStringLiteral( "id" ) ) && !goidElem.hasAttribute( QStringLiteral( "gml:id" ) ) )
154 {
155 throw QgsRequestNotWellFormedException( "GmlObjectId element without gml:id attribute" );
156 }
157
158 QString serverFid = goidElem.attribute( QStringLiteral( "id" ) );
159 if ( serverFid.isEmpty() )
160 serverFid = goidElem.attribute( QStringLiteral( "gml:id" ) );
161 if ( serverFid.contains( QLatin1String( "." ) ) )
162 {
163 if ( serverFid.section( QStringLiteral( "." ), 0, 0 ) != typeName )
164 continue;
165 serverFid = serverFid.section( QStringLiteral( "." ), 1, 1 );
166 }
167 collectedServerFids << serverFid;
168 }
169 // No server feature ids found
170 if ( collectedServerFids.isEmpty() )
171 {
172 throw QgsRequestNotWellFormedException( QStringLiteral( "No GmlObjectId element correctly parse against typeName '%1'" ).arg( typeName ) );
173 }
174 // update server feature ids
175 serverFids.append( collectedServerFids );
177 return request;
178 }
179 else if ( filterElem.firstChildElement().tagName() == QLatin1String( "BBOX" ) )
180 {
181 QDomElement bboxElem = filterElem.firstChildElement();
182 QDomElement childElem = bboxElem.firstChildElement();
183
184 while ( !childElem.isNull() )
185 {
186 if ( childElem.tagName() == QLatin1String( "Box" ) )
187 {
188 request.setFilterRect( QgsOgcUtils::rectangleFromGMLBox( childElem ) );
189 }
190 else if ( childElem.tagName() != QLatin1String( "PropertyName" ) )
191 {
192 QgsOgcUtils::Context ctx { layer, project ? project->transformContext() : QgsCoordinateTransformContext() };
193 QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem, ctx );
194 request.setFilterRect( geom.boundingBox() );
195 }
196 childElem = childElem.nextSiblingElement();
197 }
198
200 return request;
201 }
202 // Apply BBOX through filterRect even inside an And to use spatial index
203 else if ( filterElem.firstChildElement().tagName() == QLatin1String( "And" ) &&
204 !filterElem.firstChildElement().firstChildElement( QLatin1String( "BBOX" ) ).isNull() )
205 {
206 int nbChildElem = filterElem.firstChildElement().childNodes().size();
207
208 // Create a filter element to parse And child not BBOX
209 QDomElement childFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
210 if ( nbChildElem > 2 )
211 {
212 QDomElement childAndElement = filterElem.ownerDocument().createElement( QLatin1String( "And" ) );
213 childFilterElement.appendChild( childAndElement );
214 }
215
216 // Create a filter element to parse BBOX
217 QDomElement bboxFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
218
219 QDomElement childElem = filterElem.firstChildElement().firstChildElement();
220 while ( !childElem.isNull() )
221 {
222 // Update request based on BBOX
223 if ( childElem.tagName() == QLatin1String( "BBOX" ) )
224 {
225 // Clone BBOX
226 bboxFilterElement.appendChild( childElem.cloneNode( true ) );
227 }
228 else
229 {
230 // Clone And child
231 if ( nbChildElem > 2 )
232 {
233 childFilterElement.firstChildElement().appendChild( childElem.cloneNode( true ) );
234 }
235 else
236 {
237 childFilterElement.appendChild( childElem.cloneNode( true ) );
238 }
239 }
240 childElem = childElem.nextSiblingElement();
241 }
242
243 // Parse the filter element with the cloned BBOX
244 QStringList collectedServerFids;
245 QgsFeatureRequest bboxRequest = parseFilterElement( typeName, bboxFilterElement, collectedServerFids, project );
246
247 // Update request based on BBOX
248 if ( request.filterRect().isEmpty() )
249 {
250 request.setFilterRect( bboxRequest.filterRect() );
251 }
252 else
253 {
254 request.setFilterRect( request.filterRect().intersect( bboxRequest.filterRect() ) );
255 }
256
257 // Parse the filter element with the cloned And child
258 QgsFeatureRequest childRequest = parseFilterElement( typeName, childFilterElement, collectedServerFids, project );
259
260 // Update server feature ids
261 if ( !collectedServerFids.isEmpty() )
262 {
263 serverFids.append( collectedServerFids );
264 }
265
266 // Update expression
267 request.setFilterExpression( childRequest.filterExpression()->expression() );
268
270 return request;
271 }
272 else
273 {
274 QgsVectorLayer *layer = nullptr;
275 if ( project != nullptr )
276 {
277 layer = layerByTypeName( project, typeName );
278 }
279 std::shared_ptr<QgsExpression> filter( QgsOgcUtils::expressionFromOgcFilter( filterElem, layer ) );
280 if ( filter )
281 {
282 if ( filter->hasParserError() || !filter->parserErrorString().isEmpty() )
283 {
284 throw QgsRequestNotWellFormedException( filter->parserErrorString() );
285 }
286
287 if ( filter->needsGeometry() )
288 {
290 }
291 request.setFilterExpression( filter->expression() );
292 return request;
293 }
294 }
295 return request;
296 }
297
298} // namespace QgsWfs
299
300
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
@ NoFlags
No flags are set.
@ Vector
Vector layer.
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).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsRectangle filterRect() const
Returns the rectangle from which features will be taken.
QgsExpression * filterExpression() const
Returns the filter expression (if set).
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
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:162
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Base class for all map layer types.
Definition: qgsmaplayer.h:75
QString name
Definition: qgsmaplayer.h:78
Qgis::LayerType type
Definition: qgsmaplayer.h:82
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:107
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:113
bool isEmpty() const
Returns true if the rectangle has no area.
Definition: qgsrectangle.h:492
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:355
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.
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:68
QString implementationVersion()
Returns the highest version supported by this implementation.
Definition: qgswfsutils.cpp:31
QgsVectorLayer * layerByTypeName(const QgsProject *project, const QString &typeName)
Retrieve a layer by typename.
Definition: qgswfsutils.cpp:77
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Service URL string.
Definition: qgswfsutils.cpp:36
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:62