QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 "qgsconfigcache.h"
26 #include "qgsserverprojectutils.h"
27 #include "qgswfsparameters.h"
28 #include "qgsvectorlayer.h"
29 #include "qgsproject.h"
30 
31 namespace QgsWfs
32 {
34  {
35  return QStringLiteral( "1.1.0" );
36  }
37 
38  QString serviceUrl( const QgsServerRequest &request, const QgsProject *project )
39  {
40  QUrl href;
41  if ( project )
42  {
43  href.setUrl( QgsServerProjectUtils::wfsServiceUrl( *project ) );
44  }
45 
46  // Build default url
47  if ( href.isEmpty() )
48  {
49 
50  static QSet<QString> sFilter
51  {
52  QStringLiteral( "REQUEST" ),
53  QStringLiteral( "VERSION" ),
54  QStringLiteral( "SERVICE" ),
55  };
56 
57  href = request.originalUrl();
58  QUrlQuery q( href );
59 
60  for ( auto param : q.queryItems() )
61  {
62  if ( sFilter.contains( param.first.toUpper() ) )
63  q.removeAllQueryItems( param.first );
64  }
65 
66  href.setQuery( q );
67  }
68 
69  return href.toString();
70  }
71 
72  QString layerTypeName( const QgsMapLayer *layer )
73  {
74  QString name = layer->name();
75  if ( !layer->shortName().isEmpty() )
76  name = layer->shortName();
77  name = name.replace( ' ', '_' );
78  return name;
79  }
80 
81  QgsVectorLayer *layerByTypeName( const QgsProject *project, const QString &typeName )
82  {
83  QStringList layerIds = QgsServerProjectUtils::wfsLayerIds( *project );
84  for ( const QString &layerId : layerIds )
85  {
86  QgsMapLayer *layer = project->mapLayer( layerId );
87  if ( !layer )
88  {
89  continue;
90  }
91  if ( layer->type() != QgsMapLayer::LayerType::VectorLayer )
92  {
93  continue;
94  }
95 
96  if ( layerTypeName( layer ) == typeName )
97  {
98  return qobject_cast<QgsVectorLayer *>( layer );
99  }
100  }
101  return nullptr;
102  }
103 
104  QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, const QgsProject *project )
105  {
106  // Get the server feature ids in filter element
107  QStringList collectedServerFids;
108  return parseFilterElement( typeName, filterElem, collectedServerFids, project );
109  }
110 
111  QgsFeatureRequest parseFilterElement( const QString &typeName, QDomElement &filterElem, QStringList &serverFids, const QgsProject *project )
112  {
113  QgsFeatureRequest request;
114 
115  QDomNodeList fidNodes = filterElem.elementsByTagName( QStringLiteral( "FeatureId" ) );
116  QDomNodeList goidNodes = filterElem.elementsByTagName( QStringLiteral( "GmlObjectId" ) );
117  if ( !fidNodes.isEmpty() )
118  {
119  // Get the server feature ids in filter element
120  QStringList collectedServerFids;
121  QDomElement fidElem;
122  for ( int f = 0; f < fidNodes.size(); f++ )
123  {
124  fidElem = fidNodes.at( f ).toElement();
125  if ( !fidElem.hasAttribute( QStringLiteral( "fid" ) ) )
126  {
127  throw QgsRequestNotWellFormedException( "FeatureId element without fid attribute" );
128  }
129 
130  QString serverFid = fidElem.attribute( QStringLiteral( "fid" ) );
131  if ( serverFid.contains( QLatin1String( "." ) ) )
132  {
133  if ( serverFid.section( QStringLiteral( "." ), 0, 0 ) != typeName )
134  continue;
135  serverFid = serverFid.section( QStringLiteral( "." ), 1, 1 );
136  }
137  collectedServerFids << serverFid;
138  }
139  // No server feature ids found
140  if ( collectedServerFids.isEmpty() )
141  {
142  throw QgsRequestNotWellFormedException( QStringLiteral( "No FeatureId element correctly parse against typeName '%1'" ).arg( typeName ) );
143  }
144  // update server feature ids
145  serverFids.append( collectedServerFids );
147  return request;
148  }
149  else if ( !goidNodes.isEmpty() )
150  {
151  // Get the server feature idsin filter element
152  QStringList collectedServerFids;
153  QDomElement goidElem;
154  for ( int f = 0; f < goidNodes.size(); f++ )
155  {
156  goidElem = goidNodes.at( f ).toElement();
157  if ( !goidElem.hasAttribute( QStringLiteral( "id" ) ) && !goidElem.hasAttribute( QStringLiteral( "gml:id" ) ) )
158  {
159  throw QgsRequestNotWellFormedException( "GmlObjectId element without gml:id attribute" );
160  }
161 
162  QString serverFid = goidElem.attribute( QStringLiteral( "id" ) );
163  if ( serverFid.isEmpty() )
164  serverFid = goidElem.attribute( QStringLiteral( "gml:id" ) );
165  if ( serverFid.contains( QLatin1String( "." ) ) )
166  {
167  if ( serverFid.section( QStringLiteral( "." ), 0, 0 ) != typeName )
168  continue;
169  serverFid = serverFid.section( QStringLiteral( "." ), 1, 1 );
170  }
171  collectedServerFids << serverFid;
172  }
173  // No server feature ids found
174  if ( collectedServerFids.isEmpty() )
175  {
176  throw QgsRequestNotWellFormedException( QStringLiteral( "No GmlObjectId element correctly parse against typeName '%1'" ).arg( typeName ) );
177  }
178  // update server feature ids
179  serverFids.append( collectedServerFids );
181  return request;
182  }
183  else if ( filterElem.firstChildElement().tagName() == QLatin1String( "BBOX" ) )
184  {
185  QDomElement bboxElem = filterElem.firstChildElement();
186  QDomElement childElem = bboxElem.firstChildElement();
187 
188  while ( !childElem.isNull() )
189  {
190  if ( childElem.tagName() == QLatin1String( "Box" ) )
191  {
192  request.setFilterRect( QgsOgcUtils::rectangleFromGMLBox( childElem ) );
193  }
194  else if ( childElem.tagName() != QLatin1String( "PropertyName" ) )
195  {
196  QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem );
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() )
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 
Base class for all map layer types.
Definition: qgsmaplayer.h:63
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:425
QgsMapLayer::LayerType type() const
Returns the type of the layer.
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
Definition: qgsmaplayer.h:257
Use exact geometry intersection (slower) instead of bounding boxes.
const QgsRectangle & filterRect() const
Returns the rectangle from which features will be taken.
QgsVectorLayer * layerByTypeName(const QgsProject *project, const QString &typeName)
Retrieve a layer by typename.
Definition: qgswfsutils.cpp:81
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
QString layerTypeName(const QgsMapLayer *layer)
Returns typename from vector layer.
Definition: qgswfsutils.cpp:72
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...
static QgsRectangle rectangleFromGMLBox(const QDomNode &boxNode)
Read rectangle from GML2 Box.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project)
Service URL string.
Definition: qgswfsutils.cpp:38
Exception thrown in case of malformed request.
const QString & typeName
WMS implementation.
Definition: qgswfs.cpp:35
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.
Reads and writes project states.
Definition: qgsproject.h:89
QString implementationVersion()
Returns the highest version supported by this implementation.
Definition: qgswfsutils.cpp:33
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:311
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
static QgsGeometry geometryFromGML(const QString &xmlString)
Static method that creates geometry from GML.
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
QString expression() const
Returns the original, unmodified expression string.
QString name
Definition: qgsmaplayer.h:67
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.
QgsExpression * filterExpression() const
Returns the filter expression if set.