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