QGIS API Documentation  3.27.0-Master (0e23467727)
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  QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc );
73 
74  QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup );
75 
76  QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes );
77 
78  QDomElement createFeatureGML3( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes );
79 
80  void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project,
81  QgsWfsParameters::Format format, int numberOfFeatures, const QStringList &typeNames, const QgsServerSettings *serverSettings );
82 
83  void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project,
85  QgsRectangle *rect, const QStringList &typeNames, const QgsServerSettings *settings );
86 
87  void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
88  const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes = QgsAttributeList() );
89 
90  void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format );
91 
92  QgsServerRequest::Parameters mRequestParameters;
93  QgsWfsParameters mWfsParameters;
94  /* GeoJSON Exporter */
95  QgsJsonExporter mJsonExporter;
96  }
97 
98  void writeGetFeature( QgsServerInterface *serverIface, const QgsProject *project,
99  const QString &version, const QgsServerRequest &request,
100  QgsServerResponse &response )
101  {
102  Q_UNUSED( version )
103 
104  mRequestParameters = request.parameters();
105  mWfsParameters = QgsWfsParameters( QUrlQuery( request.url() ) );
106  mWfsParameters.dump();
107  getFeatureRequest aRequest;
108 
109  QDomDocument doc;
110  QString errorMsg;
111 
112  if ( doc.setContent( request.data(), true, &errorMsg ) )
113  {
114  QDomElement docElem = doc.documentElement();
115  aRequest = parseGetFeatureRequestBody( docElem, project );
116  }
117  else
118  {
119  aRequest = parseGetFeatureParameters( project );
120  }
121 
122  // store typeName
123  QStringList typeNameList;
124 
125  // Request metadata
126  bool onlyOneLayer = ( aRequest.queries.size() == 1 );
127  QgsRectangle requestRect;
128  QgsCoordinateReferenceSystem requestCrs;
129  int requestPrecision = 6;
130  if ( !onlyOneLayer )
131  requestCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) );
132 
133  QList<getFeatureQuery>::iterator qIt = aRequest.queries.begin();
134  for ( ; qIt != aRequest.queries.end(); ++qIt )
135  {
136  typeNameList << ( *qIt ).typeName;
137  }
138 
139  // get layers and
140  // update the request metadata
141  QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
142  QMap<QString, QgsMapLayer *> mapLayerMap;
143  for ( int i = 0; i < wfsLayerIds.size(); ++i )
144  {
145  QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) );
146  if ( !layer )
147  {
148  continue;
149  }
150  if ( layer->type() != QgsMapLayerType::VectorLayer )
151  {
152  continue;
153  }
154 
155  QString name = layerTypeName( layer );
156 
157  if ( typeNameList.contains( name ) )
158  {
159  // store layers
160  mapLayerMap[name] = layer;
161  // update request metadata
162  if ( onlyOneLayer )
163  {
164  requestRect = layer->extent();
165  requestCrs = layer->crs();
166  }
167  else
168  {
169  QgsCoordinateTransform transform( layer->crs(), requestCrs, project );
170  try
171  {
172  if ( requestRect.isEmpty() )
173  {
174  requestRect = transform.transform( layer->extent() );
175  }
176  else
177  {
178  requestRect.combineExtentWith( transform.transform( layer->extent() ) );
179  }
180  }
181  catch ( QgsException &cse )
182  {
183  Q_UNUSED( cse )
184  requestRect = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
185  }
186  }
187  }
188  }
189 
190  // check if all typename are valid
191  for ( const QString &typeName : typeNameList )
192  {
193  if ( !mapLayerMap.contains( typeName ) )
194  {
195  throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' could not be found" ).arg( typeName ) );
196  }
197  }
198 
199 #ifdef HAVE_SERVER_PYTHON_PLUGINS
200  QgsAccessControl *accessControl = serverIface->accessControls();
201  //scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope
202  //there's LOTS of potential exit paths here, so we avoid having to restore the filters manually
203  std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer() );
204 #else
205  ( void )serverIface;
206 #endif
207 
208  // features counters
209  long sentFeatures = 0;
210  long iteratedFeatures = 0;
211  // sent features
212  QgsFeature feature;
213  qIt = aRequest.queries.begin();
214  for ( ; qIt != aRequest.queries.end(); ++qIt )
215  {
216  getFeatureQuery &query = *qIt;
217  QString typeName = query.typeName;
218 
219  QgsMapLayer *layer = mapLayerMap[typeName];
220 #ifdef HAVE_SERVER_PYTHON_PLUGINS
221  if ( accessControl && !accessControl->layerReadPermission( layer ) )
222  {
223  throw QgsSecurityAccessException( QStringLiteral( "Feature access permission denied" ) );
224  }
225 #endif
226  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
227  if ( !vlayer )
228  {
229  throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' layer error" ).arg( typeName ) );
230  }
231 
232  //test provider
233  QgsVectorDataProvider *provider = vlayer->dataProvider();
234  if ( !provider )
235  {
236  throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' layer's provider error" ).arg( typeName ) );
237  }
238 #ifdef HAVE_SERVER_PYTHON_PLUGINS
239  if ( accessControl )
240  {
241  QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( accessControl, vlayer, filterRestorer->originalFilters() );
242  }
243 #endif
244  //is there alias info for this vector layer?
245  QMap< int, QString > layerAliasInfo;
246  QgsStringMap aliasMap = vlayer->attributeAliases();
247  QgsStringMap::const_iterator aliasIt = aliasMap.constBegin();
248  for ( ; aliasIt != aliasMap.constEnd(); ++aliasIt )
249  {
250  int attrIndex = vlayer->fields().lookupField( aliasIt.key() );
251  if ( attrIndex != -1 )
252  {
253  layerAliasInfo.insert( attrIndex, aliasIt.value() );
254  }
255  }
256 
257  // get propertyList from query
258  const QStringList propertyList = query.propertyList;
259 
260  //Using pending attributes and pending fields
261  QgsAttributeList attrIndexes = vlayer->attributeList();
262  const QgsFields fields = vlayer->fields();
263  bool withGeom = true;
264  if ( !propertyList.isEmpty() && propertyList.first() != QLatin1String( "*" ) )
265  {
266  withGeom = false;
267  QStringList::const_iterator plstIt;
268  QList<int> idxList;
269  // build corresponding propertyname
270  QList<QString> propertynames;
271  QList<QString> fieldnames;
272  for ( const QgsField &field : fields )
273  {
274  fieldnames.append( field.name() );
275  propertynames.append( field.name().replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() ) );
276  }
277  QString fieldName;
278  for ( plstIt = propertyList.constBegin(); plstIt != propertyList.constEnd(); ++plstIt )
279  {
280  fieldName = *plstIt;
281  int fieldNameIdx = propertynames.indexOf( fieldName );
282  if ( fieldNameIdx == -1 )
283  {
284  fieldNameIdx = fieldnames.indexOf( fieldName );
285  }
286  if ( fieldNameIdx > -1 )
287  {
288  idxList.append( fieldNameIdx );
289  }
290  else if ( fieldName == QLatin1String( "geometry" ) )
291  {
292  withGeom = true;
293  }
294  }
295  if ( !idxList.isEmpty() )
296  {
297  attrIndexes = idxList;
298  }
299  }
300 
301  //excluded attributes for this layer
302  if ( !attrIndexes.isEmpty() )
303  {
304  for ( const QgsField &field : fields )
305  {
307  {
308  int fieldNameIdx = fields.indexOf( field.name() );
309  if ( fieldNameIdx > -1 && attrIndexes.contains( fieldNameIdx ) )
310  {
311  attrIndexes.removeOne( fieldNameIdx );
312  }
313  }
314  }
315  }
316 
317  // update request
318  QgsFeatureRequest featureRequest = query.featureRequest;
319 
320  // expression context
321  QgsExpressionContext expressionContext;
322  expressionContext << QgsExpressionContextUtils::globalScope()
325  featureRequest.setExpressionContext( expressionContext );
326 
327  if ( !query.serverFids.isEmpty() )
328  {
329  QgsServerFeatureId::updateFeatureRequestFromServerFids( featureRequest, query.serverFids, provider );
330  }
331 
332  // geometry flags
333  if ( vlayer->wkbType() == QgsWkbTypes::NoGeometry )
334  featureRequest.setFlags( featureRequest.flags() | QgsFeatureRequest::NoGeometry );
335  else
336  featureRequest.setFlags( featureRequest.flags() | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) );
337 
338  // subset of attributes
339  featureRequest.setSubsetOfAttributes( attrIndexes );
340 #ifdef HAVE_SERVER_PYTHON_PLUGINS
341  if ( accessControl )
342  {
343  accessControl->filterFeatures( vlayer, featureRequest );
344 
345  QStringList attributes = QStringList();
346  for ( int idx : std::as_const( attrIndexes ) )
347  {
348  attributes.append( vlayer->fields().field( idx ).name() );
349  }
350  featureRequest.setSubsetOfAttributes(
351  accessControl->layerAttributes( vlayer, attributes ),
352  vlayer->fields() );
353  attrIndexes = featureRequest.subsetOfAttributes();
354  }
355 #endif
356 
357  // Force pkAttributes in subset of attributes for primary fid building
358  const QgsAttributeList pkAttributes = provider->pkAttributeIndexes();
359  if ( !pkAttributes.isEmpty() )
360  {
361  QgsAttributeList subsetOfAttrs = featureRequest.subsetOfAttributes();
362  for ( int idx : pkAttributes )
363  {
364  if ( !subsetOfAttrs.contains( idx ) )
365  {
366  subsetOfAttrs.prepend( idx );
367  }
368  }
369  if ( subsetOfAttrs.size() != featureRequest.subsetOfAttributes().size() )
370  {
371  featureRequest.setSubsetOfAttributes( subsetOfAttrs );
372  }
373  }
374 
375  if ( onlyOneLayer )
376  {
377  requestPrecision = QgsServerProjectUtils::wfsLayerPrecision( *project, vlayer->id() );
378  }
379 
380  if ( aRequest.maxFeatures > 0 )
381  {
382  featureRequest.setLimit( aRequest.maxFeatures + aRequest.startIndex - sentFeatures );
383  }
384  // specific layer precision
385  int layerPrecision = QgsServerProjectUtils::wfsLayerPrecision( *project, vlayer->id() );
386  // specific layer crs
387  QgsCoordinateReferenceSystem layerCrs = vlayer->crs();
388 
389  // Geometry name
390  QString geometryName = aRequest.geometryName;
391  if ( !withGeom )
392  {
393  geometryName = QLatin1String( "NONE" );
394  }
395  // outputCrs
397  if ( !query.srsName.isEmpty() )
398  {
400  }
401 
403 
404  if ( !featureRequest.filterRect().isEmpty() )
405  {
406  QgsCoordinateTransform transform( outputCrs, vlayer->crs(), project );
407  try
408  {
409  featureRequest.setFilterRect( transform.transform( featureRequest.filterRect() ) );
410  }
411  catch ( QgsException &cse )
412  {
413  Q_UNUSED( cse )
414  }
415  if ( onlyOneLayer )
416  {
417  requestRect = featureRequest.filterRect();
418  }
419  }
420 
421  // Iterate through features
422  QgsFeatureIterator fit = vlayer->getFeatures( featureRequest );
423 
424  if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
425  {
426  while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
427  {
428  if ( iteratedFeatures >= aRequest.startIndex )
429  {
430  ++sentFeatures;
431  }
432  ++iteratedFeatures;
433  }
434  }
435  else
436  {
437 
438  // For WFS 1.1 we honor requested CRS and axis order
439  const QString srsName {request.serverParameters().value( QStringLiteral( "SRSNAME" ) )};
440  const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
442  ! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
443 
444  const createFeatureParams cfp = { layerPrecision,
445  layerCrs,
446  attrIndexes,
447  typeName,
448  withGeom,
449  geometryName,
450  outputCrs,
452  srsName,
453  invertAxis
454  };
455  while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
456  {
457  if ( iteratedFeatures == aRequest.startIndex )
458  startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList, serverIface->serverSettings() );
459 
460  if ( iteratedFeatures >= aRequest.startIndex )
461  {
462  setGetFeature( response, aRequest.outputFormat, feature, sentFeatures, cfp, project, provider->pkAttributeIndexes() );
463  ++sentFeatures;
464  }
465  ++iteratedFeatures;
466  }
467  }
468  }
469 
470 #ifdef HAVE_SERVER_PYTHON_PLUGINS
471  //force restoration of original layer filters
472  filterRestorer.reset();
473 #endif
474 
475  if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
476  {
477  hitGetFeature( request, response, project, aRequest.outputFormat, sentFeatures, typeNameList, serverIface->serverSettings() );
478  }
479  else
480  {
481  // End of GetFeature
482  if ( iteratedFeatures <= aRequest.startIndex )
483  startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList, serverIface->serverSettings() );
484  endGetFeature( response, aRequest.outputFormat );
485  }
486 
487  }
488 
490  {
491  getFeatureRequest request;
492  request.maxFeatures = mWfsParameters.maxFeaturesAsInt();
493  request.startIndex = mWfsParameters.startIndexAsInt();
494  request.outputFormat = mWfsParameters.outputFormat();
495 
496  // Verifying parameters mutually exclusive
497  QStringList fidList = mWfsParameters.featureIds();
498  bool paramContainsFeatureIds = !fidList.isEmpty();
499  QStringList filterList = mWfsParameters.filters();
500  bool paramContainsFilters = !filterList.isEmpty();
501  QString bbox = mWfsParameters.bbox();
502  bool paramContainsBbox = !bbox.isEmpty();
503  if ( ( paramContainsFeatureIds
504  && ( paramContainsFilters || paramContainsBbox ) )
505  || ( paramContainsFilters
506  && ( paramContainsFeatureIds || paramContainsBbox ) )
507  || ( paramContainsBbox
508  && ( paramContainsFeatureIds || paramContainsFilters ) )
509  )
510  {
511  throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID FILTER and BBOX parameters are mutually exclusive" ) );
512  }
513 
514  // Get and split PROPERTYNAME parameter
515  QStringList propertyNameList = mWfsParameters.propertyNames();
516 
517  // Manage extra parameter GeometryName
518  request.geometryName = mWfsParameters.geometryNameAsString().toUpper();
519 
520  QStringList typeNameList;
521  // parse FEATUREID
522  if ( paramContainsFeatureIds )
523  {
524  // Verifying the 1:1 mapping between FEATUREID and PROPERTYNAME
525  if ( !propertyNameList.isEmpty() && propertyNameList.size() != fidList.size() )
526  {
527  throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a FEATUREID and the PROPERTYNAME list" ) );
528  }
529  if ( propertyNameList.isEmpty() )
530  {
531  for ( int i = 0; i < fidList.size(); ++i )
532  {
533  propertyNameList << QStringLiteral( "*" );
534  }
535  }
536 
537  QMap<QString, QStringList> fidsMap;
538 
539  QStringList::const_iterator fidIt = fidList.constBegin();
540  QStringList::const_iterator propertyNameIt = propertyNameList.constBegin();
541  for ( ; fidIt != fidList.constEnd(); ++fidIt )
542  {
543  // Get FeatureID
544  QString fid = *fidIt;
545  fid = fid.trimmed();
546  // Get PropertyName for this FeatureID
547  QString propertyName;
548  if ( propertyNameIt != propertyNameList.constEnd() )
549  {
550  propertyName = *propertyNameIt;
551  }
552  // testing typename in the WFS featureID
553  if ( !fid.contains( '.' ) )
554  {
555  throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID has to have TYPENAME in the values" ) );
556  }
557 
558  QString typeName = fid.section( '.', 0, 0 );
559  fid = fid.section( '.', 1, 1 );
560  if ( !typeNameList.contains( typeName ) )
561  {
562  typeNameList << typeName;
563  }
564 
565  // each Feature requested by FEATUREID can have each own property list
566  // use colon that is replaced in typenames because typenames can be used
567  // as XML tag name
568  const QString key = QStringLiteral( "%1:%2" ).arg( typeName, propertyName );
569  QStringList fids;
570  if ( fidsMap.contains( key ) )
571  {
572  fids = fidsMap.value( key );
573  }
574  fids.append( fid );
575  fidsMap.insert( key, fids );
576 
577  if ( propertyNameIt != propertyNameList.constEnd() )
578  {
579  ++propertyNameIt;
580  }
581  }
582 
583  QMap<QString, QStringList>::const_iterator fidsMapIt = fidsMap.constBegin();
584  while ( fidsMapIt != fidsMap.constEnd() )
585  {
586  QString key = fidsMapIt.key();
587 
588  //Extract TypeName and PropertyName from key
589  // separated by colon
590  const QString typeName = key.section( ':', 0, 0 );
591  const QString propertyName = key.section( ':', 1, 1 );
592 
593  getFeatureQuery query;
594  query.typeName = typeName;
595  query.srsName = mWfsParameters.srsName();
596 
597  // Parse PropertyName
598  if ( propertyName != QLatin1String( "*" ) )
599  {
600  QStringList propertyList;
601 
602  const QStringList attrList = propertyName.split( ',' );
603  QStringList::const_iterator alstIt;
604  for ( alstIt = attrList.constBegin(); alstIt != attrList.constEnd(); ++alstIt )
605  {
606  QString fieldName = *alstIt;
607  fieldName = fieldName.trimmed();
608  if ( fieldName.contains( ':' ) )
609  {
610  fieldName = fieldName.section( ':', 1, 1 );
611  }
612  if ( fieldName.contains( '/' ) )
613  {
614  if ( fieldName.section( '/', 0, 0 ) != typeName )
615  {
616  throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
617  }
618  fieldName = fieldName.section( '/', 1, 1 );
619  }
620  propertyList.append( fieldName );
621  }
622  query.propertyList = propertyList;
623  }
624 
625  query.serverFids = fidsMapIt.value();
626  QgsFeatureRequest featureRequest;
627 
628  query.featureRequest = featureRequest;
629  request.queries.append( query );
630  ++fidsMapIt;
631  }
632  return request;
633  }
634 
635  if ( !mRequestParameters.contains( QStringLiteral( "TYPENAME" ) ) )
636  {
637  throw QgsRequestNotWellFormedException( QStringLiteral( "TYPENAME is mandatory except if FEATUREID is used" ) );
638  }
639 
640  typeNameList = mWfsParameters.typeNames();
641  // Verifying the 1:1 mapping between TYPENAME and PROPERTYNAME
642  if ( !propertyNameList.isEmpty() && typeNameList.size() != propertyNameList.size() )
643  {
644  throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the PROPERTYNAME list" ) );
645  }
646  if ( propertyNameList.isEmpty() )
647  {
648  for ( int i = 0; i < typeNameList.size(); ++i )
649  {
650  propertyNameList << QStringLiteral( "*" );
651  }
652  }
653 
654  // Create queries based on TypeName and propertyName
655  QStringList::const_iterator typeNameIt = typeNameList.constBegin();
656  QStringList::const_iterator propertyNameIt = propertyNameList.constBegin();
657  for ( ; typeNameIt != typeNameList.constEnd(); ++typeNameIt )
658  {
659  QString typeName = *typeNameIt;
660  typeName = typeName.trimmed();
661  // Get PropertyName for this typeName
662  QString propertyName;
663  if ( propertyNameIt != propertyNameList.constEnd() )
664  {
665  propertyName = *propertyNameIt;
666  }
667 
668  getFeatureQuery query;
669  query.typeName = typeName;
670  query.srsName = mWfsParameters.srsName();
671 
672  // Parse PropertyName
673  if ( propertyName != QLatin1String( "*" ) )
674  {
675  QStringList propertyList;
676 
677  const QStringList attrList = propertyName.split( ',' );
678  QStringList::const_iterator alstIt;
679  for ( alstIt = attrList.constBegin(); alstIt != attrList.constEnd(); ++alstIt )
680  {
681  QString fieldName = *alstIt;
682  fieldName = fieldName.trimmed();
683  if ( fieldName.contains( ':' ) )
684  {
685  fieldName = fieldName.section( ':', 1, 1 );
686  }
687  if ( fieldName.contains( '/' ) )
688  {
689  if ( fieldName.section( '/', 0, 0 ) != typeName )
690  {
691  throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
692  }
693  fieldName = fieldName.section( '/', 1, 1 );
694  }
695  propertyList.append( fieldName );
696  }
697  query.propertyList = propertyList;
698  }
699 
700  request.queries.append( query );
701 
702  if ( propertyNameIt != propertyNameList.constEnd() )
703  {
704  ++propertyNameIt;
705  }
706  }
707 
708  // Manage extra parameter exp_filter
709  QStringList expFilterList = mWfsParameters.expFilters();
710  if ( !expFilterList.isEmpty() )
711  {
712  // Verifying the 1:1 mapping between TYPENAME and EXP_FILTER but without exception
713  if ( request.queries.size() == expFilterList.size() )
714  {
715  // set feature request filter expression based on filter element
716  QList<getFeatureQuery>::iterator qIt = request.queries.begin();
717  QStringList::const_iterator expFilterIt = expFilterList.constBegin();
718  for ( ; qIt != request.queries.end(); ++qIt )
719  {
720  getFeatureQuery &query = *qIt;
721  // Get Filter for this typeName
722  const QString expFilter = *expFilterIt++;
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 
1226  // If requested SRS (srsName) is different from rect CRS (crs) we need to transform the envelope
1227  QgsCoordinateTransform transform;
1228  transform.setSourceCrs( crs );
1230  QgsRectangle crsCorrectedRect { rect ? *rect : QgsRectangle() };
1231 
1232  try
1233  {
1234  crsCorrectedRect = transform.transformBoundingBox( crsCorrectedRect );
1235  }
1236  catch ( QgsException &cse )
1237  {
1238  Q_UNUSED( cse )
1239  }
1240 
1241  QDomElement envElem = QgsOgcUtils::rectangleToGMLEnvelope( &crsCorrectedRect, doc, srsName, invertAxis, prec );
1242  if ( !envElem.isNull() )
1243  {
1244  if ( crs.isValid() && srsName.isEmpty() )
1245  {
1246  envElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1247  }
1248  bbElem.appendChild( envElem );
1249  doc.appendChild( bbElem );
1250  }
1251  }
1252  else
1253  {
1254  QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( rect, doc, prec );
1255  if ( !boxElem.isNull() )
1256  {
1257  if ( crs.isValid() )
1258  {
1259  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1260  }
1261  bbElem.appendChild( boxElem );
1262  doc.appendChild( bbElem );
1263  }
1264  }
1265  response.write( doc.toByteArray() );
1266  response.flush();
1267  }
1268  }
1269 
1270  void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
1271  const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1272  {
1273  if ( !feature.isValid() )
1274  return;
1275 
1276  if ( format == QgsWfsParameters::Format::GeoJSON )
1277  {
1278  QString fcString;
1279  if ( featIdx == 0 )
1280  fcString += QLatin1String( " " );
1281  else
1282  fcString += QLatin1String( " ," );
1283  mJsonExporter.setSourceCrs( params.crs );
1284  mJsonExporter.setIncludeGeometry( false );
1285  mJsonExporter.setIncludeAttributes( !params.attributeIndexes.isEmpty() );
1286  mJsonExporter.setAttributes( params.attributeIndexes );
1287  fcString += createFeatureGeoJSON( feature, params, pkAttributes );
1288  fcString += QLatin1String( "\n" );
1289 
1290  response.write( fcString.toUtf8() );
1291  }
1292  else
1293  {
1294  QDomDocument gmlDoc;
1295  QDomElement featureElement;
1296  if ( format == QgsWfsParameters::Format::GML3 )
1297  {
1298  featureElement = createFeatureGML3( feature, gmlDoc, params, project, pkAttributes );
1299  gmlDoc.appendChild( featureElement );
1300  }
1301  else
1302  {
1303  featureElement = createFeatureGML2( feature, gmlDoc, params, project, pkAttributes );
1304  gmlDoc.appendChild( featureElement );
1305  }
1306  response.write( gmlDoc.toByteArray() );
1307  }
1308 
1309  // Stream partial content
1310  response.flush();
1311  }
1312 
1313  void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format )
1314  {
1315  QString fcString;
1316  if ( format == QgsWfsParameters::Format::GeoJSON )
1317  {
1318  fcString += QLatin1String( " ]\n" );
1319  fcString += QLatin1Char( '}' );
1320  }
1321  else
1322  {
1323  fcString = QStringLiteral( "</wfs:FeatureCollection>\n" );
1324  }
1325  response.write( fcString.toUtf8() );
1326  }
1327 
1328 
1329  QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams &params, const QgsAttributeList &pkAttributes )
1330  {
1331  QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1332  //QgsJsonExporter force transform geometry to EPSG:4326
1333  //and the RFC 7946 GeoJSON specification recommends limiting coordinate precision to 6
1334  //Q_UNUSED( prec )
1335 
1336  //copy feature so we can modify its geometry as required
1337  QgsFeature f( feature );
1338  QgsGeometry geom = feature.geometry();
1339  if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1340  {
1341  mJsonExporter.setIncludeGeometry( true );
1342  if ( params.geometryName == QLatin1String( "EXTENT" ) )
1343  {
1344  QgsRectangle box = geom.boundingBox();
1345  f.setGeometry( QgsGeometry::fromRect( box ) );
1346  }
1347  else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1348  {
1349  f.setGeometry( geom.centroid() );
1350  }
1351  }
1352 
1353  return mJsonExporter.exportFeature( f, QVariantMap(), id );
1354  }
1355 
1356 
1357  QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1358  {
1359  //gml:FeatureMember
1360  QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1361 
1362  //qgs:%TYPENAME%
1363  QDomElement typeNameElement = doc.createElement( "qgs:" + params.typeName /*qgs:%TYPENAME%*/ );
1364  QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1365  typeNameElement.setAttribute( QStringLiteral( "fid" ), id );
1366  featureElement.appendChild( typeNameElement );
1367 
1368  //add geometry column (as gml)
1369  QgsGeometry geom = feature.geometry();
1370  if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1371  {
1372  int prec = params.precision;
1373  QgsCoordinateReferenceSystem crs = params.crs;
1374  QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1375  try
1376  {
1377  QgsGeometry transformed = geom;
1378  if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1379  {
1380  geom = transformed;
1381  crs = params.outputCrs;
1382  if ( crs.isGeographic() && !params.crs.isGeographic() )
1383  prec = std::min( params.precision + 3, 6 );
1384  }
1385  }
1386  catch ( QgsCsException &cse )
1387  {
1388  Q_UNUSED( cse )
1389  }
1390 
1391  QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1392  QDomElement gmlElem;
1393  QgsGeometry cloneGeom( geom );
1394  if ( params.geometryName == QLatin1String( "EXTENT" ) )
1395  {
1396  cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1397  }
1398  else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1399  {
1400  cloneGeom = geom.centroid();
1401  }
1402  else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1403  {
1404  cloneGeom.convertToMultiType();
1405  }
1406  const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1407  if ( abstractGeom )
1408  {
1409  gmlElem = abstractGeom->asGml2( doc, prec, "http://www.opengis.net/gml" );
1410  }
1411 
1412  if ( !gmlElem.isNull() )
1413  {
1414  QgsRectangle box = geom.boundingBox();
1415  QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1416  QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, prec );
1417 
1418  if ( crs.isValid() )
1419  {
1420  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1421  gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1422  }
1423 
1424  bbElem.appendChild( boxElem );
1425  typeNameElement.appendChild( bbElem );
1426 
1427  geomElem.appendChild( gmlElem );
1428  typeNameElement.appendChild( geomElem );
1429  }
1430  }
1431 
1432  //read all attribute values from the feature
1433  const QgsAttributes featureAttributes = feature.attributes();
1434  const QgsFields fields = feature.fields();
1435  for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1436  {
1437  int idx = params.attributeIndexes[i];
1438  if ( idx >= fields.count() )
1439  {
1440  continue;
1441  }
1442 
1443  const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
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( QStringLiteral( "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() && params.srsName.isEmpty() )
1512  {
1513  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1514  gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1515  }
1516  else if ( !params.srsName.isEmpty() )
1517  {
1518  gmlElem.setAttribute( QStringLiteral( "srsName" ), params.srsName );
1519  }
1520 
1521  bbElem.appendChild( boxElem );
1522  typeNameElement.appendChild( bbElem );
1523 
1524  geomElem.appendChild( gmlElem );
1525  typeNameElement.appendChild( geomElem );
1526  }
1527  }
1528 
1529  //read all attribute values from the feature
1530  const QgsAttributes featureAttributes = feature.attributes();
1531  const QgsFields fields = feature.fields();
1532  for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1533  {
1534  int idx = params.attributeIndexes[i];
1535  if ( idx >= fields.count() )
1536  {
1537  continue;
1538  }
1539 
1540  const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1541  typeNameElement.appendChild( fieldElem );
1542  }
1543 
1544  return featureElement;
1545  }
1546 
1547  QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc )
1548  {
1550  const QString attributeName = field.name().replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() );
1551  QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:" ) + attributeName );
1552  if ( value.isNull() )
1553  {
1554  fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
1555  }
1556  else
1557  {
1558  const QString fieldText = encodeValueToText( value, setup );
1559  //do we need CDATA
1560  if ( fieldText.indexOf( '<' ) != -1 || fieldText.indexOf( '&' ) != -1 )
1561  {
1562  fieldElem.appendChild( doc.createCDATASection( fieldText ) );
1563  }
1564  else
1565  {
1566  fieldElem.appendChild( doc.createTextNode( fieldText ) );
1567  }
1568  }
1569  return fieldElem;
1570  }
1571 
1572  QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup )
1573  {
1574  if ( value.isNull() )
1575  return QString();
1576 
1577  if ( setup.type() == QStringLiteral( "DateTime" ) )
1578  {
1579  const QVariantMap config = setup.config();
1580  const QString fieldFormat = config.value( QStringLiteral( "field_format" ), QgsDateTimeFieldFormatter::defaultFormat( value.type() ) ).toString();
1581  QDateTime date = value.toDateTime();
1582 
1583  if ( date.isValid() )
1584  {
1585  return date.toString( fieldFormat );
1586  }
1587  }
1588  else if ( setup.type() == QStringLiteral( "Range" ) )
1589  {
1590  const QVariantMap config = setup.config();
1591  if ( config.contains( QStringLiteral( "Precision" ) ) )
1592  {
1593  // if precision is defined, use it
1594  bool ok;
1595  int precision( config[ QStringLiteral( "Precision" ) ].toInt( &ok ) );
1596  if ( ok )
1597  return QString::number( value.toDouble(), 'f', precision );
1598  }
1599  }
1600 
1601  switch ( value.type() )
1602  {
1603  case QVariant::Int:
1604  case QVariant::UInt:
1605  case QVariant::LongLong:
1606  case QVariant::ULongLong:
1607  case QVariant::Double:
1608  return value.toString();
1609 
1610  case QVariant::Bool:
1611  return value.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" );
1612 
1613  case QVariant::StringList:
1614  case QVariant::List:
1615  case QVariant::Map:
1616  return QgsJsonUtils::encodeValue( value );
1617 
1618  default:
1619  case QVariant::String:
1620  return value.toString();
1621  }
1622  }
1623 
1624 
1625  } // namespace
1626 
1627 } // namespace QgsWfs
@ 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.
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.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
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
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
Field is not available if layer is served as WFS from QGIS server.
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition: qgsfield.cpp:602
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:349
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:104
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
@ VectorLayer
Vector layer.
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:2199
QMap< QString, QString > QgsStringMap
Definition: qgis.h:2776
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