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