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