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