QGIS API Documentation  3.37.0-Master (a5b4d9743e8)
qgswfsgetfeature.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgswfsgetfeature.cpp
3  -------------------------
4  begin : December 20 , 2016
5  copyright : (C) 2007 by Marco Hugentobler (original code)
6  (C) 2012 by RenĂ©-Luc D'Hont (original code)
7  (C) 2014 by Alessandro Pasotti (original code)
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 #include "qgswfsutils.h"
23 #include "qgsserverprojectutils.h"
24 #include "qgsserverfeatureid.h"
25 #include "qgsfields.h"
27 #include "qgsexpression.h"
28 #include "qgsgeometry.h"
29 #include "qgsmaplayer.h"
30 #include "qgsfeatureiterator.h"
32 #include "qgsvectorlayer.h"
33 #include "qgsfilterrestorer.h"
34 #include "qgsproject.h"
35 #include "qgsogcutils.h"
36 #include "qgsjsonutils.h"
38 #include "qgswkbtypes.h"
39 
40 #include "qgswfsgetfeature.h"
41 
42 #include <QRegularExpression>
43 
44 namespace QgsWfs
45 {
46 
47  namespace
48  {
49  struct createFeatureParams
50  {
51  int precision;
52 
54 
56 
57  const QString &typeName;
58 
59  bool withGeom;
60 
61  const QString &geometryName;
62 
64 
66 
67  const QString &srsName;
68 
70  };
71 
72  QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams &params, const QgsAttributeList &pkAttributes );
73 
74  QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc );
75 
76  QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup );
77 
78  QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes );
79 
80  QDomElement createFeatureGML3( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes );
81 
82  void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project,
83  QgsWfsParameters::Format format, int numberOfFeatures, const QStringList &typeNames, const QgsServerSettings *serverSettings );
84 
85  void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project,
87  QgsRectangle *rect, const QStringList &typeNames, const QgsServerSettings *settings );
88 
89  void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
90  const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes = QgsAttributeList() );
91 
92  void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format );
93 
94  QgsServerRequest::Parameters mRequestParameters;
95  QgsWfsParameters mWfsParameters;
96  /* GeoJSON Exporter */
97  QgsJsonExporter mJsonExporter;
98  }
99 
100  void writeGetFeature( QgsServerInterface *serverIface, const QgsProject *project,
101  const QString &version, const QgsServerRequest &request,
102  QgsServerResponse &response )
103  {
104  Q_UNUSED( version )
105 
106  mRequestParameters = request.parameters();
107  mWfsParameters = QgsWfsParameters( QUrlQuery( request.url() ) );
108  mWfsParameters.dump();
109  getFeatureRequest aRequest;
110 
111  QDomDocument doc;
112  QString errorMsg;
113 
114  if ( doc.setContent( request.data(), true, &errorMsg ) )
115  {
116  QDomElement docElem = doc.documentElement();
117  aRequest = parseGetFeatureRequestBody( docElem, project );
118  }
119  else
120  {
121  aRequest = parseGetFeatureParameters( project );
122  }
123 
124  // store typeName
125  QStringList typeNameList;
126 
127  // Request metadata
128  bool onlyOneLayer = ( aRequest.queries.size() == 1 );
129  QgsRectangle requestRect;
130  QgsCoordinateReferenceSystem requestCrs;
131  int requestPrecision = 6;
132  if ( !onlyOneLayer )
133  requestCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) );
134 
135  QList<getFeatureQuery>::iterator qIt = aRequest.queries.begin();
136  for ( ; qIt != aRequest.queries.end(); ++qIt )
137  {
138  typeNameList << ( *qIt ).typeName;
139  }
140 
141  // get layers and
142  // update the request metadata
143  QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
144  QMap<QString, QgsMapLayer *> mapLayerMap;
145  for ( int i = 0; i < wfsLayerIds.size(); ++i )
146  {
147  QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) );
148  if ( !layer )
149  {
150  continue;
151  }
152  if ( layer->type() != Qgis::LayerType::Vector )
153  {
154  continue;
155  }
156 
157  QString name = layerTypeName( layer );
158 
159  if ( typeNameList.contains( name ) )
160  {
161  // store layers
162  mapLayerMap[name] = layer;
163  // update request metadata
164  if ( onlyOneLayer )
165  {
166  requestRect = layer->extent();
167  requestCrs = layer->crs();
168  }
169  else
170  {
171  QgsCoordinateTransform transform( layer->crs(), requestCrs, project );
172  try
173  {
174  if ( requestRect.isEmpty() )
175  {
176  requestRect = transform.transform( layer->extent() );
177  }
178  else
179  {
180  requestRect.combineExtentWith( transform.transform( layer->extent() ) );
181  }
182  }
183  catch ( QgsException &cse )
184  {
185  Q_UNUSED( cse )
186  requestRect = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
187  }
188  }
189  }
190  }
191 
192  // check if all typename are valid
193  for ( const QString &typeName : typeNameList )
194  {
195  if ( !mapLayerMap.contains( typeName ) )
196  {
197  throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' could not be found" ).arg( typeName ) );
198  }
199  }
200 
201 #ifdef HAVE_SERVER_PYTHON_PLUGINS
202  QgsAccessControl *accessControl = serverIface->accessControls();
203  //scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope
204  //there's LOTS of potential exit paths here, so we avoid having to restore the filters manually
205  std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer() );
206 #else
207  ( void )serverIface;
208 #endif
209 
210  // features counters
211  long sentFeatures = 0;
212  long iteratedFeatures = 0;
213  // sent features
214  QgsFeature feature;
215  qIt = aRequest.queries.begin();
216  for ( ; qIt != aRequest.queries.end(); ++qIt )
217  {
218  getFeatureQuery &query = *qIt;
219  QString typeName = query.typeName;
220 
221  QgsMapLayer *layer = mapLayerMap[typeName];
222 #ifdef HAVE_SERVER_PYTHON_PLUGINS
223  if ( accessControl && !accessControl->layerReadPermission( layer ) )
224  {
225  throw QgsSecurityAccessException( QStringLiteral( "Feature access permission denied" ) );
226  }
227 #endif
228  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
229  if ( !vlayer )
230  {
231  throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' layer error" ).arg( typeName ) );
232  }
233 
234  //test provider
235  QgsVectorDataProvider *provider = vlayer->dataProvider();
236  if ( !provider )
237  {
238  throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' layer's provider error" ).arg( typeName ) );
239  }
240 #ifdef HAVE_SERVER_PYTHON_PLUGINS
241  if ( accessControl )
242  {
243  QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( accessControl, vlayer, filterRestorer->originalFilters() );
244  }
245 #endif
246  //is there alias info for this vector layer?
247  QMap< int, QString > layerAliasInfo;
248  QgsStringMap aliasMap = vlayer->attributeAliases();
249  QgsStringMap::const_iterator aliasIt = aliasMap.constBegin();
250  for ( ; aliasIt != aliasMap.constEnd(); ++aliasIt )
251  {
252  int attrIndex = vlayer->fields().lookupField( aliasIt.key() );
253  if ( attrIndex != -1 )
254  {
255  layerAliasInfo.insert( attrIndex, aliasIt.value() );
256  }
257  }
258 
259  // get propertyList from query
260  const QStringList propertyList = query.propertyList;
261 
262  //Using pending attributes and pending fields
263  QgsAttributeList attrIndexes = vlayer->attributeList();
264  const QgsFields fields = vlayer->fields();
265  bool withGeom = true;
266  if ( !propertyList.isEmpty() && propertyList.first() != QLatin1String( "*" ) )
267  {
268  withGeom = false;
269  QStringList::const_iterator plstIt;
270  QList<int> idxList;
271  // build corresponding propertyname
272  QList<QString> propertynames;
273  QList<QString> fieldnames;
274  for ( const QgsField &field : fields )
275  {
276  fieldnames.append( field.name() );
277  const thread_local QRegularExpression sCleanTagNameRegExp( QStringLiteral( "[^\\w\\.-_]" ), QRegularExpression::PatternOption::UseUnicodePropertiesOption );
278  propertynames.append( field.name().replace( ' ', '_' ).replace( sCleanTagNameRegExp, QString() ) );
279  }
280  QString fieldName;
281  for ( plstIt = propertyList.constBegin(); plstIt != propertyList.constEnd(); ++plstIt )
282  {
283  fieldName = *plstIt;
284  int fieldNameIdx = propertynames.indexOf( fieldName );
285  if ( fieldNameIdx == -1 )
286  {
287  fieldNameIdx = fieldnames.indexOf( fieldName );
288  }
289  if ( fieldNameIdx > -1 )
290  {
291  idxList.append( fieldNameIdx );
292  }
293  else if ( fieldName == QLatin1String( "geometry" ) )
294  {
295  withGeom = true;
296  }
297  }
298  if ( !idxList.isEmpty() )
299  {
300  attrIndexes = idxList;
301  }
302  }
303 
304  //excluded attributes for this layer
305  if ( !attrIndexes.isEmpty() )
306  {
307  for ( const QgsField &field : fields )
308  {
309  if ( field.configurationFlags().testFlag( Qgis::FieldConfigurationFlag::HideFromWfs ) )
310  {
311  int fieldNameIdx = fields.indexOf( field.name() );
312  if ( fieldNameIdx > -1 && attrIndexes.contains( fieldNameIdx ) )
313  {
314  attrIndexes.removeOne( fieldNameIdx );
315  }
316  }
317  }
318  }
319 
320  // update request
321  QgsFeatureRequest featureRequest = query.featureRequest;
322 
323  // expression context
324  QgsExpressionContext expressionContext;
325  expressionContext << QgsExpressionContextUtils::globalScope()
328  featureRequest.setExpressionContext( expressionContext );
329 
330  if ( !query.serverFids.isEmpty() )
331  {
332  QgsServerFeatureId::updateFeatureRequestFromServerFids( featureRequest, query.serverFids, provider );
333  }
334 
335  // geometry flags
336  if ( vlayer->wkbType() == Qgis::WkbType::NoGeometry )
337  featureRequest.setFlags( featureRequest.flags() | Qgis::FeatureRequestFlag::NoGeometry );
338  else
340 
341  // subset of attributes
342  featureRequest.setSubsetOfAttributes( attrIndexes );
343  // Access control expression could not be combined with feature ids filter
344  // This request will store the access control expression if the feature request
345  // filter type is feature ids
346  QgsFeatureRequest accessControlRequest;
347 #ifdef HAVE_SERVER_PYTHON_PLUGINS
348  if ( accessControl )
349  {
350  // Access control expression could not be combined with feature ids filter
352  {
353  // expression context for access control filter
354  QgsExpressionContext accessControlContext;
355  accessControlContext << QgsExpressionContextUtils::globalScope()
358  accessControlRequest.setExpressionContext( accessControlContext );
359  accessControl->filterFeatures( vlayer, accessControlRequest );
360  }
361  else
362  {
363  accessControl->filterFeatures( vlayer, featureRequest );
364  }
365 
366  QStringList attributes = QStringList();
367  for ( int idx : std::as_const( attrIndexes ) )
368  {
369  attributes.append( vlayer->fields().field( idx ).name() );
370  }
371  featureRequest.setSubsetOfAttributes(
372  accessControl->layerAttributes( vlayer, attributes ),
373  vlayer->fields() );
374  attrIndexes = featureRequest.subsetOfAttributes();
375  }
376 #endif
377 
378  // Force pkAttributes in subset of attributes for primary fid building
379  const QgsAttributeList pkAttributes = provider->pkAttributeIndexes();
380  if ( !pkAttributes.isEmpty() )
381  {
382  QgsAttributeList subsetOfAttrs = featureRequest.subsetOfAttributes();
383  for ( int idx : pkAttributes )
384  {
385  if ( !subsetOfAttrs.contains( idx ) )
386  {
387  subsetOfAttrs.prepend( idx );
388  }
389  }
390  if ( subsetOfAttrs.size() != featureRequest.subsetOfAttributes().size() )
391  {
392  featureRequest.setSubsetOfAttributes( subsetOfAttrs );
393  }
394  }
395 
396  if ( onlyOneLayer )
397  {
398  requestPrecision = QgsServerProjectUtils::wfsLayerPrecision( *project, vlayer->id() );
399  }
400 
401  if ( aRequest.maxFeatures > 0 )
402  {
403  featureRequest.setLimit( aRequest.maxFeatures + aRequest.startIndex - sentFeatures );
404  }
405  // specific layer precision
406  int layerPrecision = QgsServerProjectUtils::wfsLayerPrecision( *project, vlayer->id() );
407  // specific layer crs
408  QgsCoordinateReferenceSystem layerCrs = vlayer->crs();
409 
410  // Geometry name
411  QString geometryName = aRequest.geometryName;
412  if ( !withGeom )
413  {
414  geometryName = QLatin1String( "NONE" );
415  }
416  // outputCrs
418  if ( !query.srsName.isEmpty() )
419  {
421  }
422 
424 
425  if ( !featureRequest.filterRect().isEmpty() )
426  {
427  QgsCoordinateTransform transform( outputCrs, vlayer->crs(), project );
428  try
429  {
430  featureRequest.setFilterRect( transform.transform( featureRequest.filterRect() ) );
431  }
432  catch ( QgsException &cse )
433  {
434  Q_UNUSED( cse )
435  }
436  if ( onlyOneLayer )
437  {
438  requestRect = featureRequest.filterRect();
439  }
440  }
441 
442  // Iterate through features
443  QgsFeatureIterator fit = vlayer->getFeatures( featureRequest );
444 
445  if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
446  {
447  while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
448  {
449  if ( accessControlRequest.filterType() != Qgis::FeatureRequestFilterType::NoFilter && !accessControlRequest.acceptFeature( feature ) )
450  {
451  continue;
452  }
453  if ( iteratedFeatures >= aRequest.startIndex )
454  {
455  ++sentFeatures;
456  }
457  ++iteratedFeatures;
458  }
459  }
460  else
461  {
462 
463  // For WFS 1.1 we honor requested CRS and axis order
464  // Axis is not inverted if srsName starts with EPSG
465  // It needs to be an EPSG urn, e.g. urn:ogc:def:crs:EPSG::4326
466  // This follows geoserver convention
467  // See: https://docs.geoserver.org/stable/en/user/services/wfs/axis_order.html
468  // if the crs is defined in the parameters, use it
469  // otherwise:
470  // - geojson uses 'EPSG:4326' by default
471  // - other formats use the default CRS (DefaultSRS, which is the layer's CRS)
472  const QString requestSrsName = request.serverParameters().value( QStringLiteral( "SRSNAME" ) );
473  const QString srsName
474  {
475  !requestSrsName.isEmpty() ? requestSrsName :
476  ( aRequest.outputFormat == QgsWfsParameters::Format::GeoJSON ? QStringLiteral( "EPSG:4326" ) : outputCrs.authid() )
477  };
478  const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
480  ! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
481 
482  const createFeatureParams cfp = { layerPrecision,
483  layerCrs,
484  attrIndexes,
485  typeName,
486  withGeom,
487  geometryName,
488  outputCrs,
490  srsName,
491  invertAxis
492  };
493  while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
494  {
495  if ( accessControlRequest.filterType() != Qgis::FeatureRequestFilterType::NoFilter && !accessControlRequest.acceptFeature( feature ) )
496  {
497  continue;
498  }
499  if ( iteratedFeatures == aRequest.startIndex )
500  startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList, serverIface->serverSettings() );
501 
502  if ( iteratedFeatures >= aRequest.startIndex )
503  {
504  setGetFeature( response, aRequest.outputFormat, feature, sentFeatures, cfp, project, provider->pkAttributeIndexes() );
505  ++sentFeatures;
506  }
507  ++iteratedFeatures;
508  }
509  }
510  }
511 
512 #ifdef HAVE_SERVER_PYTHON_PLUGINS
513  //force restoration of original layer filters
514  filterRestorer.reset();
515 #endif
516 
517  if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
518  {
519  hitGetFeature( request, response, project, aRequest.outputFormat, sentFeatures, typeNameList, serverIface->serverSettings() );
520  }
521  else
522  {
523  // End of GetFeature
524  if ( iteratedFeatures <= aRequest.startIndex )
525  startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList, serverIface->serverSettings() );
526  endGetFeature( response, aRequest.outputFormat );
527  }
528 
529  }
530 
532  {
533  getFeatureRequest request;
534  request.maxFeatures = mWfsParameters.maxFeaturesAsInt();
535  request.startIndex = mWfsParameters.startIndexAsInt();
536  request.outputFormat = mWfsParameters.outputFormat();
537 
538  // Verifying parameters mutually exclusive
539  QStringList fidList = mWfsParameters.featureIds();
540  bool paramContainsFeatureIds = !fidList.isEmpty();
541  QStringList filterList = mWfsParameters.filters();
542  bool paramContainsFilters = !filterList.isEmpty();
543  QString bbox = mWfsParameters.bbox();
544  bool paramContainsBbox = !bbox.isEmpty();
545  if ( ( paramContainsFeatureIds
546  && ( paramContainsFilters || paramContainsBbox ) )
547  || ( paramContainsFilters
548  && ( paramContainsFeatureIds || paramContainsBbox ) )
549  || ( paramContainsBbox
550  && ( paramContainsFeatureIds || paramContainsFilters ) )
551  )
552  {
553  throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID FILTER and BBOX parameters are mutually exclusive" ) );
554  }
555 
556  // Get and split PROPERTYNAME parameter
557  QStringList propertyNameList = mWfsParameters.propertyNames();
558 
559  // Manage extra parameter GeometryName
560  request.geometryName = mWfsParameters.geometryNameAsString().toUpper();
561 
562  QStringList typeNameList;
563  // parse FEATUREID
564  if ( paramContainsFeatureIds )
565  {
566  // Verifying the 1:1 mapping between FEATUREID and PROPERTYNAME
567  if ( !propertyNameList.isEmpty() && propertyNameList.size() != fidList.size() )
568  {
569  throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a FEATUREID and the PROPERTYNAME list" ) );
570  }
571  if ( propertyNameList.isEmpty() )
572  {
573  for ( int i = 0; i < fidList.size(); ++i )
574  {
575  propertyNameList << QStringLiteral( "*" );
576  }
577  }
578 
579  QMap<QString, QStringList> fidsMap;
580 
581  QStringList::const_iterator fidIt = fidList.constBegin();
582  QStringList::const_iterator propertyNameIt = propertyNameList.constBegin();
583  for ( ; fidIt != fidList.constEnd(); ++fidIt )
584  {
585  // Get FeatureID
586  QString fid = *fidIt;
587  fid = fid.trimmed();
588  // Get PropertyName for this FeatureID
589  QString propertyName;
590  if ( propertyNameIt != propertyNameList.constEnd() )
591  {
592  propertyName = *propertyNameIt;
593  }
594  // testing typename in the WFS featureID
595  if ( !fid.contains( '.' ) )
596  {
597  throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID has to have TYPENAME in the values" ) );
598  }
599 
600  QString typeName = fid.section( '.', 0, 0 );
601  fid = fid.section( '.', 1, 1 );
602  if ( !typeNameList.contains( typeName ) )
603  {
604  typeNameList << typeName;
605  }
606 
607  // each Feature requested by FEATUREID can have each own property list
608  // use colon that is replaced in typenames because typenames can be used
609  // as XML tag name
610  const QString key = QStringLiteral( "%1:%2" ).arg( typeName, propertyName );
611  QStringList fids;
612  if ( fidsMap.contains( key ) )
613  {
614  fids = fidsMap.value( key );
615  }
616  fids.append( fid );
617  fidsMap.insert( key, fids );
618 
619  if ( propertyNameIt != propertyNameList.constEnd() )
620  {
621  ++propertyNameIt;
622  }
623  }
624 
625  QMap<QString, QStringList>::const_iterator fidsMapIt = fidsMap.constBegin();
626  while ( fidsMapIt != fidsMap.constEnd() )
627  {
628  QString key = fidsMapIt.key();
629 
630  //Extract TypeName and PropertyName from key
631  // separated by colon
632  const QString typeName = key.section( ':', 0, 0 );
633  const QString propertyName = key.section( ':', 1, 1 );
634 
635  getFeatureQuery query;
636  query.typeName = typeName;
637  query.srsName = mWfsParameters.srsName();
638 
639  // Parse PropertyName
640  if ( propertyName != QLatin1String( "*" ) )
641  {
642  QStringList propertyList;
643 
644  const QStringList attrList = propertyName.split( ',' );
645  QStringList::const_iterator alstIt;
646  for ( alstIt = attrList.constBegin(); alstIt != attrList.constEnd(); ++alstIt )
647  {
648  QString fieldName = *alstIt;
649  fieldName = fieldName.trimmed();
650  if ( fieldName.contains( ':' ) )
651  {
652  fieldName = fieldName.section( ':', 1, 1 );
653  }
654  if ( fieldName.contains( '/' ) )
655  {
656  if ( fieldName.section( '/', 0, 0 ) != typeName )
657  {
658  throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
659  }
660  fieldName = fieldName.section( '/', 1, 1 );
661  }
662  propertyList.append( fieldName );
663  }
664  query.propertyList = propertyList;
665  }
666 
667  query.serverFids = fidsMapIt.value();
668  QgsFeatureRequest featureRequest;
669 
670  query.featureRequest = featureRequest;
671  request.queries.append( query );
672  ++fidsMapIt;
673  }
674  return request;
675  }
676 
677  if ( !mRequestParameters.contains( QStringLiteral( "TYPENAME" ) ) )
678  {
679  throw QgsRequestNotWellFormedException( QStringLiteral( "TYPENAME is mandatory except if FEATUREID is used" ) );
680  }
681 
682  typeNameList = mWfsParameters.typeNames();
683  // Verifying the 1:1 mapping between TYPENAME and PROPERTYNAME
684  if ( !propertyNameList.isEmpty() && typeNameList.size() != propertyNameList.size() )
685  {
686  throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the PROPERTYNAME list" ) );
687  }
688  if ( propertyNameList.isEmpty() )
689  {
690  for ( int i = 0; i < typeNameList.size(); ++i )
691  {
692  propertyNameList << QStringLiteral( "*" );
693  }
694  }
695 
696  // Create queries based on TypeName and propertyName
697  QStringList::const_iterator typeNameIt = typeNameList.constBegin();
698  QStringList::const_iterator propertyNameIt = propertyNameList.constBegin();
699  for ( ; typeNameIt != typeNameList.constEnd(); ++typeNameIt )
700  {
701  QString typeName = *typeNameIt;
702  typeName = typeName.trimmed();
703  // Get PropertyName for this typeName
704  QString propertyName;
705  if ( propertyNameIt != propertyNameList.constEnd() )
706  {
707  propertyName = *propertyNameIt;
708  }
709 
710  getFeatureQuery query;
711  query.typeName = typeName;
712  query.srsName = mWfsParameters.srsName();
713 
714  // Parse PropertyName
715  if ( propertyName != QLatin1String( "*" ) )
716  {
717  QStringList propertyList;
718 
719  const QStringList attrList = propertyName.split( ',' );
720  QStringList::const_iterator alstIt;
721  for ( alstIt = attrList.constBegin(); alstIt != attrList.constEnd(); ++alstIt )
722  {
723  QString fieldName = *alstIt;
724  fieldName = fieldName.trimmed();
725  if ( fieldName.contains( ':' ) )
726  {
727  fieldName = fieldName.section( ':', 1, 1 );
728  }
729  if ( fieldName.contains( '/' ) )
730  {
731  if ( fieldName.section( '/', 0, 0 ) != typeName )
732  {
733  throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
734  }
735  fieldName = fieldName.section( '/', 1, 1 );
736  }
737  propertyList.append( fieldName );
738  }
739  query.propertyList = propertyList;
740  }
741 
742  request.queries.append( query );
743 
744  if ( propertyNameIt != propertyNameList.constEnd() )
745  {
746  ++propertyNameIt;
747  }
748  }
749 
750  // Manage extra parameter exp_filter
751  QStringList expFilterList = mWfsParameters.expFilters();
752  if ( !expFilterList.isEmpty() )
753  {
754  // Verifying the 1:1 mapping between TYPENAME and EXP_FILTER but without exception
755  if ( request.queries.size() == expFilterList.size() )
756  {
757  // set feature request filter expression based on filter element
758  QList<getFeatureQuery>::iterator qIt = request.queries.begin();
759  QStringList::const_iterator expFilterIt = expFilterList.constBegin();
760  for ( ; qIt != request.queries.end(); ++qIt )
761  {
762  getFeatureQuery &query = *qIt;
763  // Get Filter for this typeName
764  const QString expFilter = *expFilterIt++;
765  std::shared_ptr<QgsExpression> filter( new QgsExpression( expFilter ) );
766  if ( filter )
767  {
768  if ( filter->hasParserError() )
769  {
770  throw QgsRequestNotWellFormedException( QStringLiteral( "The EXP_FILTER expression has errors: %1" ).arg( filter->parserErrorString() ) );
771  }
772  if ( filter->needsGeometry() )
773  {
775  }
776  query.featureRequest.setFilterExpression( filter->expression() );
777  }
778  }
779  }
780  else
781  {
782  QgsMessageLog::logMessage( "There has to be a 1:1 mapping between each element in a TYPENAME and the EXP_FILTER list" );
783  }
784  }
785 
786  if ( paramContainsBbox )
787  {
788 
789  // get bbox extent
790  QgsRectangle extent = mWfsParameters.bboxAsRectangle();
791 
792  QString extentSrsName { mWfsParameters.srsName() };
793 
794  // handle WFS 1.1.0 optional CRS
795  if ( mWfsParameters.bbox().split( ',' ).size() == 5 && ! mWfsParameters.srsName().isEmpty() )
796  {
797  QString crs( mWfsParameters.bbox().split( ',' )[4] );
798  if ( crs != mWfsParameters.srsName() )
799  {
800  extentSrsName = crs;
801  QgsCoordinateReferenceSystem sourceCrs( crs );
802  QgsCoordinateReferenceSystem destinationCrs( mWfsParameters.srsName() );
803  if ( sourceCrs.isValid() && destinationCrs.isValid( ) )
804  {
805  QgsGeometry extentGeom = QgsGeometry::fromRect( extent );
806  QgsCoordinateTransform transform;
807  transform.setSourceCrs( sourceCrs );
808  transform.setDestinationCrs( destinationCrs );
809  try
810  {
811  if ( extentGeom.transform( transform ) == Qgis::GeometryOperationResult::Success )
812  {
813  extent = QgsRectangle( extentGeom.boundingBox() );
814  }
815  }
816  catch ( QgsException &cse )
817  {
818  Q_UNUSED( cse )
819  }
820  }
821  }
822  }
823 
824  // Follow GeoServer conventions and handle axis order
825  // See: https://docs.geoserver.org/latest/en/user/services/wfs/axis_order.html#wfs-basics-axis
827  extentCrs.createFromUserInput( extentSrsName );
828  if ( extentCrs.isValid() && extentCrs.hasAxisInverted() && ! extentSrsName.startsWith( QLatin1String( "EPSG:" ) ) )
829  {
830  QgsGeometry geom { QgsGeometry::fromRect( extent ) };
831  geom.get()->swapXy();
832  extent = geom.boundingBox();
833  }
834 
835  // set feature request filter rectangle
836  QList<getFeatureQuery>::iterator qIt = request.queries.begin();
837  for ( ; qIt != request.queries.end(); ++qIt )
838  {
839  getFeatureQuery &query = *qIt;
841  }
842  return request;
843  }
844  else if ( paramContainsFilters )
845  {
846  // Verifying the 1:1 mapping between TYPENAME and FILTER
847  if ( request.queries.size() != filterList.size() )
848  {
849  throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the FILTER list" ) );
850  }
851 
852  // set feature request filter expression based on filter element
853  QList<getFeatureQuery>::iterator qIt = request.queries.begin();
854  QStringList::const_iterator filterIt = filterList.constBegin();
855  for ( ; qIt != request.queries.end(); ++qIt )
856  {
857  getFeatureQuery &query = *qIt;
858  // Get Filter for this typeName
859  QDomDocument filter;
860  if ( filterIt != filterList.constEnd() )
861  {
862  QString errorMsg;
863  if ( !filter.setContent( *filterIt, true, &errorMsg ) )
864  {
865  throw QgsRequestNotWellFormedException( QStringLiteral( "error message: %1. The XML string was: %2" ).arg( errorMsg, *filterIt ) );
866  }
867  }
868 
869  QDomElement filterElem = filter.firstChildElement();
870  QStringList serverFids;
871  query.featureRequest = parseFilterElement( query.typeName, filterElem, serverFids, project );
872  query.serverFids = serverFids;
873 
874  if ( filterIt != filterList.constEnd() )
875  {
876  ++filterIt;
877  }
878  }
879  return request;
880  }
881 
882  QStringList sortByList = mWfsParameters.sortBy();
883  if ( !sortByList.isEmpty() && request.queries.size() == sortByList.size() )
884  {
885  // add order by to feature request
886  QList<getFeatureQuery>::iterator qIt = request.queries.begin();
887  QStringList::const_iterator sortByIt = sortByList.constBegin();
888  for ( ; qIt != request.queries.end(); ++qIt )
889  {
890  getFeatureQuery &query = *qIt;
891  // Get sortBy for this typeName
892  QString sortBy;
893  if ( sortByIt != sortByList.constEnd() )
894  {
895  sortBy = *sortByIt;
896  }
897  for ( const QString &attribute : sortBy.split( ',' ) )
898  {
899  if ( attribute.endsWith( QLatin1String( " D" ) ) || attribute.endsWith( QLatin1String( "+D" ) ) )
900  {
901  query.featureRequest.addOrderBy( attribute.left( attribute.size() - 2 ), false );
902  }
903  else if ( attribute.endsWith( QLatin1String( " DESC" ) ) || attribute.endsWith( QLatin1String( "+DESC" ) ) )
904  {
905  query.featureRequest.addOrderBy( attribute.left( attribute.size() - 5 ), false );
906  }
907  else if ( attribute.endsWith( QLatin1String( " A" ) ) || attribute.endsWith( QLatin1String( "+A" ) ) )
908  {
909  query.featureRequest.addOrderBy( attribute.left( attribute.size() - 2 ) );
910  }
911  else if ( attribute.endsWith( QLatin1String( " ASC" ) ) || attribute.endsWith( QLatin1String( "+ASC" ) ) )
912  {
913  query.featureRequest.addOrderBy( attribute.left( attribute.size() - 4 ) );
914  }
915  else
916  {
917  query.featureRequest.addOrderBy( attribute );
918  }
919  }
920  }
921  }
922 
923  return request;
924  }
925 
926  getFeatureRequest parseGetFeatureRequestBody( QDomElement &docElem, const QgsProject *project )
927  {
928  getFeatureRequest request;
929  request.maxFeatures = mWfsParameters.maxFeaturesAsInt();
930  request.startIndex = mWfsParameters.startIndexAsInt();
931  request.outputFormat = mWfsParameters.outputFormat();
932 
933  QDomNodeList queryNodes = docElem.elementsByTagName( QStringLiteral( "Query" ) );
934  QDomElement queryElem;
935  for ( int i = 0; i < queryNodes.size(); i++ )
936  {
937  queryElem = queryNodes.at( i ).toElement();
938  getFeatureQuery query = parseQueryElement( queryElem, project );
939  request.queries.append( query );
940  }
941  return request;
942  }
943 
944  void parseSortByElement( QDomElement &sortByElem, QgsFeatureRequest &featureRequest, const QString &typeName )
945  {
946  QDomNodeList sortByNodes = sortByElem.childNodes();
947  if ( sortByNodes.size() )
948  {
949  for ( int i = 0; i < sortByNodes.size(); i++ )
950  {
951  QDomElement sortPropElem = sortByNodes.at( i ).toElement();
952  QDomNodeList sortPropChildNodes = sortPropElem.childNodes();
953  if ( sortPropChildNodes.size() )
954  {
955  QString fieldName;
956  bool ascending = true;
957  for ( int j = 0; j < sortPropChildNodes.size(); j++ )
958  {
959  QDomElement sortPropChildElem = sortPropChildNodes.at( j ).toElement();
960  if ( sortPropChildElem.tagName() == QLatin1String( "PropertyName" ) )
961  {
962  fieldName = sortPropChildElem.text().trimmed();
963  }
964  else if ( sortPropChildElem.tagName() == QLatin1String( "SortOrder" ) )
965  {
966  QString sortOrder = sortPropChildElem.text().trimmed().toUpper();
967  if ( sortOrder == QLatin1String( "DESC" ) || sortOrder == QLatin1String( "D" ) )
968  ascending = false;
969  }
970  }
971  // clean fieldName
972  if ( fieldName.contains( ':' ) )
973  {
974  fieldName = fieldName.section( ':', 1, 1 );
975  }
976  if ( fieldName.contains( '/' ) )
977  {
978  if ( fieldName.section( '/', 0, 0 ) != typeName )
979  {
980  throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
981  }
982  fieldName = fieldName.section( '/', 1, 1 );
983  }
984  // addOrderBy
985  if ( !fieldName.isEmpty() )
986  featureRequest.addOrderBy( fieldName, ascending );
987  }
988  }
989  }
990  }
991 
992  getFeatureQuery parseQueryElement( QDomElement &queryElem, const QgsProject *project )
993  {
994  QString typeName = queryElem.attribute( QStringLiteral( "typeName" ), QString() );
995  if ( typeName.contains( ':' ) )
996  {
997  typeName = typeName.section( ':', 1, 1 );
998  }
999 
1000  QgsFeatureRequest featureRequest;
1001  QStringList serverFids;
1002  QStringList propertyList;
1003  QDomNodeList queryChildNodes = queryElem.childNodes();
1004  if ( queryChildNodes.size() )
1005  {
1006  QDomElement sortByElem;
1007  for ( int q = 0; q < queryChildNodes.size(); q++ )
1008  {
1009  QDomElement queryChildElem = queryChildNodes.at( q ).toElement();
1010  if ( queryChildElem.tagName() == QLatin1String( "PropertyName" ) )
1011  {
1012  QString fieldName = queryChildElem.text().trimmed();
1013  if ( fieldName.contains( ':' ) )
1014  {
1015  fieldName = fieldName.section( ':', 1, 1 );
1016  }
1017  if ( fieldName.contains( '/' ) )
1018  {
1019  if ( fieldName.section( '/', 0, 0 ) != typeName )
1020  {
1021  throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
1022  }
1023  fieldName = fieldName.section( '/', 1, 1 );
1024  }
1025  propertyList.append( fieldName );
1026  }
1027  else if ( queryChildElem.tagName() == QLatin1String( "Filter" ) )
1028  {
1029  featureRequest = parseFilterElement( typeName, queryChildElem, serverFids, project );
1030  }
1031  else if ( queryChildElem.tagName() == QLatin1String( "SortBy" ) )
1032  {
1033  sortByElem = queryChildElem;
1034  }
1035  }
1036  parseSortByElement( sortByElem, featureRequest, typeName );
1037  }
1038 
1039  // srsName attribute
1040  QString srsName = queryElem.attribute( QStringLiteral( "srsName" ), QString() );
1041 
1042  getFeatureQuery query;
1043  query.typeName = typeName;
1044  query.srsName = srsName;
1045  query.featureRequest = featureRequest;
1046  query.serverFids = serverFids;
1047  query.propertyList = propertyList;
1048  return query;
1049  }
1050 
1051  namespace
1052  {
1053  static QSet< QString > sParamFilter
1054  {
1055  QStringLiteral( "REQUEST" ),
1056  QStringLiteral( "FORMAT" ),
1057  QStringLiteral( "OUTPUTFORMAT" ),
1058  QStringLiteral( "BBOX" ),
1059  QStringLiteral( "FEATUREID" ),
1060  QStringLiteral( "TYPENAME" ),
1061  QStringLiteral( "FILTER" ),
1062  QStringLiteral( "EXP_FILTER" ),
1063  QStringLiteral( "MAXFEATURES" ),
1064  QStringLiteral( "STARTINDEX" ),
1065  QStringLiteral( "PROPERTYNAME" ),
1066  QStringLiteral( "_DC" )
1067  };
1068 
1069 
1070  void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
1071  int numberOfFeatures, const QStringList &typeNames, const QgsServerSettings *settings )
1072  {
1073  QDateTime now = QDateTime::currentDateTime();
1074  QString fcString;
1075 
1076  if ( format == QgsWfsParameters::Format::GeoJSON )
1077  {
1078  response.setHeader( "Content-Type", "application/vnd.geo+json; charset=utf-8" );
1079  fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
1080  fcString += QStringLiteral( " \"timeStamp\": \"%1\",\n" ).arg( now.toString( Qt::ISODate ) );
1081  fcString += QStringLiteral( " \"numberOfFeatures\": %1\n" ).arg( QString::number( numberOfFeatures ) );
1082  fcString += QLatin1Char( '}' );
1083  }
1084  else
1085  {
1086  if ( format == QgsWfsParameters::Format::GML2 )
1087  response.setHeader( "Content-Type", "text/xml; subtype=gml/2.1.2; charset=utf-8" );
1088  else
1089  response.setHeader( "Content-Type", "text/xml; subtype=gml/3.1.1; charset=utf-8" );
1090 
1091  //Prepare url
1092  QString hrefString = serviceUrl( request, project, *settings );
1093 
1094  QUrl mapUrl( hrefString );
1095 
1096  QUrlQuery query( mapUrl );
1097  query.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
1098  //Set version
1099  if ( mWfsParameters.version().isEmpty() )
1100  query.addQueryItem( QStringLiteral( "VERSION" ), implementationVersion() );
1101  else if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1102  query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.1.0" ) );
1103  else
1104  query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
1105 
1106  const auto constItems { query.queryItems() };
1107  for ( const auto &param : std::as_const( constItems ) )
1108  {
1109  if ( sParamFilter.contains( param.first.toUpper() ) )
1110  query.removeAllQueryItems( param.first );
1111  }
1112 
1113  query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
1114  query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
1115  if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1116  {
1117  if ( format == QgsWfsParameters::Format::GML2 )
1118  query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
1119  else
1120  query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
1121  }
1122  else
1123  query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );
1124 
1125  mapUrl.setQuery( query );
1126 
1127  hrefString = mapUrl.toString();
1128 
1129  QString wfsSchema;
1130  if ( mWfsParameters.version().isEmpty() || mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1131  wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" );
1132  else
1133  wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
1134 
1135  //wfs:FeatureCollection valid
1136  fcString = QStringLiteral( "<wfs:FeatureCollection" );
1137  fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
1138  fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
1139  fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
1140  fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
1141  fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
1142  fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
1143  fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
1144  fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " " + wfsSchema + " " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
1145  fcString += "\n timeStamp=\"" + now.toString( Qt::ISODate ) + "\"";
1146  fcString += "\n numberOfFeatures=\"" + QString::number( numberOfFeatures ) + "\"";
1147  fcString += QLatin1String( ">\n" );
1148  fcString += QLatin1String( "</wfs:FeatureCollection>" );
1149  }
1150 
1151  response.write( fcString.toUtf8() );
1152  response.flush();
1153  }
1154 
1155  void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
1156  int prec, QgsCoordinateReferenceSystem &crs, QgsRectangle *rect, const QStringList &typeNames, const QgsServerSettings *settings )
1157  {
1158  QString fcString;
1159 
1160  std::unique_ptr< QgsRectangle > transformedRect;
1161 
1162  if ( format == QgsWfsParameters::Format::GeoJSON )
1163  {
1164  response.setHeader( "Content-Type", "application/vnd.geo+json; charset=utf-8" );
1165 
1166  if ( crs.isValid() && !rect->isEmpty() )
1167  {
1168  QgsGeometry exportGeom = QgsGeometry::fromRect( *rect );
1169  QgsCoordinateTransform transform;
1170  transform.setSourceCrs( crs );
1171  transform.setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
1172  try
1173  {
1174  if ( exportGeom.transform( transform ) == Qgis::GeometryOperationResult::Success )
1175  {
1176  transformedRect.reset( new QgsRectangle( exportGeom.boundingBox() ) );
1177  rect = transformedRect.get();
1178  }
1179  }
1180  catch ( QgsException &cse )
1181  {
1182  Q_UNUSED( cse )
1183  }
1184  }
1185  // EPSG:4326 max extent is -180, -90, 180, 90
1186  rect = new QgsRectangle( rect->intersect( QgsRectangle( -180.0, -90.0, 180.0, 90.0 ) ) );
1187 
1188  fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
1189  fcString += " \"bbox\": [ " + qgsDoubleToString( rect->xMinimum(), prec ) + ", " + qgsDoubleToString( rect->yMinimum(), prec ) + ", " + qgsDoubleToString( rect->xMaximum(), prec ) + ", " + qgsDoubleToString( rect->yMaximum(), prec ) + "],\n";
1190  fcString += QLatin1String( " \"features\": [\n" );
1191  response.write( fcString.toUtf8() );
1192  }
1193  else
1194  {
1195  if ( format == QgsWfsParameters::Format::GML2 )
1196  response.setHeader( "Content-Type", "text/xml; subtype=gml/2.1.2; charset=utf-8" );
1197  else
1198  response.setHeader( "Content-Type", "text/xml; subtype=gml/3.1.1; charset=utf-8" );
1199 
1200  //Prepare url
1201  QString hrefString = serviceUrl( request, project, *settings );
1202 
1203  QUrl mapUrl( hrefString );
1204 
1205  QUrlQuery query( mapUrl );
1206  query.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
1207  //Set version
1208  if ( mWfsParameters.version().isEmpty() )
1209  query.addQueryItem( QStringLiteral( "VERSION" ), implementationVersion() );
1210  else if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1211  query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.1.0" ) );
1212  else
1213  query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
1214 
1215  const auto queryItems {query.queryItems()};
1216  for ( auto param : std::as_const( queryItems ) )
1217  {
1218  if ( sParamFilter.contains( param.first.toUpper() ) )
1219  query.removeAllQueryItems( param.first );
1220  }
1221 
1222  query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
1223  query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
1224  if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1225  {
1226  if ( format == QgsWfsParameters::Format::GML2 )
1227  query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
1228  else
1229  query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
1230  }
1231  else
1232  query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );
1233 
1234  mapUrl.setQuery( query );
1235 
1236  hrefString = mapUrl.toString();
1237 
1238  QString wfsSchema;
1239  if ( mWfsParameters.version().isEmpty() || mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1240  wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" );
1241  else
1242  wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
1243 
1244  //wfs:FeatureCollection valid
1245  fcString = QStringLiteral( "<wfs:FeatureCollection" );
1246  fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
1247  fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
1248  fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
1249  fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
1250  fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
1251  fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
1252  fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
1253  fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " " + wfsSchema + " " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
1254  fcString += QLatin1String( ">\n" );
1255 
1256  response.write( fcString.toUtf8() );
1257  response.flush();
1258 
1259  QDomDocument doc;
1260  QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1261  if ( format == QgsWfsParameters::Format::GML3 )
1262  {
1263  // For WFS 1.1 we honor requested CRS and axis order
1264  // Axis is not inverted if srsName starts with EPSG
1265  // It needs to be an EPSG urn, e.g. urn:ogc:def:crs:EPSG::4326
1266  // This follows geoserver convention
1267  // See: https://docs.geoserver.org/stable/en/user/services/wfs/axis_order.html
1268  const QString requestSrsName = request.serverParameters().value( QStringLiteral( "SRSNAME" ) );
1269  const QString srsName = !requestSrsName.isEmpty() ? requestSrsName : crs.authid();
1270  const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
1271  crs.hasAxisInverted() &&
1272  ! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
1273 
1274  // If requested SRS (srsName) is different from rect CRS (crs) we need to transform the envelope
1275  QgsCoordinateTransform transform;
1276  transform.setSourceCrs( crs );
1278  QgsRectangle crsCorrectedRect { rect ? *rect : QgsRectangle() };
1279 
1280  try
1281  {
1282  crsCorrectedRect = transform.transformBoundingBox( crsCorrectedRect );
1283  }
1284  catch ( QgsException &cse )
1285  {
1286  Q_UNUSED( cse )
1287  }
1288 
1289  QDomElement envElem = QgsOgcUtils::rectangleToGMLEnvelope( &crsCorrectedRect, doc, srsName, invertAxis, prec );
1290  if ( !envElem.isNull() )
1291  {
1292  if ( crs.isValid() && srsName.isEmpty() )
1293  {
1294  envElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1295  }
1296  bbElem.appendChild( envElem );
1297  doc.appendChild( bbElem );
1298  }
1299  }
1300  else
1301  {
1302  QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( rect, doc, prec );
1303  if ( !boxElem.isNull() )
1304  {
1305  if ( crs.isValid() )
1306  {
1307  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1308  }
1309  bbElem.appendChild( boxElem );
1310  doc.appendChild( bbElem );
1311  }
1312  }
1313  response.write( doc.toByteArray() );
1314  response.flush();
1315  }
1316  }
1317 
1318  void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
1319  const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1320  {
1321  if ( !feature.isValid() )
1322  return;
1323 
1324  if ( format == QgsWfsParameters::Format::GeoJSON )
1325  {
1326  QString fcString;
1327  if ( featIdx == 0 )
1328  fcString += QLatin1String( " " );
1329  else
1330  fcString += QLatin1String( " ," );
1331 
1332  const QgsCoordinateReferenceSystem destinationCrs { params.srsName.isEmpty( ) ? QStringLiteral( "EPSG:4326" ) : params.srsName };
1333  if ( ! destinationCrs.isValid() )
1334  {
1335  throw QgsRequestNotWellFormedException( QStringLiteral( "srsName error: '%1' is not valid." ).arg( params.srsName ) );
1336  }
1337 
1338  mJsonExporter.setDestinationCrs( destinationCrs );
1339  mJsonExporter.setTransformGeometries( true );
1340  mJsonExporter.setSourceCrs( params.crs );
1341  mJsonExporter.setIncludeGeometry( false );
1342  mJsonExporter.setIncludeAttributes( !params.attributeIndexes.isEmpty() );
1343  mJsonExporter.setAttributes( params.attributeIndexes );
1344  fcString += createFeatureGeoJSON( feature, params, pkAttributes );
1345  fcString += QLatin1String( "\n" );
1346 
1347  response.write( fcString.toUtf8() );
1348  }
1349  else
1350  {
1351  QDomDocument gmlDoc;
1352  QDomElement featureElement;
1353  if ( format == QgsWfsParameters::Format::GML3 )
1354  {
1355  featureElement = createFeatureGML3( feature, gmlDoc, params, project, pkAttributes );
1356  gmlDoc.appendChild( featureElement );
1357  }
1358  else
1359  {
1360  featureElement = createFeatureGML2( feature, gmlDoc, params, project, pkAttributes );
1361  gmlDoc.appendChild( featureElement );
1362  }
1363  response.write( gmlDoc.toByteArray() );
1364  }
1365 
1366  // Stream partial content
1367  response.flush();
1368  }
1369 
1370  void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format )
1371  {
1372  QString fcString;
1373  if ( format == QgsWfsParameters::Format::GeoJSON )
1374  {
1375  fcString += QLatin1String( " ]\n" );
1376  fcString += QLatin1Char( '}' );
1377  }
1378  else
1379  {
1380  fcString = QStringLiteral( "</wfs:FeatureCollection>\n" );
1381  }
1382  response.write( fcString.toUtf8() );
1383  }
1384 
1385 
1386  QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams &params, const QgsAttributeList &pkAttributes )
1387  {
1388  QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1389  //QgsJsonExporter force transform geometry to EPSG:4326
1390  //and the RFC 7946 GeoJSON specification recommends limiting coordinate precision to 6
1391  //Q_UNUSED( prec )
1392 
1393  //copy feature so we can modify its geometry as required
1394  QgsFeature f( feature );
1395  QgsGeometry geom = feature.geometry();
1396  if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1397  {
1398  mJsonExporter.setIncludeGeometry( true );
1399  if ( params.geometryName == QLatin1String( "EXTENT" ) )
1400  {
1401  QgsRectangle box = geom.boundingBox();
1402  f.setGeometry( QgsGeometry::fromRect( box ) );
1403  }
1404  else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1405  {
1406  f.setGeometry( geom.centroid() );
1407  }
1408  }
1409 
1410  return mJsonExporter.exportFeature( f, QVariantMap(), id );
1411  }
1412 
1413 
1414  QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1415  {
1416  //gml:FeatureMember
1417  QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1418 
1419  //qgs:%TYPENAME%
1420  QDomElement typeNameElement = doc.createElement( "qgs:" + params.typeName /*qgs:%TYPENAME%*/ );
1421  QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1422  typeNameElement.setAttribute( QStringLiteral( "fid" ), id );
1423  featureElement.appendChild( typeNameElement );
1424 
1425  //add geometry column (as gml)
1426  QgsGeometry geom = feature.geometry();
1427  if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1428  {
1429  int prec = params.precision;
1430  QgsCoordinateReferenceSystem crs = params.crs;
1431  QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1432  try
1433  {
1434  QgsGeometry transformed = geom;
1435  if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1436  {
1437  geom = transformed;
1438  crs = params.outputCrs;
1439  if ( crs.isGeographic() && !params.crs.isGeographic() )
1440  prec = std::min( params.precision + 3, 6 );
1441  }
1442  }
1443  catch ( QgsCsException &cse )
1444  {
1445  Q_UNUSED( cse )
1446  }
1447 
1448  QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1449  QDomElement gmlElem;
1450  QgsGeometry cloneGeom( geom );
1451  if ( params.geometryName == QLatin1String( "EXTENT" ) )
1452  {
1453  cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1454  }
1455  else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1456  {
1457  cloneGeom = geom.centroid();
1458  }
1459  else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1460  {
1461  cloneGeom.convertToMultiType();
1462  }
1463  const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1464  if ( abstractGeom )
1465  {
1466  gmlElem = abstractGeom->asGml2( doc, prec, "http://www.opengis.net/gml" );
1467  }
1468 
1469  if ( !gmlElem.isNull() )
1470  {
1471  QgsRectangle box = geom.boundingBox();
1472  QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1473  QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, prec );
1474 
1475  if ( crs.isValid() )
1476  {
1477  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1478  gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1479  }
1480 
1481  bbElem.appendChild( boxElem );
1482  typeNameElement.appendChild( bbElem );
1483 
1484  geomElem.appendChild( gmlElem );
1485  typeNameElement.appendChild( geomElem );
1486  }
1487  }
1488 
1489  //read all attribute values from the feature
1490  const QgsAttributes featureAttributes = feature.attributes();
1491  const QgsFields fields = feature.fields();
1492  for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1493  {
1494  int idx = params.attributeIndexes[i];
1495  if ( idx >= fields.count() || QgsVariantUtils::isNull( featureAttributes[idx] ) )
1496  {
1497  continue;
1498  }
1499 
1500  const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1501  typeNameElement.appendChild( fieldElem );
1502  }
1503 
1504  return featureElement;
1505  }
1506 
1507  QDomElement createFeatureGML3( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1508  {
1509  //gml:FeatureMember
1510  QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1511 
1512  //qgs:%TYPENAME%
1513  QDomElement typeNameElement = doc.createElement( QStringLiteral( "qgs:" ) + params.typeName /*qgs:%TYPENAME%*/ );
1514  QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1515  typeNameElement.setAttribute( QStringLiteral( "gml:id" ), id );
1516  featureElement.appendChild( typeNameElement );
1517 
1518  //add geometry column (as gml)
1519  QgsGeometry geom = feature.geometry();
1520  if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1521  {
1522  int prec = params.precision;
1523  QgsCoordinateReferenceSystem crs = params.crs;
1524  QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1525  try
1526  {
1527  QgsGeometry transformed = geom;
1528  if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1529  {
1530  geom = transformed;
1531  crs = params.outputCrs;
1532  if ( crs.isGeographic() && !params.crs.isGeographic() )
1533  prec = std::min( params.precision + 3, 6 );
1534  }
1535  }
1536  catch ( QgsCsException &cse )
1537  {
1538  Q_UNUSED( cse )
1539  }
1540 
1541  QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1542  QDomElement gmlElem;
1543  QgsGeometry cloneGeom( geom );
1544  if ( params.geometryName == QLatin1String( "EXTENT" ) )
1545  {
1546  cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1547  }
1548  else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1549  {
1550  cloneGeom = geom.centroid();
1551  }
1552  else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1553  {
1554  cloneGeom.convertToMultiType();
1555  }
1556  const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1557  if ( abstractGeom )
1558  {
1559  gmlElem = abstractGeom->asGml3( doc, prec, "http://www.opengis.net/gml", params.hasAxisInverted ? QgsAbstractGeometry::AxisOrder::YX : QgsAbstractGeometry::AxisOrder::XY );
1560  }
1561 
1562  if ( !gmlElem.isNull() )
1563  {
1564  QgsRectangle box = geom.boundingBox();
1565  QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1566  QDomElement boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, params.srsName, params.hasAxisInverted, prec );
1567 
1568  if ( crs.isValid() && params.srsName.isEmpty() )
1569  {
1570  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1571  gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1572  }
1573  else if ( !params.srsName.isEmpty() )
1574  {
1575  gmlElem.setAttribute( QStringLiteral( "srsName" ), params.srsName );
1576  }
1577 
1578  bbElem.appendChild( boxElem );
1579  typeNameElement.appendChild( bbElem );
1580 
1581  geomElem.appendChild( gmlElem );
1582  typeNameElement.appendChild( geomElem );
1583  }
1584  }
1585 
1586  //read all attribute values from the feature
1587  const QgsAttributes featureAttributes = feature.attributes();
1588  const QgsFields fields = feature.fields();
1589  for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1590  {
1591  int idx = params.attributeIndexes[i];
1592  if ( idx >= fields.count() || QgsVariantUtils::isNull( featureAttributes[idx] ) )
1593  {
1594  continue;
1595  }
1596 
1597  const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1598  typeNameElement.appendChild( fieldElem );
1599  }
1600 
1601  return featureElement;
1602  }
1603 
1604  QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc )
1605  {
1606  const QgsEditorWidgetSetup setup = field.editorWidgetSetup();
1607  const thread_local QRegularExpression sCleanTagNameRegExp( QStringLiteral( "[^\\w\\.-_]" ), QRegularExpression::PatternOption::UseUnicodePropertiesOption );
1608  const QString attributeName = field.name().replace( ' ', '_' ).replace( sCleanTagNameRegExp, QString() );
1609  QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:" ) + attributeName );
1610  if ( QgsVariantUtils::isNull( value ) )
1611  {
1612  fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
1613  }
1614  else
1615  {
1616  const QString fieldText = encodeValueToText( value, setup );
1617  //do we need CDATA
1618  if ( fieldText.indexOf( '<' ) != -1 || fieldText.indexOf( '&' ) != -1 )
1619  {
1620  fieldElem.appendChild( doc.createCDATASection( fieldText ) );
1621  }
1622  else
1623  {
1624  fieldElem.appendChild( doc.createTextNode( fieldText ) );
1625  }
1626  }
1627  return fieldElem;
1628  }
1629 
1630  QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup )
1631  {
1632  if ( QgsVariantUtils::isNull( value ) )
1633  return QString();
1634 
1635  if ( setup.type() == QStringLiteral( "DateTime" ) )
1636  {
1637  // For time fields use const TIME_FORMAT
1638  if ( value.type() == QVariant::Time )
1639  {
1640  return value.toTime().toString( QgsDateTimeFieldFormatter::TIME_FORMAT );
1641  }
1642 
1643  // Get editor widget setup config
1644  const QVariantMap config = setup.config();
1645  // Get field format, for ISO format then use const display format
1646  // else use field format saved in editor widget setup config
1647  const QString fieldFormat =
1648  config.value( QStringLiteral( "field_iso_format" ), false ).toBool() ?
1650  config.value( QStringLiteral( "field_format" ), QgsDateTimeFieldFormatter::defaultFormat( value.type() ) ).toString();
1651 
1652  // Convert value to date time
1653  QDateTime date = value.toDateTime();
1654  // if not valid try to convert to date with field format
1655  if ( !date.isValid() )
1656  {
1657  date = QDateTime::fromString( value.toString(), fieldFormat );
1658  }
1659  // if the date is valid, convert to string with field format
1660  if ( date.isValid() )
1661  {
1662  return date.toString( fieldFormat );
1663  }
1664  // else provide the value as string
1665  return value.toString();
1666  }
1667  else if ( setup.type() == QStringLiteral( "Range" ) )
1668  {
1669  const QVariantMap config = setup.config();
1670  if ( config.contains( QStringLiteral( "Precision" ) ) )
1671  {
1672  // if precision is defined, use it
1673  bool ok;
1674  int precision( config[ QStringLiteral( "Precision" ) ].toInt( &ok ) );
1675  if ( ok )
1676  return QString::number( value.toDouble(), 'f', precision );
1677  }
1678  }
1679 
1680  switch ( value.type() )
1681  {
1682  case QVariant::Int:
1683  case QVariant::UInt:
1684  case QVariant::LongLong:
1685  case QVariant::ULongLong:
1686  case QVariant::Double:
1687  return value.toString();
1688 
1689  case QVariant::Bool:
1690  return value.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" );
1691 
1692  case QVariant::StringList:
1693  case QVariant::List:
1694  case QVariant::Map:
1695  return QgsJsonUtils::encodeValue( value );
1696 
1697  default:
1698  case QVariant::String:
1699  return value.toString();
1700  }
1701  }
1702 
1703 
1704  } // namespace
1705 
1706 } // namespace QgsWfs
@ Success
Operation succeeded.
@ Fid
Filter using feature ID.
@ Fids
Filter using feature IDs.
@ NoFilter
No filter is applied.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ NoFlags
No flags are set.
@ Vector
Vector layer.
@ NoGeometry
No geometry.
@ HideFromWfs
Field is not available if layer is served as WFS from QGIS server.
Abstract base class for all geometries.
virtual QDomElement asGml2(QDomDocument &doc, int precision=17, const QString &ns="gml", AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const =0
Returns a GML2 representation of the geometry.
virtual void swapXy()=0
Swaps the x and y coordinates from the geometry.
virtual QDomElement asGml3(QDomDocument &doc, int precision=17, const QString &ns="gml", AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const =0
Returns a GML3 representation of the geometry.
A helper class that centralizes restrictions given by all the access control filter plugins.
QStringList layerAttributes(const QgsVectorLayer *layer, const QStringList &attributes) const override
Returns the authorized layer attributes.
void filterFeatures(const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures) const override
Filter the features of the layer.
bool layerReadPermission(const QgsMapLayer *layer) const
Returns the layer read right.
A vector of attributes.
Definition: qgsattributes.h:59
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromUserInput(const QString &definition)
Set up this CRS from various text formats.
bool hasAxisInverted() const
Returns whether the axis order is inverted for the CRS compared to the order east/north (longitude/la...
Class for doing transforms between two map coordinate systems.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs)
Sets the source coordinate reference system.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination coordinate reference system.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
static const QString DISPLAY_FOR_ISO_FORMAT
static QString defaultFormat(QVariant::Type type)
Gets the default format in function of the type.
static const QString TIME_FORMAT
Date format was localized by applyLocaleChange before QGIS 3.30.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
Defines a QGIS exception class.
Definition: qgsexception.h:35
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setLimit(long long limit)
Set the maximum number of features to request.
QgsRectangle filterRect() const
Returns the rectangle from which features will be taken.
QgsFeatureRequest & addOrderBy(const QString &expression, bool ascending=true)
Adds a new OrderByClause, appending it as the least important one.
Qgis::FeatureRequestFilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
Qgis::FeatureRequestFlags flags() const
Returns the flags which affect how features are fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
bool acceptFeature(const QgsFeature &feature)
Check if a feature is accepted by this requests filter.
QgsAttributeList subsetOfAttributes() const
Returns the subset of attributes which at least need to be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsFields fields
Definition: qgsfeature.h:66
QgsGeometry geometry
Definition: qgsfeature.h:67
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:216
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
QString name
Definition: qgsfield.h:62
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition: qgsfield.cpp:714
Container of fields for a vector layer.
Definition: qgsfields.h:45
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:168
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:359
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:162
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
Q_GADGET bool isNull
Definition: qgsgeometry.h:164
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Handles exporting QgsFeature features to GeoJSON features.
Definition: qgsjsonutils.h:45
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
Base class for all map layer types.
Definition: qgsmaplayer.h:75
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:81
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Qgis::LayerType type
Definition: qgsmaplayer.h:82
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
RAII class to restore layer filters on destruction.
static void applyAccessControlLayerFilters(const QgsAccessControl *accessControl, QgsMapLayer *mapLayer, QHash< QgsMapLayer *, QString > &originalLayerFilters)
Apply filter from AccessControl.
static QDomElement rectangleToGMLEnvelope(QgsRectangle *env, QDomDocument &doc, int precision=17)
Exports the rectangle to GML3 Envelope.
static QDomElement rectangleToGMLBox(QgsRectangle *box, QDomDocument &doc, int precision=17)
Exports the rectangle to GML2 Box.
A class to describe the version of a project.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:107
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:413
bool isEmpty() const
Returns true if the rectangle has no area.
Definition: qgsrectangle.h:492
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:355
QgsServerInterface Class defining interfaces exposed by QGIS Server and made available to plugins.
virtual QgsAccessControl * accessControls() const =0
Gets the registered access control filters.
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
QString value(const QString &key) const
Returns the value of a parameter.
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
QgsServerParameters serverParameters() const
Returns parameters.
QgsServerRequest::Parameters parameters() const
Returns a map of query parameters with keys converted to uppercase.
QMap< QString, QString > Parameters
virtual QByteArray data() const
Returns post/put data Check for QByteArray::isNull() to check if data is available.
QgsServerResponse Class defining response interface passed to services QgsService::executeRequest() m...
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
virtual void flush()
Flushes the current output buffer to the network.
virtual void setHeader(const QString &key, const QString &value)=0
Set Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
Provides a way to retrieve settings by prioritizing according to environment variables,...
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
This is the base class for vector data providers.
virtual QgsAttributeList pkAttributeIndexes() const
Returns list of indexes of fields that make up the primary key.
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
QgsStringMap attributeAliases() const
Returns a map of field name to attribute alias.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
Exception thrown in case of malformed request.
Exception thrown when data access violates access controls.
Provides an interface to retrieve and manipulate WFS parameters received from the client.
Format
Output format for the response.
static bool isMultiType(Qgis::WkbType type)
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:758
SERVER_EXPORT QgsFeatureRequest updateFeatureRequestFromServerFids(QgsFeatureRequest &featureRequest, const QStringList &serverFids, const QgsVectorDataProvider *provider)
Returns the feature request based on feature ids build with primary keys.
SERVER_EXPORT QString getServerFid(const QgsFeature &feature, const QgsAttributeList &pkAttributes)
Returns the feature id based on primary keys.
SERVER_EXPORT QStringList wfsLayerIds(const QgsProject &project)
Returns the Layer ids list defined in a QGIS project as published in WFS.
SERVER_EXPORT int wfsLayerPrecision(const QgsProject &project, const QString &layerId)
Returns the Layer precision defined in a QGIS project for the WFS GetFeature.
WMS implementation.
Definition: qgswfs.cpp:36
QString layerTypeName(const QgsMapLayer *layer)
Returns typename from vector layer.
Definition: qgswfsutils.cpp:68
QString implementationVersion()
Returns the highest version supported by this implementation.
Definition: qgswfsutils.cpp:31
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Service URL string.
Definition: qgswfsutils.cpp:36
const QString OGC_NAMESPACE
Definition: qgswfsutils.h:76
const QString GML_NAMESPACE
Definition: qgswfsutils.h:75
const QString WFS_NAMESPACE
Definition: qgswfsutils.h:74
getFeatureRequest parseGetFeatureRequestBody(QDomElement &docElem, const QgsProject *project)
Transform RequestBody root element to getFeatureRequest.
getFeatureQuery parseQueryElement(QDomElement &queryElem, const QgsProject *project)
Transform Query element to getFeatureQuery.
const QString QGS_NAMESPACE
Definition: qgswfsutils.h:77
getFeatureRequest parseGetFeatureParameters(const QgsProject *project)
Transform parameters to getFeatureRequest.
void parseSortByElement(QDomElement &sortByElem, QgsFeatureRequest &featureRequest, const QString &typeName)
Add SortBy element to featureRequest.
void writeGetFeature(QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request, QgsServerResponse &response)
Output WFS GetFeature response.
QgsFeatureRequest parseFilterElement(const QString &typeName, QDomElement &filterElem, QgsProject *project)
Transform a Filter element to a feature request.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:5089
QMap< QString, QString > QgsStringMap
Definition: qgis.h:5702
QList< int > QgsAttributeList
Definition: qgsfield.h:27
const QString & srsName
bool forceGeomToMulti
const QString & geometryName
const QgsCoordinateReferenceSystem & outputCrs
const QgsCoordinateReferenceSystem & crs
const QString & typeName
int precision
bool hasAxisInverted
const QgsAttributeList & attributeIndexes
bool withGeom
QgsFeatureRequest featureRequest
QgsWfsParameters::Format outputFormat
QList< getFeatureQuery > queries