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