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