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