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