QGIS API Documentation 3.37.0-Master (614ebf5f6b2)
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
418 if ( !query.srsName.isEmpty() )
419 {
421 }
422
424
425 if ( !featureRequest.filterRect().isEmpty() )
426 {
427 QgsCoordinateTransform transform( outputCrs, vlayer->crs(), project );
428 try
429 {
430 featureRequest.setFilterRect( transform.transform( featureRequest.filterRect() ) );
431 }
432 catch ( QgsException &cse )
433 {
434 Q_UNUSED( cse )
435 }
436 if ( onlyOneLayer )
437 {
438 requestRect = featureRequest.filterRect();
439 }
440 }
441
442 // Iterate through features
443 QgsFeatureIterator fit = vlayer->getFeatures( featureRequest );
444
445 if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
446 {
447 while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
448 {
449 if ( accessControlRequest.filterType() != Qgis::FeatureRequestFilterType::NoFilter && !accessControlRequest.acceptFeature( feature ) )
450 {
451 continue;
452 }
453 if ( iteratedFeatures >= aRequest.startIndex )
454 {
455 ++sentFeatures;
456 }
457 ++iteratedFeatures;
458 }
459 }
460 else
461 {
462
463 // For WFS 1.1 we honor requested CRS and axis order
464 // Axis is not inverted if srsName starts with EPSG
465 // It needs to be an EPSG urn, e.g. urn:ogc:def:crs:EPSG::4326
466 // This follows geoserver convention
467 // See: https://docs.geoserver.org/stable/en/user/services/wfs/axis_order.html
468 // if the crs is defined in the parameters, use it
469 // otherwise:
470 // - geojson uses 'EPSG:4326' by default
471 // - other formats use the default CRS (DefaultSRS, which is the layer's CRS)
472 const QString requestSrsName = request.serverParameters().value( QStringLiteral( "SRSNAME" ) );
473 const QString srsName
474 {
475 !requestSrsName.isEmpty() ? requestSrsName :
476 ( aRequest.outputFormat == QgsWfsParameters::Format::GeoJSON ? QStringLiteral( "EPSG:4326" ) : outputCrs.authid() )
477 };
478 const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
480 ! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
481
482 const createFeatureParams cfp = { layerPrecision,
483 layerCrs,
484 attrIndexes,
485 typeName,
486 withGeom,
488 outputCrs,
490 srsName,
491 invertAxis
492 };
493 while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
494 {
495 if ( accessControlRequest.filterType() != Qgis::FeatureRequestFilterType::NoFilter && !accessControlRequest.acceptFeature( feature ) )
496 {
497 continue;
498 }
499 if ( iteratedFeatures == aRequest.startIndex )
500 startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList, serverIface->serverSettings() );
501
502 if ( iteratedFeatures >= aRequest.startIndex )
503 {
504 setGetFeature( response, aRequest.outputFormat, feature, sentFeatures, cfp, project, provider->pkAttributeIndexes() );
505 ++sentFeatures;
506 }
507 ++iteratedFeatures;
508 }
509 }
510 }
511
512#ifdef HAVE_SERVER_PYTHON_PLUGINS
513 //force restoration of original layer filters
514 filterRestorer.reset();
515#endif
516
517 if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
518 {
519 hitGetFeature( request, response, project, aRequest.outputFormat, sentFeatures, typeNameList, serverIface->serverSettings() );
520 }
521 else
522 {
523 // End of GetFeature
524 if ( iteratedFeatures <= aRequest.startIndex )
525 startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList, serverIface->serverSettings() );
526 endGetFeature( response, aRequest.outputFormat );
527 }
528
529 }
530
532 {
533 getFeatureRequest request;
534 request.maxFeatures = mWfsParameters.maxFeaturesAsInt();
535 request.startIndex = mWfsParameters.startIndexAsInt();
536 request.outputFormat = mWfsParameters.outputFormat();
537
538 // Verifying parameters mutually exclusive
539 QStringList fidList = mWfsParameters.featureIds();
540 bool paramContainsFeatureIds = !fidList.isEmpty();
541 QStringList filterList = mWfsParameters.filters();
542 bool paramContainsFilters = !filterList.isEmpty();
543 QString bbox = mWfsParameters.bbox();
544 bool paramContainsBbox = !bbox.isEmpty();
545 if ( ( paramContainsFeatureIds
546 && ( paramContainsFilters || paramContainsBbox ) )
547 || ( paramContainsFilters
548 && ( paramContainsFeatureIds || paramContainsBbox ) )
549 || ( paramContainsBbox
550 && ( paramContainsFeatureIds || paramContainsFilters ) )
551 )
552 {
553 throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID FILTER and BBOX parameters are mutually exclusive" ) );
554 }
555
556 // Get and split PROPERTYNAME parameter
557 QStringList propertyNameList = mWfsParameters.propertyNames();
558
559 // Manage extra parameter GeometryName
560 request.geometryName = mWfsParameters.geometryNameAsString().toUpper();
561
562 QStringList typeNameList;
563 // parse FEATUREID
564 if ( paramContainsFeatureIds )
565 {
566 // Verifying the 1:1 mapping between FEATUREID and PROPERTYNAME
567 if ( !propertyNameList.isEmpty() && propertyNameList.size() != fidList.size() )
568 {
569 throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a FEATUREID and the PROPERTYNAME list" ) );
570 }
571 if ( propertyNameList.isEmpty() )
572 {
573 for ( int i = 0; i < fidList.size(); ++i )
574 {
575 propertyNameList << QStringLiteral( "*" );
576 }
577 }
578
579 QMap<QString, QStringList> fidsMap;
580
581 QStringList::const_iterator fidIt = fidList.constBegin();
582 QStringList::const_iterator propertyNameIt = propertyNameList.constBegin();
583 for ( ; fidIt != fidList.constEnd(); ++fidIt )
584 {
585 // Get FeatureID
586 QString fid = *fidIt;
587 fid = fid.trimmed();
588 // Get PropertyName for this FeatureID
589 QString propertyName;
590 if ( propertyNameIt != propertyNameList.constEnd() )
591 {
592 propertyName = *propertyNameIt;
593 }
594 // testing typename in the WFS featureID
595 if ( !fid.contains( '.' ) )
596 {
597 throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID has to have TYPENAME in the values" ) );
598 }
599
600 QString typeName = fid.section( '.', 0, 0 );
601 fid = fid.section( '.', 1, 1 );
602 if ( !typeNameList.contains( typeName ) )
603 {
604 typeNameList << typeName;
605 }
606
607 // each Feature requested by FEATUREID can have each own property list
608 // use colon that is replaced in typenames because typenames can be used
609 // as XML tag name
610 const QString key = QStringLiteral( "%1:%2" ).arg( typeName, propertyName );
611 QStringList fids;
612 if ( fidsMap.contains( key ) )
613 {
614 fids = fidsMap.value( key );
615 }
616 fids.append( fid );
617 fidsMap.insert( key, fids );
618
619 if ( propertyNameIt != propertyNameList.constEnd() )
620 {
621 ++propertyNameIt;
622 }
623 }
624
625 QMap<QString, QStringList>::const_iterator fidsMapIt = fidsMap.constBegin();
626 while ( fidsMapIt != fidsMap.constEnd() )
627 {
628 QString key = fidsMapIt.key();
629
630 //Extract TypeName and PropertyName from key
631 // separated by colon
632 const QString typeName = key.section( ':', 0, 0 );
633 const QString propertyName = key.section( ':', 1, 1 );
634
635 getFeatureQuery query;
636 query.typeName = typeName;
637 query.srsName = mWfsParameters.srsName();
638
639 // Parse PropertyName
640 if ( propertyName != QLatin1String( "*" ) )
641 {
642 QStringList propertyList;
643
644 const QStringList attrList = propertyName.split( ',' );
645 QStringList::const_iterator alstIt;
646 for ( alstIt = attrList.constBegin(); alstIt != attrList.constEnd(); ++alstIt )
647 {
648 QString fieldName = *alstIt;
649 fieldName = fieldName.trimmed();
650 if ( fieldName.contains( ':' ) )
651 {
652 fieldName = fieldName.section( ':', 1, 1 );
653 }
654 if ( fieldName.contains( '/' ) )
655 {
656 if ( fieldName.section( '/', 0, 0 ) != typeName )
657 {
658 throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
659 }
660 fieldName = fieldName.section( '/', 1, 1 );
661 }
662 propertyList.append( fieldName );
663 }
664 query.propertyList = propertyList;
665 }
666
667 query.serverFids = fidsMapIt.value();
668 QgsFeatureRequest featureRequest;
669
670 query.featureRequest = featureRequest;
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
789 // get bbox extent
790 QgsRectangle extent = mWfsParameters.bboxAsRectangle();
791
792 QString extentSrsName { mWfsParameters.srsName() };
793
794 // handle WFS 1.1.0 optional CRS
795 if ( mWfsParameters.bbox().split( ',' ).size() == 5 && ! mWfsParameters.srsName().isEmpty() )
796 {
797 QString crs( mWfsParameters.bbox().split( ',' )[4] );
798 if ( crs != mWfsParameters.srsName() )
799 {
800 extentSrsName = crs;
802 QgsCoordinateReferenceSystem destinationCrs( mWfsParameters.srsName() );
803 if ( sourceCrs.isValid() && destinationCrs.isValid( ) )
804 {
805 QgsGeometry extentGeom = QgsGeometry::fromRect( extent );
806 QgsCoordinateTransform transform;
807 transform.setSourceCrs( sourceCrs );
808 transform.setDestinationCrs( destinationCrs );
809 try
810 {
811 if ( extentGeom.transform( transform ) == Qgis::GeometryOperationResult::Success )
812 {
813 extent = QgsRectangle( extentGeom.boundingBox() );
814 }
815 }
816 catch ( QgsException &cse )
817 {
818 Q_UNUSED( cse )
819 }
820 }
821 }
822 }
823
824 // Follow GeoServer conventions and handle axis order
825 // See: https://docs.geoserver.org/latest/en/user/services/wfs/axis_order.html#wfs-basics-axis
827 extentCrs.createFromUserInput( extentSrsName );
828 if ( extentCrs.isValid() && extentCrs.hasAxisInverted() && ! extentSrsName.startsWith( QLatin1String( "EPSG:" ) ) )
829 {
830 QgsGeometry geom { QgsGeometry::fromRect( extent ) };
831 geom.get()->swapXy();
832 extent = geom.boundingBox();
833 }
834
835 // set feature request filter rectangle
836 QList<getFeatureQuery>::iterator qIt = request.queries.begin();
837 for ( ; qIt != request.queries.end(); ++qIt )
838 {
839 getFeatureQuery &query = *qIt;
841 }
842 return request;
843 }
844 else if ( paramContainsFilters )
845 {
846 // Verifying the 1:1 mapping between TYPENAME and FILTER
847 if ( request.queries.size() != filterList.size() )
848 {
849 throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the FILTER list" ) );
850 }
851
852 // set feature request filter expression based on filter element
853 QList<getFeatureQuery>::iterator qIt = request.queries.begin();
854 QStringList::const_iterator filterIt = filterList.constBegin();
855 for ( ; qIt != request.queries.end(); ++qIt )
856 {
857 getFeatureQuery &query = *qIt;
858 // Get Filter for this typeName
859 QDomDocument filter;
860 if ( filterIt != filterList.constEnd() )
861 {
862 QString errorMsg;
863 if ( !filter.setContent( *filterIt, true, &errorMsg ) )
864 {
865 throw QgsRequestNotWellFormedException( QStringLiteral( "error message: %1. The XML string was: %2" ).arg( errorMsg, *filterIt ) );
866 }
867 }
868
869 QDomElement filterElem = filter.firstChildElement();
870 QStringList serverFids;
871 query.featureRequest = parseFilterElement( query.typeName, filterElem, serverFids, project );
872 query.serverFids = serverFids;
873
874 if ( filterIt != filterList.constEnd() )
875 {
876 ++filterIt;
877 }
878 }
879 return request;
880 }
881
882 QStringList sortByList = mWfsParameters.sortBy();
883 if ( !sortByList.isEmpty() && request.queries.size() == sortByList.size() )
884 {
885 // add order by to feature request
886 QList<getFeatureQuery>::iterator qIt = request.queries.begin();
887 QStringList::const_iterator sortByIt = sortByList.constBegin();
888 for ( ; qIt != request.queries.end(); ++qIt )
889 {
890 getFeatureQuery &query = *qIt;
891 // Get sortBy for this typeName
892 QString sortBy;
893 if ( sortByIt != sortByList.constEnd() )
894 {
895 sortBy = *sortByIt;
896 }
897 for ( const QString &attribute : sortBy.split( ',' ) )
898 {
899 if ( attribute.endsWith( QLatin1String( " D" ) ) || attribute.endsWith( QLatin1String( "+D" ) ) )
900 {
901 query.featureRequest.addOrderBy( attribute.left( attribute.size() - 2 ), false );
902 }
903 else if ( attribute.endsWith( QLatin1String( " DESC" ) ) || attribute.endsWith( QLatin1String( "+DESC" ) ) )
904 {
905 query.featureRequest.addOrderBy( attribute.left( attribute.size() - 5 ), false );
906 }
907 else if ( attribute.endsWith( QLatin1String( " A" ) ) || attribute.endsWith( QLatin1String( "+A" ) ) )
908 {
909 query.featureRequest.addOrderBy( attribute.left( attribute.size() - 2 ) );
910 }
911 else if ( attribute.endsWith( QLatin1String( " ASC" ) ) || attribute.endsWith( QLatin1String( "+ASC" ) ) )
912 {
913 query.featureRequest.addOrderBy( attribute.left( attribute.size() - 4 ) );
914 }
915 else
916 {
917 query.featureRequest.addOrderBy( attribute );
918 }
919 }
920 }
921 }
922
923 return request;
924 }
925
926 getFeatureRequest parseGetFeatureRequestBody( QDomElement &docElem, const QgsProject *project )
927 {
928 getFeatureRequest request;
929 request.maxFeatures = mWfsParameters.maxFeaturesAsInt();
930 request.startIndex = mWfsParameters.startIndexAsInt();
931 request.outputFormat = mWfsParameters.outputFormat();
932
933 QDomNodeList queryNodes = docElem.elementsByTagName( QStringLiteral( "Query" ) );
934 QDomElement queryElem;
935 for ( int i = 0; i < queryNodes.size(); i++ )
936 {
937 queryElem = queryNodes.at( i ).toElement();
938 getFeatureQuery query = parseQueryElement( queryElem, project );
939 request.queries.append( query );
940 }
941 return request;
942 }
943
944 void parseSortByElement( QDomElement &sortByElem, QgsFeatureRequest &featureRequest, const QString &typeName )
945 {
946 QDomNodeList sortByNodes = sortByElem.childNodes();
947 if ( sortByNodes.size() )
948 {
949 for ( int i = 0; i < sortByNodes.size(); i++ )
950 {
951 QDomElement sortPropElem = sortByNodes.at( i ).toElement();
952 QDomNodeList sortPropChildNodes = sortPropElem.childNodes();
953 if ( sortPropChildNodes.size() )
954 {
955 QString fieldName;
956 bool ascending = true;
957 for ( int j = 0; j < sortPropChildNodes.size(); j++ )
958 {
959 QDomElement sortPropChildElem = sortPropChildNodes.at( j ).toElement();
960 if ( sortPropChildElem.tagName() == QLatin1String( "PropertyName" ) )
961 {
962 fieldName = sortPropChildElem.text().trimmed();
963 }
964 else if ( sortPropChildElem.tagName() == QLatin1String( "SortOrder" ) )
965 {
966 QString sortOrder = sortPropChildElem.text().trimmed().toUpper();
967 if ( sortOrder == QLatin1String( "DESC" ) || sortOrder == QLatin1String( "D" ) )
968 ascending = false;
969 }
970 }
971 // clean fieldName
972 if ( fieldName.contains( ':' ) )
973 {
974 fieldName = fieldName.section( ':', 1, 1 );
975 }
976 if ( fieldName.contains( '/' ) )
977 {
978 if ( fieldName.section( '/', 0, 0 ) != typeName )
979 {
980 throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
981 }
982 fieldName = fieldName.section( '/', 1, 1 );
983 }
984 // addOrderBy
985 if ( !fieldName.isEmpty() )
986 featureRequest.addOrderBy( fieldName, ascending );
987 }
988 }
989 }
990 }
991
992 getFeatureQuery parseQueryElement( QDomElement &queryElem, const QgsProject *project )
993 {
994 QString typeName = queryElem.attribute( QStringLiteral( "typeName" ), QString() );
995 if ( typeName.contains( ':' ) )
996 {
997 typeName = typeName.section( ':', 1, 1 );
998 }
999
1000 QgsFeatureRequest featureRequest;
1001 QStringList serverFids;
1002 QStringList propertyList;
1003 QDomNodeList queryChildNodes = queryElem.childNodes();
1004 if ( queryChildNodes.size() )
1005 {
1006 QDomElement sortByElem;
1007 for ( int q = 0; q < queryChildNodes.size(); q++ )
1008 {
1009 QDomElement queryChildElem = queryChildNodes.at( q ).toElement();
1010 if ( queryChildElem.tagName() == QLatin1String( "PropertyName" ) )
1011 {
1012 QString fieldName = queryChildElem.text().trimmed();
1013 if ( fieldName.contains( ':' ) )
1014 {
1015 fieldName = fieldName.section( ':', 1, 1 );
1016 }
1017 if ( fieldName.contains( '/' ) )
1018 {
1019 if ( fieldName.section( '/', 0, 0 ) != typeName )
1020 {
1021 throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
1022 }
1023 fieldName = fieldName.section( '/', 1, 1 );
1024 }
1025 propertyList.append( fieldName );
1026 }
1027 else if ( queryChildElem.tagName() == QLatin1String( "Filter" ) )
1028 {
1029 featureRequest = parseFilterElement( typeName, queryChildElem, serverFids, project );
1030 }
1031 else if ( queryChildElem.tagName() == QLatin1String( "SortBy" ) )
1032 {
1033 sortByElem = queryChildElem;
1034 }
1035 }
1036 parseSortByElement( sortByElem, featureRequest, typeName );
1037 }
1038
1039 // srsName attribute
1040 QString srsName = queryElem.attribute( QStringLiteral( "srsName" ), QString() );
1041
1042 getFeatureQuery query;
1043 query.typeName = typeName;
1044 query.srsName = srsName;
1045 query.featureRequest = featureRequest;
1046 query.serverFids = serverFids;
1047 query.propertyList = propertyList;
1048 return query;
1049 }
1050
1051 namespace
1052 {
1053 static QSet< QString > sParamFilter
1054 {
1055 QStringLiteral( "REQUEST" ),
1056 QStringLiteral( "FORMAT" ),
1057 QStringLiteral( "OUTPUTFORMAT" ),
1058 QStringLiteral( "BBOX" ),
1059 QStringLiteral( "FEATUREID" ),
1060 QStringLiteral( "TYPENAME" ),
1061 QStringLiteral( "FILTER" ),
1062 QStringLiteral( "EXP_FILTER" ),
1063 QStringLiteral( "MAXFEATURES" ),
1064 QStringLiteral( "STARTINDEX" ),
1065 QStringLiteral( "PROPERTYNAME" ),
1066 QStringLiteral( "_DC" )
1067 };
1068
1069
1070 void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
1071 int numberOfFeatures, const QStringList &typeNames, const QgsServerSettings *settings )
1072 {
1073 QDateTime now = QDateTime::currentDateTime();
1074 QString fcString;
1075
1076 if ( format == QgsWfsParameters::Format::GeoJSON )
1077 {
1078 response.setHeader( "Content-Type", "application/vnd.geo+json; charset=utf-8" );
1079 fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
1080 fcString += QStringLiteral( " \"timeStamp\": \"%1\",\n" ).arg( now.toString( Qt::ISODate ) );
1081 fcString += QStringLiteral( " \"numberOfFeatures\": %1\n" ).arg( QString::number( numberOfFeatures ) );
1082 fcString += QLatin1Char( '}' );
1083 }
1084 else
1085 {
1086 if ( format == QgsWfsParameters::Format::GML2 )
1087 response.setHeader( "Content-Type", "text/xml; subtype=gml/2.1.2; charset=utf-8" );
1088 else
1089 response.setHeader( "Content-Type", "text/xml; subtype=gml/3.1.1; charset=utf-8" );
1090
1091 //Prepare url
1092 QString hrefString = serviceUrl( request, project, *settings );
1093
1094 QUrl mapUrl( hrefString );
1095
1096 QUrlQuery query( mapUrl );
1097 query.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
1098 //Set version
1099 if ( mWfsParameters.version().isEmpty() )
1100 query.addQueryItem( QStringLiteral( "VERSION" ), implementationVersion() );
1101 else if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1102 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.1.0" ) );
1103 else
1104 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
1105
1106 const auto constItems { query.queryItems() };
1107 for ( const auto &param : std::as_const( constItems ) )
1108 {
1109 if ( sParamFilter.contains( param.first.toUpper() ) )
1110 query.removeAllQueryItems( param.first );
1111 }
1112
1113 query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
1114 query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
1115 if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1116 {
1117 if ( format == QgsWfsParameters::Format::GML2 )
1118 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
1119 else
1120 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
1121 }
1122 else
1123 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );
1124
1125 mapUrl.setQuery( query );
1126
1127 hrefString = mapUrl.toString();
1128
1129 QString wfsSchema;
1130 if ( mWfsParameters.version().isEmpty() || mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1131 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" );
1132 else
1133 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
1134
1135 //wfs:FeatureCollection valid
1136 fcString = QStringLiteral( "<wfs:FeatureCollection" );
1137 fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
1138 fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
1139 fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
1140 fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
1141 fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
1142 fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
1143 fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
1144 fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " " + wfsSchema + " " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
1145 fcString += "\n timeStamp=\"" + now.toString( Qt::ISODate ) + "\"";
1146 fcString += "\n numberOfFeatures=\"" + QString::number( numberOfFeatures ) + "\"";
1147 fcString += QLatin1String( ">\n" );
1148 fcString += QLatin1String( "</wfs:FeatureCollection>" );
1149 }
1150
1151 response.write( fcString.toUtf8() );
1152 response.flush();
1153 }
1154
1155 void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
1156 int prec, QgsCoordinateReferenceSystem &crs, QgsRectangle *rect, const QStringList &typeNames, const QgsServerSettings *settings )
1157 {
1158 QString fcString;
1159
1160 std::unique_ptr< QgsRectangle > transformedRect;
1161
1162 if ( format == QgsWfsParameters::Format::GeoJSON )
1163 {
1164 response.setHeader( "Content-Type", "application/vnd.geo+json; charset=utf-8" );
1165
1166 if ( crs.isValid() && !rect->isEmpty() )
1167 {
1168 QgsGeometry exportGeom = QgsGeometry::fromRect( *rect );
1169 QgsCoordinateTransform transform;
1170 transform.setSourceCrs( crs );
1171 transform.setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
1172 try
1173 {
1174 if ( exportGeom.transform( transform ) == Qgis::GeometryOperationResult::Success )
1175 {
1176 transformedRect.reset( new QgsRectangle( exportGeom.boundingBox() ) );
1177 rect = transformedRect.get();
1178 }
1179 }
1180 catch ( QgsException &cse )
1181 {
1182 Q_UNUSED( cse )
1183 }
1184 }
1185 // EPSG:4326 max extent is -180, -90, 180, 90
1186 rect = new QgsRectangle( rect->intersect( QgsRectangle( -180.0, -90.0, 180.0, 90.0 ) ) );
1187
1188 fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
1189 fcString += " \"bbox\": [ " + qgsDoubleToString( rect->xMinimum(), prec ) + ", " + qgsDoubleToString( rect->yMinimum(), prec ) + ", " + qgsDoubleToString( rect->xMaximum(), prec ) + ", " + qgsDoubleToString( rect->yMaximum(), prec ) + "],\n";
1190
1191 const QString srsName {request.serverParameters().value( QStringLiteral( "SRSNAME" ) )};
1192 const QgsCoordinateReferenceSystem destinationCrs { srsName.isEmpty( ) ? QStringLiteral( "EPSG:4326" ) : srsName };
1193 if ( ! destinationCrs.isValid() )
1194 {
1195 throw QgsRequestNotWellFormedException( QStringLiteral( "srsName error: '%1' is not valid." ).arg( srsName ) );
1196 }
1197
1198 json value;
1199 QgsJsonUtils::addCrsInfo( value, destinationCrs );
1200 for ( const auto &it : value.items() )
1201 {
1202 fcString += " \"" + QString::fromStdString( it.key() ) + "\": " + QString::fromStdString( it.value().dump() ) + ",\n";
1203 }
1204
1205 fcString += QLatin1String( " \"features\": [\n" );
1206 response.write( fcString.toUtf8() );
1207 }
1208 else
1209 {
1210 if ( format == QgsWfsParameters::Format::GML2 )
1211 response.setHeader( "Content-Type", "text/xml; subtype=gml/2.1.2; charset=utf-8" );
1212 else
1213 response.setHeader( "Content-Type", "text/xml; subtype=gml/3.1.1; charset=utf-8" );
1214
1215 //Prepare url
1216 QString hrefString = serviceUrl( request, project, *settings );
1217
1218 QUrl mapUrl( hrefString );
1219
1220 QUrlQuery query( mapUrl );
1221 query.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
1222 //Set version
1223 if ( mWfsParameters.version().isEmpty() )
1224 query.addQueryItem( QStringLiteral( "VERSION" ), implementationVersion() );
1225 else if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1226 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.1.0" ) );
1227 else
1228 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
1229
1230 const auto queryItems {query.queryItems()};
1231 for ( auto param : std::as_const( queryItems ) )
1232 {
1233 if ( sParamFilter.contains( param.first.toUpper() ) )
1234 query.removeAllQueryItems( param.first );
1235 }
1236
1237 query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
1238 query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
1239 if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1240 {
1241 if ( format == QgsWfsParameters::Format::GML2 )
1242 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
1243 else
1244 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
1245 }
1246 else
1247 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );
1248
1249 mapUrl.setQuery( query );
1250
1251 hrefString = mapUrl.toString();
1252
1253 QString wfsSchema;
1254 if ( mWfsParameters.version().isEmpty() || mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1255 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" );
1256 else
1257 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
1258
1259 //wfs:FeatureCollection valid
1260 fcString = QStringLiteral( "<wfs:FeatureCollection" );
1261 fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
1262 fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
1263 fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
1264 fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
1265 fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
1266 fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
1267 fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
1268 fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " " + wfsSchema + " " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
1269 fcString += QLatin1String( ">\n" );
1270
1271 response.write( fcString.toUtf8() );
1272 response.flush();
1273
1274 QDomDocument doc;
1275 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1276 if ( format == QgsWfsParameters::Format::GML3 )
1277 {
1278 // For WFS 1.1 we honor requested CRS and axis order
1279 // Axis is not inverted if srsName starts with EPSG
1280 // It needs to be an EPSG urn, e.g. urn:ogc:def:crs:EPSG::4326
1281 // This follows geoserver convention
1282 // See: https://docs.geoserver.org/stable/en/user/services/wfs/axis_order.html
1283 const QString requestSrsName = request.serverParameters().value( QStringLiteral( "SRSNAME" ) );
1284 const QString srsName = !requestSrsName.isEmpty() ? requestSrsName : crs.authid();
1285 const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
1286 crs.hasAxisInverted() &&
1287 ! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
1288
1289 // If requested SRS (srsName) is different from rect CRS (crs) we need to transform the envelope
1290 QgsCoordinateTransform transform;
1291 transform.setSourceCrs( crs );
1293 QgsRectangle crsCorrectedRect { rect ? *rect : QgsRectangle() };
1294
1295 try
1296 {
1297 crsCorrectedRect = transform.transformBoundingBox( crsCorrectedRect );
1298 }
1299 catch ( QgsException &cse )
1300 {
1301 Q_UNUSED( cse )
1302 }
1303
1304 QDomElement envElem = QgsOgcUtils::rectangleToGMLEnvelope( &crsCorrectedRect, doc, srsName, invertAxis, prec );
1305 if ( !envElem.isNull() )
1306 {
1307 if ( crs.isValid() && srsName.isEmpty() )
1308 {
1309 envElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1310 }
1311 bbElem.appendChild( envElem );
1312 doc.appendChild( bbElem );
1313 }
1314 }
1315 else
1316 {
1317 QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( rect, doc, prec );
1318 if ( !boxElem.isNull() )
1319 {
1320 if ( crs.isValid() )
1321 {
1322 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1323 }
1324 bbElem.appendChild( boxElem );
1325 doc.appendChild( bbElem );
1326 }
1327 }
1328 response.write( doc.toByteArray() );
1329 response.flush();
1330 }
1331 }
1332
1333 void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
1334 const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1335 {
1336 if ( !feature.isValid() )
1337 return;
1338
1339 if ( format == QgsWfsParameters::Format::GeoJSON )
1340 {
1341 QString fcString;
1342 if ( featIdx == 0 )
1343 fcString += QLatin1String( " " );
1344 else
1345 fcString += QLatin1String( " ," );
1346
1347 const QgsCoordinateReferenceSystem destinationCrs { params.srsName.isEmpty( ) ? QStringLiteral( "EPSG:4326" ) : params.srsName };
1348 if ( ! destinationCrs.isValid() )
1349 {
1350 throw QgsRequestNotWellFormedException( QStringLiteral( "srsName error: '%1' is not valid." ).arg( params.srsName ) );
1351 }
1352
1353 mJsonExporter.setDestinationCrs( destinationCrs );
1354 mJsonExporter.setTransformGeometries( true );
1355 mJsonExporter.setSourceCrs( params.crs );
1356 mJsonExporter.setIncludeGeometry( false );
1357 mJsonExporter.setIncludeAttributes( !params.attributeIndexes.isEmpty() );
1358 mJsonExporter.setAttributes( params.attributeIndexes );
1359 fcString += createFeatureGeoJSON( feature, params, pkAttributes );
1360 fcString += QLatin1String( "\n" );
1361
1362 response.write( fcString.toUtf8() );
1363 }
1364 else
1365 {
1366 QDomDocument gmlDoc;
1367 QDomElement featureElement;
1368 if ( format == QgsWfsParameters::Format::GML3 )
1369 {
1370 featureElement = createFeatureGML3( feature, gmlDoc, params, project, pkAttributes );
1371 gmlDoc.appendChild( featureElement );
1372 }
1373 else
1374 {
1375 featureElement = createFeatureGML2( feature, gmlDoc, params, project, pkAttributes );
1376 gmlDoc.appendChild( featureElement );
1377 }
1378 response.write( gmlDoc.toByteArray() );
1379 }
1380
1381 // Stream partial content
1382 response.flush();
1383 }
1384
1385 void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format )
1386 {
1387 QString fcString;
1388 if ( format == QgsWfsParameters::Format::GeoJSON )
1389 {
1390 fcString += QLatin1String( " ]\n" );
1391 fcString += QLatin1Char( '}' );
1392 }
1393 else
1394 {
1395 fcString = QStringLiteral( "</wfs:FeatureCollection>\n" );
1396 }
1397 response.write( fcString.toUtf8() );
1398 }
1399
1400
1401 QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams &params, const QgsAttributeList &pkAttributes )
1402 {
1403 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1404 //QgsJsonExporter force transform geometry to EPSG:4326
1405 //and the RFC 7946 GeoJSON specification recommends limiting coordinate precision to 6
1406 //Q_UNUSED( prec )
1407
1408 //copy feature so we can modify its geometry as required
1409 QgsFeature f( feature );
1410 QgsGeometry geom = feature.geometry();
1411 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1412 {
1413 mJsonExporter.setIncludeGeometry( true );
1414 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1415 {
1416 QgsRectangle box = geom.boundingBox();
1417 f.setGeometry( QgsGeometry::fromRect( box ) );
1418 }
1419 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1420 {
1421 f.setGeometry( geom.centroid() );
1422 }
1423 }
1424
1425 return mJsonExporter.exportFeature( f, QVariantMap(), id );
1426 }
1427
1428
1429 QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1430 {
1431 //gml:FeatureMember
1432 QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1433
1434 //qgs:%TYPENAME%
1435 QDomElement typeNameElement = doc.createElement( "qgs:" + params.typeName /*qgs:%TYPENAME%*/ );
1436 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1437 typeNameElement.setAttribute( QStringLiteral( "fid" ), id );
1438 featureElement.appendChild( typeNameElement );
1439
1440 //add geometry column (as gml)
1441 QgsGeometry geom = feature.geometry();
1442 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1443 {
1444 int prec = params.precision;
1445 QgsCoordinateReferenceSystem crs = params.crs;
1446 QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1447 try
1448 {
1449 QgsGeometry transformed = geom;
1450 if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1451 {
1452 geom = transformed;
1453 crs = params.outputCrs;
1454 if ( crs.isGeographic() && !params.crs.isGeographic() )
1455 prec = std::min( params.precision + 3, 6 );
1456 }
1457 }
1458 catch ( QgsCsException &cse )
1459 {
1460 Q_UNUSED( cse )
1461 }
1462
1463 QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1464 QDomElement gmlElem;
1465 QgsGeometry cloneGeom( geom );
1466 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1467 {
1468 cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1469 }
1470 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1471 {
1472 cloneGeom = geom.centroid();
1473 }
1474 else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1475 {
1476 cloneGeom.convertToMultiType();
1477 }
1478 const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1479 if ( abstractGeom )
1480 {
1481 gmlElem = abstractGeom->asGml2( doc, prec, "http://www.opengis.net/gml" );
1482 }
1483
1484 if ( !gmlElem.isNull() )
1485 {
1486 QgsRectangle box = geom.boundingBox();
1487 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1488 QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, prec );
1489
1490 if ( crs.isValid() )
1491 {
1492 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1493 gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1494 }
1495
1496 bbElem.appendChild( boxElem );
1497 typeNameElement.appendChild( bbElem );
1498
1499 geomElem.appendChild( gmlElem );
1500 typeNameElement.appendChild( geomElem );
1501 }
1502 }
1503
1504 //read all attribute values from the feature
1505 const QgsAttributes featureAttributes = feature.attributes();
1506 const QgsFields fields = feature.fields();
1507 for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1508 {
1509 int idx = params.attributeIndexes[i];
1510 if ( idx >= fields.count() || QgsVariantUtils::isNull( featureAttributes[idx] ) )
1511 {
1512 continue;
1513 }
1514
1515 const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1516 typeNameElement.appendChild( fieldElem );
1517 }
1518
1519 return featureElement;
1520 }
1521
1522 QDomElement createFeatureGML3( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1523 {
1524 //gml:FeatureMember
1525 QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1526
1527 //qgs:%TYPENAME%
1528 QDomElement typeNameElement = doc.createElement( QStringLiteral( "qgs:" ) + params.typeName /*qgs:%TYPENAME%*/ );
1529 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1530 typeNameElement.setAttribute( QStringLiteral( "gml:id" ), id );
1531 featureElement.appendChild( typeNameElement );
1532
1533 //add geometry column (as gml)
1534 QgsGeometry geom = feature.geometry();
1535 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1536 {
1537 int prec = params.precision;
1538 QgsCoordinateReferenceSystem crs = params.crs;
1539 QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1540 try
1541 {
1542 QgsGeometry transformed = geom;
1543 if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1544 {
1545 geom = transformed;
1546 crs = params.outputCrs;
1547 if ( crs.isGeographic() && !params.crs.isGeographic() )
1548 prec = std::min( params.precision + 3, 6 );
1549 }
1550 }
1551 catch ( QgsCsException &cse )
1552 {
1553 Q_UNUSED( cse )
1554 }
1555
1556 QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1557 QDomElement gmlElem;
1558 QgsGeometry cloneGeom( geom );
1559 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1560 {
1561 cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1562 }
1563 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1564 {
1565 cloneGeom = geom.centroid();
1566 }
1567 else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1568 {
1569 cloneGeom.convertToMultiType();
1570 }
1571 const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1572 if ( abstractGeom )
1573 {
1574 gmlElem = abstractGeom->asGml3( doc, prec, "http://www.opengis.net/gml", params.hasAxisInverted ? QgsAbstractGeometry::AxisOrder::YX : QgsAbstractGeometry::AxisOrder::XY );
1575 }
1576
1577 if ( !gmlElem.isNull() )
1578 {
1579 QgsRectangle box = geom.boundingBox();
1580 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1581 QDomElement boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, params.srsName, params.hasAxisInverted, prec );
1582
1583 if ( crs.isValid() && params.srsName.isEmpty() )
1584 {
1585 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1586 gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1587 }
1588 else if ( !params.srsName.isEmpty() )
1589 {
1590 gmlElem.setAttribute( QStringLiteral( "srsName" ), params.srsName );
1591 }
1592
1593 bbElem.appendChild( boxElem );
1594 typeNameElement.appendChild( bbElem );
1595
1596 geomElem.appendChild( gmlElem );
1597 typeNameElement.appendChild( geomElem );
1598 }
1599 }
1600
1601 //read all attribute values from the feature
1602 const QgsAttributes featureAttributes = feature.attributes();
1603 const QgsFields fields = feature.fields();
1604 for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1605 {
1606 int idx = params.attributeIndexes[i];
1607 if ( idx >= fields.count() || QgsVariantUtils::isNull( featureAttributes[idx] ) )
1608 {
1609 continue;
1610 }
1611
1612 const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1613 typeNameElement.appendChild( fieldElem );
1614 }
1615
1616 return featureElement;
1617 }
1618
1619 QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc )
1620 {
1621 const QgsEditorWidgetSetup setup = field.editorWidgetSetup();
1622 const thread_local QRegularExpression sCleanTagNameRegExp( QStringLiteral( "[^\\w\\.-_]" ), QRegularExpression::PatternOption::UseUnicodePropertiesOption );
1623 const QString attributeName = field.name().replace( ' ', '_' ).replace( sCleanTagNameRegExp, QString() );
1624 QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:" ) + attributeName );
1625 if ( QgsVariantUtils::isNull( value ) )
1626 {
1627 fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
1628 }
1629 else
1630 {
1631 const QString fieldText = encodeValueToText( value, setup );
1632 //do we need CDATA
1633 if ( fieldText.indexOf( '<' ) != -1 || fieldText.indexOf( '&' ) != -1 )
1634 {
1635 fieldElem.appendChild( doc.createCDATASection( fieldText ) );
1636 }
1637 else
1638 {
1639 fieldElem.appendChild( doc.createTextNode( fieldText ) );
1640 }
1641 }
1642 return fieldElem;
1643 }
1644
1645 QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup )
1646 {
1647 if ( QgsVariantUtils::isNull( value ) )
1648 return QString();
1649
1650 if ( setup.type() == QStringLiteral( "DateTime" ) )
1651 {
1652 // For time fields use const TIME_FORMAT
1653 if ( value.userType() == QMetaType::Type::QTime )
1654 {
1655 return value.toTime().toString( QgsDateTimeFieldFormatter::TIME_FORMAT );
1656 }
1657
1658 // Get editor widget setup config
1659 const QVariantMap config = setup.config();
1660 // Get field format, for ISO format then use const display format
1661 // else use field format saved in editor widget setup config
1662 const QString fieldFormat =
1663 config.value( QStringLiteral( "field_iso_format" ), false ).toBool() ?
1665 config.value( QStringLiteral( "field_format" ), QgsDateTimeFieldFormatter::defaultFormat( static_cast<QMetaType::Type>( value.userType() ) ) ).toString();
1666
1667 // Convert value to date time
1668 QDateTime date = value.toDateTime();
1669 // if not valid try to convert to date with field format
1670 if ( !date.isValid() )
1671 {
1672 date = QDateTime::fromString( value.toString(), fieldFormat );
1673 }
1674 // if the date is valid, convert to string with field format
1675 if ( date.isValid() )
1676 {
1677 return date.toString( fieldFormat );
1678 }
1679 // else provide the value as string
1680 return value.toString();
1681 }
1682 else if ( setup.type() == QStringLiteral( "Range" ) )
1683 {
1684 const QVariantMap config = setup.config();
1685 if ( config.contains( QStringLiteral( "Precision" ) ) )
1686 {
1687 // if precision is defined, use it
1688 bool ok;
1689 int precision( config[ QStringLiteral( "Precision" ) ].toInt( &ok ) );
1690 if ( ok )
1691 return QString::number( value.toDouble(), 'f', precision );
1692 }
1693 }
1694
1695 switch ( value.userType() )
1696 {
1697 case QMetaType::Type::Int:
1698 case QMetaType::Type::UInt:
1699 case QMetaType::Type::LongLong:
1700 case QMetaType::Type::ULongLong:
1701 case QMetaType::Type::Double:
1702 return value.toString();
1703
1704 case QMetaType::Type::Bool:
1705 return value.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" );
1706
1707 case QMetaType::Type::QStringList:
1708 case QMetaType::Type::QVariantList:
1709 case QMetaType::Type::QVariantMap:
1710 return QgsJsonUtils::encodeValue( value );
1711
1712 default:
1713 case QMetaType::Type::QString:
1714 return value.toString();
1715 }
1716 }
1717
1718
1719 } // namespace
1720
1721} // 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).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
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:56
QgsAttributes attributes
Definition qgsfeature.h:65
QgsFields fields
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:67
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:739
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:60
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:75
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:82
QString id
Definition qgsmaplayer.h:78
Qgis::LayerType type
Definition qgsmaplayer.h:85
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() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
bool isEmpty() const
Returns true if the rectangle has no area.
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:5362
QMap< QString, QString > QgsStringMap
Definition qgis.h:5983
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