QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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, const 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 )
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 idsin 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  QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem );
196  request.setFilterRect( geom.boundingBox() );
197  }
198  childElem = childElem.nextSiblingElement();
199  }
200 
202  return request;
203  }
204  // Apply BBOX through filterRect even inside an And to use spatial index
205  else if ( filterElem.firstChildElement().tagName() == QLatin1String( "And" ) &&
206  !filterElem.firstChildElement().firstChildElement( QLatin1String( "BBOX" ) ).isNull() )
207  {
208  int nbChildElem = filterElem.firstChildElement().childNodes().size();
209 
210  // Create a filter element to parse And child not BBOX
211  QDomElement childFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
212  if ( nbChildElem > 2 )
213  {
214  QDomElement childAndElement = filterElem.ownerDocument().createElement( QLatin1String( "And" ) );
215  childFilterElement.appendChild( childAndElement );
216  }
217 
218  // Create a filter element to parse BBOX
219  QDomElement bboxFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
220 
221  QDomElement childElem = filterElem.firstChildElement().firstChildElement();
222  while ( !childElem.isNull() )
223  {
224  // Update request based on BBOX
225  if ( childElem.tagName() == QLatin1String( "BBOX" ) )
226  {
227  // Clone BBOX
228  bboxFilterElement.appendChild( childElem.cloneNode( true ) );
229  }
230  else
231  {
232  // Clone And child
233  if ( nbChildElem > 2 )
234  {
235  childFilterElement.firstChildElement().appendChild( childElem.cloneNode( true ) );
236  }
237  else
238  {
239  childFilterElement.appendChild( childElem.cloneNode( true ) );
240  }
241  }
242  childElem = childElem.nextSiblingElement();
243  }
244 
245  // Parse the filter element with the cloned BBOX
246  QStringList collectedServerFids;
247  QgsFeatureRequest bboxRequest = parseFilterElement( typeName, bboxFilterElement, collectedServerFids, project );
248 
249  // Update request based on BBOX
250  if ( request.filterRect().isEmpty() )
251  {
252  request.setFilterRect( bboxRequest.filterRect() );
253  }
254  else
255  {
256  request.setFilterRect( request.filterRect().intersect( bboxRequest.filterRect() ) );
257  }
258 
259  // Parse the filter element with the cloned And child
260  QgsFeatureRequest childRequest = parseFilterElement( typeName, childFilterElement, collectedServerFids, project );
261 
262  // Update server feature ids
263  if ( !collectedServerFids.isEmpty() )
264  {
265  serverFids.append( collectedServerFids );
266  }
267 
268  // Update expression
269  request.setFilterExpression( childRequest.filterExpression()->expression() );
270 
272  return request;
273  }
274  else
275  {
276  QgsVectorLayer *layer = nullptr;
277  if ( project != nullptr )
278  {
279  layer = layerByTypeName( project, typeName );
280  }
281  std::shared_ptr<QgsExpression> filter( QgsOgcUtils::expressionFromOgcFilter( filterElem, layer ) );
282  if ( filter )
283  {
284  if ( filter->hasParserError() )
285  {
286  throw QgsRequestNotWellFormedException( filter->parserErrorString() );
287  }
288 
289  if ( filter->needsGeometry() )
290  {
292  }
293  request.setFilterExpression( filter->expression() );
294  return request;
295  }
296  }
297  return request;
298  }
299 
300 } // namespace QgsWfs
301 
302 
Base class for all map layer types.
Definition: qgsmaplayer.h:79
QgsMapLayerType type() const
Returns the type of the layer.
Use exact geometry intersection (slower) instead of bounding boxes.
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
QgsVectorLayer * layerByTypeName(const QgsProject *project, const QString &typeName)
Retrieve a layer by typename.
Definition: qgswfsutils.cpp:80
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
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...
const QgsRectangle & filterRect() const
Returns the rectangle from which features will be taken.
QString layerTypeName(const QgsMapLayer *layer)
Returns typename from vector layer.
Definition: qgswfsutils.cpp:71
static QgsRectangle rectangleFromGMLBox(const QDomNode &boxNode)
Read rectangle from GML2 Box.
QgsExpression * filterExpression() const
Returns the filter expression if set.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project)
Service URL string.
Definition: qgswfsutils.cpp:37
Exception thrown in case of malformed request.
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:426
const QString & typeName
WMS implementation.
Definition: qgswfs.cpp:35
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:312
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
QgsFeatureRequest parseFilterElement(const QString &typeName, QDomElement &filterElem, const QgsProject *project)
Transform a Filter element to a feature request.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:89
QString implementationVersion()
Returns the highest version supported by this implementation.
Definition: qgswfsutils.cpp:32
QString expression() const
Returns the original, unmodified expression string.
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
static QgsGeometry geometryFromGML(const QString &xmlString)
Static method that creates geometry from GML.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QString name
Definition: qgsmaplayer.h:83
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
Represents a vector layer which manages a vector based data sets.
SERVER_EXPORT QString wfsServiceUrl(const QgsProject &project)
Returns the WFS service url defined in a QGIS project.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.