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