QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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  const auto constQueryItems = q.queryItems();
60  for ( const auto &param : constQueryItems )
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( ' ', '_' ).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 : qgis::as_const( layerIds ) )
85  {
86  QgsMapLayer *layer = project->mapLayer( layerId );
87  if ( !layer )
88  {
89  continue;
90  }
91  if ( layer->type() != QgsMapLayerType::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, 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, const QgsMapLayer *layer )
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 ids in 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  QgsOgcUtils::Context ctx { layer, project ? project->transformContext() : QgsCoordinateTransformContext() };
197  QgsGeometry geom = QgsOgcUtils::geometryFromGML( childElem, ctx );
198  request.setFilterRect( geom.boundingBox() );
199  }
200  childElem = childElem.nextSiblingElement();
201  }
202 
204  return request;
205  }
206  // Apply BBOX through filterRect even inside an And to use spatial index
207  else if ( filterElem.firstChildElement().tagName() == QLatin1String( "And" ) &&
208  !filterElem.firstChildElement().firstChildElement( QLatin1String( "BBOX" ) ).isNull() )
209  {
210  int nbChildElem = filterElem.firstChildElement().childNodes().size();
211 
212  // Create a filter element to parse And child not BBOX
213  QDomElement childFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
214  if ( nbChildElem > 2 )
215  {
216  QDomElement childAndElement = filterElem.ownerDocument().createElement( QLatin1String( "And" ) );
217  childFilterElement.appendChild( childAndElement );
218  }
219 
220  // Create a filter element to parse BBOX
221  QDomElement bboxFilterElement = filterElem.ownerDocument().createElement( QLatin1String( "Filter" ) );
222 
223  QDomElement childElem = filterElem.firstChildElement().firstChildElement();
224  while ( !childElem.isNull() )
225  {
226  // Update request based on BBOX
227  if ( childElem.tagName() == QLatin1String( "BBOX" ) )
228  {
229  // Clone BBOX
230  bboxFilterElement.appendChild( childElem.cloneNode( true ) );
231  }
232  else
233  {
234  // Clone And child
235  if ( nbChildElem > 2 )
236  {
237  childFilterElement.firstChildElement().appendChild( childElem.cloneNode( true ) );
238  }
239  else
240  {
241  childFilterElement.appendChild( childElem.cloneNode( true ) );
242  }
243  }
244  childElem = childElem.nextSiblingElement();
245  }
246 
247  // Parse the filter element with the cloned BBOX
248  QStringList collectedServerFids;
249  QgsFeatureRequest bboxRequest = parseFilterElement( typeName, bboxFilterElement, collectedServerFids, project );
250 
251  // Update request based on BBOX
252  if ( request.filterRect().isEmpty() )
253  {
254  request.setFilterRect( bboxRequest.filterRect() );
255  }
256  else
257  {
258  request.setFilterRect( request.filterRect().intersect( bboxRequest.filterRect() ) );
259  }
260 
261  // Parse the filter element with the cloned And child
262  QgsFeatureRequest childRequest = parseFilterElement( typeName, childFilterElement, collectedServerFids, project );
263 
264  // Update server feature ids
265  if ( !collectedServerFids.isEmpty() )
266  {
267  serverFids.append( collectedServerFids );
268  }
269 
270  // Update expression
271  request.setFilterExpression( childRequest.filterExpression()->expression() );
272 
274  return request;
275  }
276  else
277  {
278  QgsVectorLayer *layer = nullptr;
279  if ( project != nullptr )
280  {
281  layer = layerByTypeName( project, typeName );
282  }
283  std::shared_ptr<QgsExpression> filter( QgsOgcUtils::expressionFromOgcFilter( filterElem, layer ) );
284  if ( filter )
285  {
286  if ( filter->hasParserError() || !filter->parserErrorString().isEmpty() )
287  {
288  throw QgsRequestNotWellFormedException( filter->parserErrorString() );
289  }
290 
291  if ( filter->needsGeometry() )
292  {
294  }
295  request.setFilterExpression( filter->expression() );
296  return request;
297  }
298  }
299  return request;
300  }
301 
302 } // namespace QgsWfs
303 
304 
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).
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.
const QgsRectangle & filterRect() const
Returns the rectangle from which features will be taken.
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:124
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Base class for all map layer types.
Definition: qgsmaplayer.h:85
QString name
Definition: qgsmaplayer.h:88
QgsMapLayerType type
Definition: qgsmaplayer.h:92
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:99
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:105
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:437
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:312
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...
Represents a vector layer which manages a vector based data sets.
Exception thrown in case of malformed request.
SERVER_EXPORT QString wfsServiceUrl(const QgsProject &project)
Returns the WFS service url defined in a QGIS project.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
WMS implementation.
Definition: qgswfs.cpp:36
QString layerTypeName(const QgsMapLayer *layer)
Returns typename from vector layer.
Definition: qgswfsutils.cpp:72
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:81
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project)
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:59