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