QGIS API Documentation 3.39.0-Master (bca3cdb6021)
Loading...
Searching...
No Matches
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->serverProperties()->shortName().isEmpty() )
72 name = layer->serverProperties()->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 )
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.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
Base class for all map layer types.
Definition qgsmaplayer.h:76
QString name
Definition qgsmaplayer.h:80
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the map layer.
Qgis::LayerType type
Definition qgsmaplayer.h:86
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.
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.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
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.
QString implementationVersion()
Returns the highest version supported by this implementation.
QgsVectorLayer * layerByTypeName(const QgsProject *project, const QString &typeName)
Retrieve a layer by typename.
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Service URL string.
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