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