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