QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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  QDomElement envElem = QgsOgcUtils::rectangleToGMLEnvelope( rect, doc, srsName, invertAxis, prec );
1226  if ( !envElem.isNull() )
1227  {
1228  if ( crs.isValid() && srsName.isEmpty() )
1229  {
1230  envElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1231  }
1232  bbElem.appendChild( envElem );
1233  doc.appendChild( bbElem );
1234  }
1235  }
1236  else
1237  {
1238  QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( rect, doc, prec );
1239  if ( !boxElem.isNull() )
1240  {
1241  if ( crs.isValid() )
1242  {
1243  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1244  }
1245  bbElem.appendChild( boxElem );
1246  doc.appendChild( bbElem );
1247  }
1248  }
1249  response.write( doc.toByteArray() );
1250  response.flush();
1251  }
1252  }
1253 
1254  void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
1255  const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1256  {
1257  if ( !feature.isValid() )
1258  return;
1259 
1260  if ( format == QgsWfsParameters::Format::GeoJSON )
1261  {
1262  QString fcString;
1263  if ( featIdx == 0 )
1264  fcString += QLatin1String( " " );
1265  else
1266  fcString += QLatin1String( " ," );
1267  mJsonExporter.setSourceCrs( params.crs );
1268  mJsonExporter.setIncludeGeometry( false );
1269  mJsonExporter.setIncludeAttributes( !params.attributeIndexes.isEmpty() );
1270  mJsonExporter.setAttributes( params.attributeIndexes );
1271  fcString += createFeatureGeoJSON( feature, params, pkAttributes );
1272  fcString += QLatin1String( "\n" );
1273 
1274  response.write( fcString.toUtf8() );
1275  }
1276  else
1277  {
1278  QDomDocument gmlDoc;
1279  QDomElement featureElement;
1280  if ( format == QgsWfsParameters::Format::GML3 )
1281  {
1282  featureElement = createFeatureGML3( feature, gmlDoc, params, project, pkAttributes );
1283  gmlDoc.appendChild( featureElement );
1284  }
1285  else
1286  {
1287  featureElement = createFeatureGML2( feature, gmlDoc, params, project, pkAttributes );
1288  gmlDoc.appendChild( featureElement );
1289  }
1290  response.write( gmlDoc.toByteArray() );
1291  }
1292 
1293  // Stream partial content
1294  response.flush();
1295  }
1296 
1297  void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format )
1298  {
1299  QString fcString;
1300  if ( format == QgsWfsParameters::Format::GeoJSON )
1301  {
1302  fcString += QLatin1String( " ]\n" );
1303  fcString += QLatin1Char( '}' );
1304  }
1305  else
1306  {
1307  fcString = QStringLiteral( "</wfs:FeatureCollection>\n" );
1308  }
1309  response.write( fcString.toUtf8() );
1310  }
1311 
1312 
1313  QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams &params, const QgsAttributeList &pkAttributes )
1314  {
1315  QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1316  //QgsJsonExporter force transform geometry to EPSG:4326
1317  //and the RFC 7946 GeoJSON specification recommends limiting coordinate precision to 6
1318  //Q_UNUSED( prec )
1319 
1320  //copy feature so we can modify its geometry as required
1321  QgsFeature f( feature );
1322  QgsGeometry geom = feature.geometry();
1323  if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1324  {
1325  mJsonExporter.setIncludeGeometry( true );
1326  if ( params.geometryName == QLatin1String( "EXTENT" ) )
1327  {
1328  QgsRectangle box = geom.boundingBox();
1329  f.setGeometry( QgsGeometry::fromRect( box ) );
1330  }
1331  else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1332  {
1333  f.setGeometry( geom.centroid() );
1334  }
1335  }
1336 
1337  return mJsonExporter.exportFeature( f, QVariantMap(), id );
1338  }
1339 
1340 
1341  QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1342  {
1343  //gml:FeatureMember
1344  QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1345 
1346  //qgs:%TYPENAME%
1347  QDomElement typeNameElement = doc.createElement( "qgs:" + params.typeName /*qgs:%TYPENAME%*/ );
1348  QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1349  typeNameElement.setAttribute( QStringLiteral( "fid" ), id );
1350  featureElement.appendChild( typeNameElement );
1351 
1352  //add geometry column (as gml)
1353  QgsGeometry geom = feature.geometry();
1354  if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1355  {
1356  int prec = params.precision;
1357  QgsCoordinateReferenceSystem crs = params.crs;
1358  QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1359  try
1360  {
1361  QgsGeometry transformed = geom;
1362  if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1363  {
1364  geom = transformed;
1365  crs = params.outputCrs;
1366  if ( crs.isGeographic() && !params.crs.isGeographic() )
1367  prec = std::min( params.precision + 3, 6 );
1368  }
1369  }
1370  catch ( QgsCsException &cse )
1371  {
1372  Q_UNUSED( cse )
1373  }
1374 
1375  QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1376  QDomElement gmlElem;
1377  QgsGeometry cloneGeom( geom );
1378  if ( params.geometryName == QLatin1String( "EXTENT" ) )
1379  {
1380  cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1381  }
1382  else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1383  {
1384  cloneGeom = geom.centroid();
1385  }
1386  else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1387  {
1388  cloneGeom.convertToMultiType();
1389  }
1390  const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1391  if ( abstractGeom )
1392  {
1393  gmlElem = abstractGeom->asGml2( doc, prec, "http://www.opengis.net/gml" );
1394  }
1395 
1396  if ( !gmlElem.isNull() )
1397  {
1398  QgsRectangle box = geom.boundingBox();
1399  QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1400  QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, prec );
1401 
1402  if ( crs.isValid() )
1403  {
1404  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1405  gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1406  }
1407 
1408  bbElem.appendChild( boxElem );
1409  typeNameElement.appendChild( bbElem );
1410 
1411  geomElem.appendChild( gmlElem );
1412  typeNameElement.appendChild( geomElem );
1413  }
1414  }
1415 
1416  //read all attribute values from the feature
1417  const QgsAttributes featureAttributes = feature.attributes();
1418  const QgsFields fields = feature.fields();
1419  for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1420  {
1421  int idx = params.attributeIndexes[i];
1422  if ( idx >= fields.count() )
1423  {
1424  continue;
1425  }
1426 
1427  const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1428  typeNameElement.appendChild( fieldElem );
1429  }
1430 
1431  return featureElement;
1432  }
1433 
1434  QDomElement createFeatureGML3( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1435  {
1436  //gml:FeatureMember
1437  QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1438 
1439  //qgs:%TYPENAME%
1440  QDomElement typeNameElement = doc.createElement( QStringLiteral( "qgs:" ) + params.typeName /*qgs:%TYPENAME%*/ );
1441  QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1442  typeNameElement.setAttribute( QStringLiteral( "gml:id" ), id );
1443  featureElement.appendChild( typeNameElement );
1444 
1445  //add geometry column (as gml)
1446  QgsGeometry geom = feature.geometry();
1447  if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1448  {
1449  int prec = params.precision;
1450  QgsCoordinateReferenceSystem crs = params.crs;
1451  QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1452  try
1453  {
1454  QgsGeometry transformed = geom;
1455  if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1456  {
1457  geom = transformed;
1458  crs = params.outputCrs;
1459  if ( crs.isGeographic() && !params.crs.isGeographic() )
1460  prec = std::min( params.precision + 3, 6 );
1461  }
1462  }
1463  catch ( QgsCsException &cse )
1464  {
1465  Q_UNUSED( cse )
1466  }
1467 
1468  QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1469  QDomElement gmlElem;
1470  QgsGeometry cloneGeom( geom );
1471  if ( params.geometryName == QLatin1String( "EXTENT" ) )
1472  {
1473  cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1474  }
1475  else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1476  {
1477  cloneGeom = geom.centroid();
1478  }
1479  else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1480  {
1481  cloneGeom.convertToMultiType();
1482  }
1483  const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1484  if ( abstractGeom )
1485  {
1486  gmlElem = abstractGeom->asGml3( doc, prec, "http://www.opengis.net/gml", params.hasAxisInverted ? QgsAbstractGeometry::AxisOrder::YX : QgsAbstractGeometry::AxisOrder::XY );
1487  }
1488 
1489  if ( !gmlElem.isNull() )
1490  {
1491  QgsRectangle box = geom.boundingBox();
1492  QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1493  QDomElement boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, params.srsName, params.hasAxisInverted, prec );
1494 
1495  if ( crs.isValid() && params.srsName.isEmpty() )
1496  {
1497  boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1498  gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1499  }
1500  else if ( !params.srsName.isEmpty() )
1501  {
1502  gmlElem.setAttribute( QStringLiteral( "srsName" ), params.srsName );
1503  }
1504 
1505  bbElem.appendChild( boxElem );
1506  typeNameElement.appendChild( bbElem );
1507 
1508  geomElem.appendChild( gmlElem );
1509  typeNameElement.appendChild( geomElem );
1510  }
1511  }
1512 
1513  //read all attribute values from the feature
1514  const QgsAttributes featureAttributes = feature.attributes();
1515  const QgsFields fields = feature.fields();
1516  for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1517  {
1518  int idx = params.attributeIndexes[i];
1519  if ( idx >= fields.count() )
1520  {
1521  continue;
1522  }
1523 
1524  const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1525  typeNameElement.appendChild( fieldElem );
1526  }
1527 
1528  return featureElement;
1529  }
1530 
1531  QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc )
1532  {
1534  const QString attributeName = field.name().replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() );
1535  QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:" ) + attributeName );
1536  if ( value.isNull() )
1537  {
1538  fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
1539  }
1540  else
1541  {
1542  const QString fieldText = encodeValueToText( value, setup );
1543  //do we need CDATA
1544  if ( fieldText.indexOf( '<' ) != -1 || fieldText.indexOf( '&' ) != -1 )
1545  {
1546  fieldElem.appendChild( doc.createCDATASection( fieldText ) );
1547  }
1548  else
1549  {
1550  fieldElem.appendChild( doc.createTextNode( fieldText ) );
1551  }
1552  }
1553  return fieldElem;
1554  }
1555 
1556  QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup )
1557  {
1558  if ( value.isNull() )
1559  return QString();
1560 
1561  if ( setup.type() == QStringLiteral( "DateTime" ) )
1562  {
1563  const QVariantMap config = setup.config();
1564  const QString fieldFormat = config.value( QStringLiteral( "field_format" ), QgsDateTimeFieldFormatter::defaultFormat( value.type() ) ).toString();
1565  QDateTime date = value.toDateTime();
1566 
1567  if ( date.isValid() )
1568  {
1569  return date.toString( fieldFormat );
1570  }
1571  }
1572  else if ( setup.type() == QStringLiteral( "Range" ) )
1573  {
1574  const QVariantMap config = setup.config();
1575  if ( config.contains( QStringLiteral( "Precision" ) ) )
1576  {
1577  // if precision is defined, use it
1578  bool ok;
1579  int precision( config[ QStringLiteral( "Precision" ) ].toInt( &ok ) );
1580  if ( ok )
1581  return QString::number( value.toDouble(), 'f', precision );
1582  }
1583  }
1584 
1585  switch ( value.type() )
1586  {
1587  case QVariant::Int:
1588  case QVariant::UInt:
1589  case QVariant::LongLong:
1590  case QVariant::ULongLong:
1591  case QVariant::Double:
1592  return value.toString();
1593 
1594  case QVariant::Bool:
1595  return value.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" );
1596 
1597  case QVariant::StringList:
1598  case QVariant::List:
1599  case QVariant::Map:
1600  return QgsJsonUtils::encodeValue( value );
1601 
1602  default:
1603  case QVariant::String:
1604  return value.toString();
1605  }
1606  }
1607 
1608 
1609  } // namespace
1610 
1611 } // 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.
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: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
@ 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:1530
QMap< QString, QString > QgsStringMap
Definition: qgis.h:2026
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