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