QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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() != QgsMapLayerType::VectorLayer )
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 {
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() == QgsWkbTypes::NoGeometry )
337 featureRequest.setFlags( featureRequest.flags() | QgsFeatureRequest::NoGeometry );
338 else
339 featureRequest.setFlags( featureRequest.flags() | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) );
340
341 // subset of attributes
342 featureRequest.setSubsetOfAttributes( attrIndexes );
343#ifdef HAVE_SERVER_PYTHON_PLUGINS
344 if ( accessControl )
345 {
346 accessControl->filterFeatures( vlayer, featureRequest );
347
348 QStringList attributes = QStringList();
349 for ( int idx : std::as_const( attrIndexes ) )
350 {
351 attributes.append( vlayer->fields().field( idx ).name() );
352 }
353 featureRequest.setSubsetOfAttributes(
354 accessControl->layerAttributes( vlayer, attributes ),
355 vlayer->fields() );
356 attrIndexes = featureRequest.subsetOfAttributes();
357 }
358#endif
359
360 // Force pkAttributes in subset of attributes for primary fid building
361 const QgsAttributeList pkAttributes = provider->pkAttributeIndexes();
362 if ( !pkAttributes.isEmpty() )
363 {
364 QgsAttributeList subsetOfAttrs = featureRequest.subsetOfAttributes();
365 for ( int idx : pkAttributes )
366 {
367 if ( !subsetOfAttrs.contains( idx ) )
368 {
369 subsetOfAttrs.prepend( idx );
370 }
371 }
372 if ( subsetOfAttrs.size() != featureRequest.subsetOfAttributes().size() )
373 {
374 featureRequest.setSubsetOfAttributes( subsetOfAttrs );
375 }
376 }
377
378 if ( onlyOneLayer )
379 {
380 requestPrecision = QgsServerProjectUtils::wfsLayerPrecision( *project, vlayer->id() );
381 }
382
383 if ( aRequest.maxFeatures > 0 )
384 {
385 featureRequest.setLimit( aRequest.maxFeatures + aRequest.startIndex - sentFeatures );
386 }
387 // specific layer precision
388 int layerPrecision = QgsServerProjectUtils::wfsLayerPrecision( *project, vlayer->id() );
389 // specific layer crs
390 QgsCoordinateReferenceSystem layerCrs = vlayer->crs();
391
392 // Geometry name
393 QString geometryName = aRequest.geometryName;
394 if ( !withGeom )
395 {
396 geometryName = QLatin1String( "NONE" );
397 }
398 // outputCrs
400 if ( !query.srsName.isEmpty() )
401 {
403 }
404
406
407 if ( !featureRequest.filterRect().isEmpty() )
408 {
409 QgsCoordinateTransform transform( outputCrs, vlayer->crs(), project );
410 try
411 {
412 featureRequest.setFilterRect( transform.transform( featureRequest.filterRect() ) );
413 }
414 catch ( QgsException &cse )
415 {
416 Q_UNUSED( cse )
417 }
418 if ( onlyOneLayer )
419 {
420 requestRect = featureRequest.filterRect();
421 }
422 }
423
424 // Iterate through features
425 QgsFeatureIterator fit = vlayer->getFeatures( featureRequest );
426
427 if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
428 {
429 while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
430 {
431 if ( iteratedFeatures >= aRequest.startIndex )
432 {
433 ++sentFeatures;
434 }
435 ++iteratedFeatures;
436 }
437 }
438 else
439 {
440
441 // For WFS 1.1 we honor requested CRS and axis order
442 const QString srsName {request.serverParameters().value( QStringLiteral( "SRSNAME" ) )};
443 const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
445 ! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
446
447 const createFeatureParams cfp = { layerPrecision,
448 layerCrs,
449 attrIndexes,
450 typeName,
451 withGeom,
453 outputCrs,
455 srsName,
456 invertAxis
457 };
458 while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
459 {
460 if ( iteratedFeatures == aRequest.startIndex )
461 startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList, serverIface->serverSettings() );
462
463 if ( iteratedFeatures >= aRequest.startIndex )
464 {
465 setGetFeature( response, aRequest.outputFormat, feature, sentFeatures, cfp, project, provider->pkAttributeIndexes() );
466 ++sentFeatures;
467 }
468 ++iteratedFeatures;
469 }
470 }
471 }
472
473#ifdef HAVE_SERVER_PYTHON_PLUGINS
474 //force restoration of original layer filters
475 filterRestorer.reset();
476#endif
477
478 if ( mWfsParameters.resultType() == QgsWfsParameters::ResultType::HITS )
479 {
480 hitGetFeature( request, response, project, aRequest.outputFormat, sentFeatures, typeNameList, serverIface->serverSettings() );
481 }
482 else
483 {
484 // End of GetFeature
485 if ( iteratedFeatures <= aRequest.startIndex )
486 startGetFeature( request, response, project, aRequest.outputFormat, requestPrecision, requestCrs, &requestRect, typeNameList, serverIface->serverSettings() );
487 endGetFeature( response, aRequest.outputFormat );
488 }
489
490 }
491
493 {
494 getFeatureRequest request;
495 request.maxFeatures = mWfsParameters.maxFeaturesAsInt();
496 request.startIndex = mWfsParameters.startIndexAsInt();
497 request.outputFormat = mWfsParameters.outputFormat();
498
499 // Verifying parameters mutually exclusive
500 QStringList fidList = mWfsParameters.featureIds();
501 bool paramContainsFeatureIds = !fidList.isEmpty();
502 QStringList filterList = mWfsParameters.filters();
503 bool paramContainsFilters = !filterList.isEmpty();
504 QString bbox = mWfsParameters.bbox();
505 bool paramContainsBbox = !bbox.isEmpty();
506 if ( ( paramContainsFeatureIds
507 && ( paramContainsFilters || paramContainsBbox ) )
508 || ( paramContainsFilters
509 && ( paramContainsFeatureIds || paramContainsBbox ) )
510 || ( paramContainsBbox
511 && ( paramContainsFeatureIds || paramContainsFilters ) )
512 )
513 {
514 throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID FILTER and BBOX parameters are mutually exclusive" ) );
515 }
516
517 // Get and split PROPERTYNAME parameter
518 QStringList propertyNameList = mWfsParameters.propertyNames();
519
520 // Manage extra parameter GeometryName
521 request.geometryName = mWfsParameters.geometryNameAsString().toUpper();
522
523 QStringList typeNameList;
524 // parse FEATUREID
525 if ( paramContainsFeatureIds )
526 {
527 // Verifying the 1:1 mapping between FEATUREID and PROPERTYNAME
528 if ( !propertyNameList.isEmpty() && propertyNameList.size() != fidList.size() )
529 {
530 throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a FEATUREID and the PROPERTYNAME list" ) );
531 }
532 if ( propertyNameList.isEmpty() )
533 {
534 for ( int i = 0; i < fidList.size(); ++i )
535 {
536 propertyNameList << QStringLiteral( "*" );
537 }
538 }
539
540 QMap<QString, QStringList> fidsMap;
541
542 QStringList::const_iterator fidIt = fidList.constBegin();
543 QStringList::const_iterator propertyNameIt = propertyNameList.constBegin();
544 for ( ; fidIt != fidList.constEnd(); ++fidIt )
545 {
546 // Get FeatureID
547 QString fid = *fidIt;
548 fid = fid.trimmed();
549 // Get PropertyName for this FeatureID
550 QString propertyName;
551 if ( propertyNameIt != propertyNameList.constEnd() )
552 {
553 propertyName = *propertyNameIt;
554 }
555 // testing typename in the WFS featureID
556 if ( !fid.contains( '.' ) )
557 {
558 throw QgsRequestNotWellFormedException( QStringLiteral( "FEATUREID has to have TYPENAME in the values" ) );
559 }
560
561 QString typeName = fid.section( '.', 0, 0 );
562 fid = fid.section( '.', 1, 1 );
563 if ( !typeNameList.contains( typeName ) )
564 {
565 typeNameList << typeName;
566 }
567
568 // each Feature requested by FEATUREID can have each own property list
569 // use colon that is replaced in typenames because typenames can be used
570 // as XML tag name
571 const QString key = QStringLiteral( "%1:%2" ).arg( typeName, propertyName );
572 QStringList fids;
573 if ( fidsMap.contains( key ) )
574 {
575 fids = fidsMap.value( key );
576 }
577 fids.append( fid );
578 fidsMap.insert( key, fids );
579
580 if ( propertyNameIt != propertyNameList.constEnd() )
581 {
582 ++propertyNameIt;
583 }
584 }
585
586 QMap<QString, QStringList>::const_iterator fidsMapIt = fidsMap.constBegin();
587 while ( fidsMapIt != fidsMap.constEnd() )
588 {
589 QString key = fidsMapIt.key();
590
591 //Extract TypeName and PropertyName from key
592 // separated by colon
593 const QString typeName = key.section( ':', 0, 0 );
594 const QString propertyName = key.section( ':', 1, 1 );
595
596 getFeatureQuery query;
597 query.typeName = typeName;
598 query.srsName = mWfsParameters.srsName();
599
600 // Parse PropertyName
601 if ( propertyName != QLatin1String( "*" ) )
602 {
603 QStringList propertyList;
604
605 const QStringList attrList = propertyName.split( ',' );
606 QStringList::const_iterator alstIt;
607 for ( alstIt = attrList.constBegin(); alstIt != attrList.constEnd(); ++alstIt )
608 {
609 QString fieldName = *alstIt;
610 fieldName = fieldName.trimmed();
611 if ( fieldName.contains( ':' ) )
612 {
613 fieldName = fieldName.section( ':', 1, 1 );
614 }
615 if ( fieldName.contains( '/' ) )
616 {
617 if ( fieldName.section( '/', 0, 0 ) != typeName )
618 {
619 throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
620 }
621 fieldName = fieldName.section( '/', 1, 1 );
622 }
623 propertyList.append( fieldName );
624 }
625 query.propertyList = propertyList;
626 }
627
628 query.serverFids = fidsMapIt.value();
629 QgsFeatureRequest featureRequest;
630
631 query.featureRequest = featureRequest;
632 request.queries.append( query );
633 ++fidsMapIt;
634 }
635 return request;
636 }
637
638 if ( !mRequestParameters.contains( QStringLiteral( "TYPENAME" ) ) )
639 {
640 throw QgsRequestNotWellFormedException( QStringLiteral( "TYPENAME is mandatory except if FEATUREID is used" ) );
641 }
642
643 typeNameList = mWfsParameters.typeNames();
644 // Verifying the 1:1 mapping between TYPENAME and PROPERTYNAME
645 if ( !propertyNameList.isEmpty() && typeNameList.size() != propertyNameList.size() )
646 {
647 throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the PROPERTYNAME list" ) );
648 }
649 if ( propertyNameList.isEmpty() )
650 {
651 for ( int i = 0; i < typeNameList.size(); ++i )
652 {
653 propertyNameList << QStringLiteral( "*" );
654 }
655 }
656
657 // Create queries based on TypeName and propertyName
658 QStringList::const_iterator typeNameIt = typeNameList.constBegin();
659 QStringList::const_iterator propertyNameIt = propertyNameList.constBegin();
660 for ( ; typeNameIt != typeNameList.constEnd(); ++typeNameIt )
661 {
662 QString typeName = *typeNameIt;
663 typeName = typeName.trimmed();
664 // Get PropertyName for this typeName
665 QString propertyName;
666 if ( propertyNameIt != propertyNameList.constEnd() )
667 {
668 propertyName = *propertyNameIt;
669 }
670
671 getFeatureQuery query;
672 query.typeName = typeName;
673 query.srsName = mWfsParameters.srsName();
674
675 // Parse PropertyName
676 if ( propertyName != QLatin1String( "*" ) )
677 {
678 QStringList propertyList;
679
680 const QStringList attrList = propertyName.split( ',' );
681 QStringList::const_iterator alstIt;
682 for ( alstIt = attrList.constBegin(); alstIt != attrList.constEnd(); ++alstIt )
683 {
684 QString fieldName = *alstIt;
685 fieldName = fieldName.trimmed();
686 if ( fieldName.contains( ':' ) )
687 {
688 fieldName = fieldName.section( ':', 1, 1 );
689 }
690 if ( fieldName.contains( '/' ) )
691 {
692 if ( fieldName.section( '/', 0, 0 ) != typeName )
693 {
694 throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
695 }
696 fieldName = fieldName.section( '/', 1, 1 );
697 }
698 propertyList.append( fieldName );
699 }
700 query.propertyList = propertyList;
701 }
702
703 request.queries.append( query );
704
705 if ( propertyNameIt != propertyNameList.constEnd() )
706 {
707 ++propertyNameIt;
708 }
709 }
710
711 // Manage extra parameter exp_filter
712 QStringList expFilterList = mWfsParameters.expFilters();
713 if ( !expFilterList.isEmpty() )
714 {
715 // Verifying the 1:1 mapping between TYPENAME and EXP_FILTER but without exception
716 if ( request.queries.size() == expFilterList.size() )
717 {
718 // set feature request filter expression based on filter element
719 QList<getFeatureQuery>::iterator qIt = request.queries.begin();
720 QStringList::const_iterator expFilterIt = expFilterList.constBegin();
721 for ( ; qIt != request.queries.end(); ++qIt )
722 {
723 getFeatureQuery &query = *qIt;
724 // Get Filter for this typeName
725 const QString expFilter = *expFilterIt++;
726 std::shared_ptr<QgsExpression> filter( new QgsExpression( expFilter ) );
727 if ( filter )
728 {
729 if ( filter->hasParserError() )
730 {
731 throw QgsRequestNotWellFormedException( QStringLiteral( "The EXP_FILTER expression has errors: %1" ).arg( filter->parserErrorString() ) );
732 }
733 if ( filter->needsGeometry() )
734 {
736 }
737 query.featureRequest.setFilterExpression( filter->expression() );
738 }
739 }
740 }
741 else
742 {
743 QgsMessageLog::logMessage( "There has to be a 1:1 mapping between each element in a TYPENAME and the EXP_FILTER list" );
744 }
745 }
746
747 if ( paramContainsBbox )
748 {
749
750 // get bbox extent
751 QgsRectangle extent = mWfsParameters.bboxAsRectangle();
752
753 QString extentSrsName { mWfsParameters.srsName() };
754
755 // handle WFS 1.1.0 optional CRS
756 if ( mWfsParameters.bbox().split( ',' ).size() == 5 && ! mWfsParameters.srsName().isEmpty() )
757 {
758 QString crs( mWfsParameters.bbox().split( ',' )[4] );
759 if ( crs != mWfsParameters.srsName() )
760 {
761 extentSrsName = crs;
763 QgsCoordinateReferenceSystem destinationCrs( mWfsParameters.srsName() );
764 if ( sourceCrs.isValid() && destinationCrs.isValid( ) )
765 {
766 QgsGeometry extentGeom = QgsGeometry::fromRect( extent );
767 QgsCoordinateTransform transform;
768 transform.setSourceCrs( sourceCrs );
769 transform.setDestinationCrs( destinationCrs );
770 try
771 {
772 if ( extentGeom.transform( transform ) == Qgis::GeometryOperationResult::Success )
773 {
774 extent = QgsRectangle( extentGeom.boundingBox() );
775 }
776 }
777 catch ( QgsException &cse )
778 {
779 Q_UNUSED( cse )
780 }
781 }
782 }
783 }
784
785 // Follow GeoServer conventions and handle axis order
786 // See: https://docs.geoserver.org/latest/en/user/services/wfs/axis_order.html#wfs-basics-axis
788 extentCrs.createFromUserInput( extentSrsName );
789 if ( extentCrs.isValid() && extentCrs.hasAxisInverted() && ! extentSrsName.startsWith( QLatin1String( "EPSG:" ) ) )
790 {
791 QgsGeometry geom { QgsGeometry::fromRect( extent ) };
792 geom.get()->swapXy();
793 extent = geom.boundingBox();
794 }
795
796 // set feature request filter rectangle
797 QList<getFeatureQuery>::iterator qIt = request.queries.begin();
798 for ( ; qIt != request.queries.end(); ++qIt )
799 {
800 getFeatureQuery &query = *qIt;
802 }
803 return request;
804 }
805 else if ( paramContainsFilters )
806 {
807 // Verifying the 1:1 mapping between TYPENAME and FILTER
808 if ( request.queries.size() != filterList.size() )
809 {
810 throw QgsRequestNotWellFormedException( QStringLiteral( "There has to be a 1:1 mapping between each element in a TYPENAME and the FILTER list" ) );
811 }
812
813 // set feature request filter expression based on filter element
814 QList<getFeatureQuery>::iterator qIt = request.queries.begin();
815 QStringList::const_iterator filterIt = filterList.constBegin();
816 for ( ; qIt != request.queries.end(); ++qIt )
817 {
818 getFeatureQuery &query = *qIt;
819 // Get Filter for this typeName
820 QDomDocument filter;
821 if ( filterIt != filterList.constEnd() )
822 {
823 QString errorMsg;
824 if ( !filter.setContent( *filterIt, true, &errorMsg ) )
825 {
826 throw QgsRequestNotWellFormedException( QStringLiteral( "error message: %1. The XML string was: %2" ).arg( errorMsg, *filterIt ) );
827 }
828 }
829
830 QDomElement filterElem = filter.firstChildElement();
831 QStringList serverFids;
832 query.featureRequest = parseFilterElement( query.typeName, filterElem, serverFids, project );
833 query.serverFids = serverFids;
834
835 if ( filterIt != filterList.constEnd() )
836 {
837 ++filterIt;
838 }
839 }
840 return request;
841 }
842
843 QStringList sortByList = mWfsParameters.sortBy();
844 if ( !sortByList.isEmpty() && request.queries.size() == sortByList.size() )
845 {
846 // add order by to feature request
847 QList<getFeatureQuery>::iterator qIt = request.queries.begin();
848 QStringList::const_iterator sortByIt = sortByList.constBegin();
849 for ( ; qIt != request.queries.end(); ++qIt )
850 {
851 getFeatureQuery &query = *qIt;
852 // Get sortBy for this typeName
853 QString sortBy;
854 if ( sortByIt != sortByList.constEnd() )
855 {
856 sortBy = *sortByIt;
857 }
858 for ( const QString &attribute : sortBy.split( ',' ) )
859 {
860 if ( attribute.endsWith( QLatin1String( " D" ) ) || attribute.endsWith( QLatin1String( "+D" ) ) )
861 {
862 query.featureRequest.addOrderBy( attribute.left( attribute.size() - 2 ), false );
863 }
864 else if ( attribute.endsWith( QLatin1String( " DESC" ) ) || attribute.endsWith( QLatin1String( "+DESC" ) ) )
865 {
866 query.featureRequest.addOrderBy( attribute.left( attribute.size() - 5 ), false );
867 }
868 else if ( attribute.endsWith( QLatin1String( " A" ) ) || attribute.endsWith( QLatin1String( "+A" ) ) )
869 {
870 query.featureRequest.addOrderBy( attribute.left( attribute.size() - 2 ) );
871 }
872 else if ( attribute.endsWith( QLatin1String( " ASC" ) ) || attribute.endsWith( QLatin1String( "+ASC" ) ) )
873 {
874 query.featureRequest.addOrderBy( attribute.left( attribute.size() - 4 ) );
875 }
876 else
877 {
878 query.featureRequest.addOrderBy( attribute );
879 }
880 }
881 }
882 }
883
884 return request;
885 }
886
887 getFeatureRequest parseGetFeatureRequestBody( QDomElement &docElem, const QgsProject *project )
888 {
889 getFeatureRequest request;
890 request.maxFeatures = mWfsParameters.maxFeaturesAsInt();
891 request.startIndex = mWfsParameters.startIndexAsInt();
892 request.outputFormat = mWfsParameters.outputFormat();
893
894 QDomNodeList queryNodes = docElem.elementsByTagName( QStringLiteral( "Query" ) );
895 QDomElement queryElem;
896 for ( int i = 0; i < queryNodes.size(); i++ )
897 {
898 queryElem = queryNodes.at( i ).toElement();
899 getFeatureQuery query = parseQueryElement( queryElem, project );
900 request.queries.append( query );
901 }
902 return request;
903 }
904
905 void parseSortByElement( QDomElement &sortByElem, QgsFeatureRequest &featureRequest, const QString &typeName )
906 {
907 QDomNodeList sortByNodes = sortByElem.childNodes();
908 if ( sortByNodes.size() )
909 {
910 for ( int i = 0; i < sortByNodes.size(); i++ )
911 {
912 QDomElement sortPropElem = sortByNodes.at( i ).toElement();
913 QDomNodeList sortPropChildNodes = sortPropElem.childNodes();
914 if ( sortPropChildNodes.size() )
915 {
916 QString fieldName;
917 bool ascending = true;
918 for ( int j = 0; j < sortPropChildNodes.size(); j++ )
919 {
920 QDomElement sortPropChildElem = sortPropChildNodes.at( j ).toElement();
921 if ( sortPropChildElem.tagName() == QLatin1String( "PropertyName" ) )
922 {
923 fieldName = sortPropChildElem.text().trimmed();
924 }
925 else if ( sortPropChildElem.tagName() == QLatin1String( "SortOrder" ) )
926 {
927 QString sortOrder = sortPropChildElem.text().trimmed().toUpper();
928 if ( sortOrder == QLatin1String( "DESC" ) || sortOrder == QLatin1String( "D" ) )
929 ascending = false;
930 }
931 }
932 // clean fieldName
933 if ( fieldName.contains( ':' ) )
934 {
935 fieldName = fieldName.section( ':', 1, 1 );
936 }
937 if ( fieldName.contains( '/' ) )
938 {
939 if ( fieldName.section( '/', 0, 0 ) != typeName )
940 {
941 throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
942 }
943 fieldName = fieldName.section( '/', 1, 1 );
944 }
945 // addOrderBy
946 if ( !fieldName.isEmpty() )
947 featureRequest.addOrderBy( fieldName, ascending );
948 }
949 }
950 }
951 }
952
953 getFeatureQuery parseQueryElement( QDomElement &queryElem, const QgsProject *project )
954 {
955 QString typeName = queryElem.attribute( QStringLiteral( "typeName" ), QString() );
956 if ( typeName.contains( ':' ) )
957 {
958 typeName = typeName.section( ':', 1, 1 );
959 }
960
961 QgsFeatureRequest featureRequest;
962 QStringList serverFids;
963 QStringList propertyList;
964 QDomNodeList queryChildNodes = queryElem.childNodes();
965 if ( queryChildNodes.size() )
966 {
967 QDomElement sortByElem;
968 for ( int q = 0; q < queryChildNodes.size(); q++ )
969 {
970 QDomElement queryChildElem = queryChildNodes.at( q ).toElement();
971 if ( queryChildElem.tagName() == QLatin1String( "PropertyName" ) )
972 {
973 QString fieldName = queryChildElem.text().trimmed();
974 if ( fieldName.contains( ':' ) )
975 {
976 fieldName = fieldName.section( ':', 1, 1 );
977 }
978 if ( fieldName.contains( '/' ) )
979 {
980 if ( fieldName.section( '/', 0, 0 ) != typeName )
981 {
982 throw QgsRequestNotWellFormedException( QStringLiteral( "PropertyName text '%1' has to contain TypeName '%2'" ).arg( fieldName ).arg( typeName ) );
983 }
984 fieldName = fieldName.section( '/', 1, 1 );
985 }
986 propertyList.append( fieldName );
987 }
988 else if ( queryChildElem.tagName() == QLatin1String( "Filter" ) )
989 {
990 featureRequest = parseFilterElement( typeName, queryChildElem, serverFids, project );
991 }
992 else if ( queryChildElem.tagName() == QLatin1String( "SortBy" ) )
993 {
994 sortByElem = queryChildElem;
995 }
996 }
997 parseSortByElement( sortByElem, featureRequest, typeName );
998 }
999
1000 // srsName attribute
1001 QString srsName = queryElem.attribute( QStringLiteral( "srsName" ), QString() );
1002
1003 getFeatureQuery query;
1004 query.typeName = typeName;
1005 query.srsName = srsName;
1006 query.featureRequest = featureRequest;
1007 query.serverFids = serverFids;
1008 query.propertyList = propertyList;
1009 return query;
1010 }
1011
1012 namespace
1013 {
1014 static QSet< QString > sParamFilter
1015 {
1016 QStringLiteral( "REQUEST" ),
1017 QStringLiteral( "FORMAT" ),
1018 QStringLiteral( "OUTPUTFORMAT" ),
1019 QStringLiteral( "BBOX" ),
1020 QStringLiteral( "FEATUREID" ),
1021 QStringLiteral( "TYPENAME" ),
1022 QStringLiteral( "FILTER" ),
1023 QStringLiteral( "EXP_FILTER" ),
1024 QStringLiteral( "MAXFEATURES" ),
1025 QStringLiteral( "STARTINDEX" ),
1026 QStringLiteral( "PROPERTYNAME" ),
1027 QStringLiteral( "_DC" )
1028 };
1029
1030
1031 void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
1032 int numberOfFeatures, const QStringList &typeNames, const QgsServerSettings *settings )
1033 {
1034 QDateTime now = QDateTime::currentDateTime();
1035 QString fcString;
1036
1037 if ( format == QgsWfsParameters::Format::GeoJSON )
1038 {
1039 response.setHeader( "Content-Type", "application/vnd.geo+json; charset=utf-8" );
1040 fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
1041 fcString += QStringLiteral( " \"timeStamp\": \"%1\",\n" ).arg( now.toString( Qt::ISODate ) );
1042 fcString += QStringLiteral( " \"numberOfFeatures\": %1\n" ).arg( QString::number( numberOfFeatures ) );
1043 fcString += QLatin1Char( '}' );
1044 }
1045 else
1046 {
1047 if ( format == QgsWfsParameters::Format::GML2 )
1048 response.setHeader( "Content-Type", "text/xml; subtype=gml/2.1.2; charset=utf-8" );
1049 else
1050 response.setHeader( "Content-Type", "text/xml; subtype=gml/3.1.1; charset=utf-8" );
1051
1052 //Prepare url
1053 QString hrefString = serviceUrl( request, project, *settings );
1054
1055 QUrl mapUrl( hrefString );
1056
1057 QUrlQuery query( mapUrl );
1058 query.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
1059 //Set version
1060 if ( mWfsParameters.version().isEmpty() )
1061 query.addQueryItem( QStringLiteral( "VERSION" ), implementationVersion() );
1062 else if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1063 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.1.0" ) );
1064 else
1065 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
1066
1067 for ( auto param : query.queryItems() )
1068 {
1069 if ( sParamFilter.contains( param.first.toUpper() ) )
1070 query.removeAllQueryItems( param.first );
1071 }
1072
1073 query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
1074 query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
1075 if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1076 {
1077 if ( format == QgsWfsParameters::Format::GML2 )
1078 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
1079 else
1080 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
1081 }
1082 else
1083 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );
1084
1085 mapUrl.setQuery( query );
1086
1087 hrefString = mapUrl.toString();
1088
1089 QString wfsSchema;
1090 if ( mWfsParameters.version().isEmpty() || mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1091 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" );
1092 else
1093 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
1094
1095 //wfs:FeatureCollection valid
1096 fcString = QStringLiteral( "<wfs:FeatureCollection" );
1097 fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
1098 fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
1099 fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
1100 fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
1101 fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
1102 fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
1103 fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
1104 fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " " + wfsSchema + " " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
1105 fcString += "\n timeStamp=\"" + now.toString( Qt::ISODate ) + "\"";
1106 fcString += "\n numberOfFeatures=\"" + QString::number( numberOfFeatures ) + "\"";
1107 fcString += QLatin1String( ">\n" );
1108 fcString += QLatin1String( "</wfs:FeatureCollection>" );
1109 }
1110
1111 response.write( fcString.toUtf8() );
1112 response.flush();
1113 }
1114
1115 void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
1116 int prec, QgsCoordinateReferenceSystem &crs, QgsRectangle *rect, const QStringList &typeNames, const QgsServerSettings *settings )
1117 {
1118 QString fcString;
1119
1120 std::unique_ptr< QgsRectangle > transformedRect;
1121
1122 if ( format == QgsWfsParameters::Format::GeoJSON )
1123 {
1124 response.setHeader( "Content-Type", "application/vnd.geo+json; charset=utf-8" );
1125
1126 if ( crs.isValid() && !rect->isEmpty() )
1127 {
1128 QgsGeometry exportGeom = QgsGeometry::fromRect( *rect );
1129 QgsCoordinateTransform transform;
1130 transform.setSourceCrs( crs );
1131 transform.setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
1132 try
1133 {
1134 if ( exportGeom.transform( transform ) == Qgis::GeometryOperationResult::Success )
1135 {
1136 transformedRect.reset( new QgsRectangle( exportGeom.boundingBox() ) );
1137 rect = transformedRect.get();
1138 }
1139 }
1140 catch ( QgsException &cse )
1141 {
1142 Q_UNUSED( cse )
1143 }
1144 }
1145 // EPSG:4326 max extent is -180, -90, 180, 90
1146 rect = new QgsRectangle( rect->intersect( QgsRectangle( -180.0, -90.0, 180.0, 90.0 ) ) );
1147
1148 fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
1149 fcString += " \"bbox\": [ " + qgsDoubleToString( rect->xMinimum(), prec ) + ", " + qgsDoubleToString( rect->yMinimum(), prec ) + ", " + qgsDoubleToString( rect->xMaximum(), prec ) + ", " + qgsDoubleToString( rect->yMaximum(), prec ) + "],\n";
1150 fcString += QLatin1String( " \"features\": [\n" );
1151 response.write( fcString.toUtf8() );
1152 }
1153 else
1154 {
1155 if ( format == QgsWfsParameters::Format::GML2 )
1156 response.setHeader( "Content-Type", "text/xml; subtype=gml/2.1.2; charset=utf-8" );
1157 else
1158 response.setHeader( "Content-Type", "text/xml; subtype=gml/3.1.1; charset=utf-8" );
1159
1160 //Prepare url
1161 QString hrefString = serviceUrl( request, project, *settings );
1162
1163 QUrl mapUrl( hrefString );
1164
1165 QUrlQuery query( mapUrl );
1166 query.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
1167 //Set version
1168 if ( mWfsParameters.version().isEmpty() )
1169 query.addQueryItem( QStringLiteral( "VERSION" ), implementationVersion() );
1170 else if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1171 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.1.0" ) );
1172 else
1173 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
1174
1175 const auto queryItems {query.queryItems()};
1176 for ( auto param : std::as_const( queryItems ) )
1177 {
1178 if ( sParamFilter.contains( param.first.toUpper() ) )
1179 query.removeAllQueryItems( param.first );
1180 }
1181
1182 query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
1183 query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
1184 if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1185 {
1186 if ( format == QgsWfsParameters::Format::GML2 )
1187 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
1188 else
1189 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
1190 }
1191 else
1192 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );
1193
1194 mapUrl.setQuery( query );
1195
1196 hrefString = mapUrl.toString();
1197
1198 QString wfsSchema;
1199 if ( mWfsParameters.version().isEmpty() || mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1200 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" );
1201 else
1202 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
1203
1204 //wfs:FeatureCollection valid
1205 fcString = QStringLiteral( "<wfs:FeatureCollection" );
1206 fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
1207 fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
1208 fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
1209 fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
1210 fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
1211 fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
1212 fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
1213 fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " " + wfsSchema + " " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
1214 fcString += QLatin1String( ">\n" );
1215
1216 response.write( fcString.toUtf8() );
1217 response.flush();
1218
1219 QDomDocument doc;
1220 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1221 if ( format == QgsWfsParameters::Format::GML3 )
1222 {
1223 // For WFS 1.1 we honor requested CRS and axis order
1224 const QString srsName {request.serverParameters().value( QStringLiteral( "SRSNAME" ) )};
1225 const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
1226 crs.hasAxisInverted() &&
1227 ! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
1228
1229 // If requested SRS (srsName) is different from rect CRS (crs) we need to transform the envelope
1230 QgsCoordinateTransform transform;
1231 transform.setSourceCrs( crs );
1233 QgsRectangle crsCorrectedRect { rect ? *rect : QgsRectangle() };
1234
1235 try
1236 {
1237 crsCorrectedRect = transform.transformBoundingBox( crsCorrectedRect );
1238 }
1239 catch ( QgsException &cse )
1240 {
1241 Q_UNUSED( cse )
1242 }
1243
1244 QDomElement envElem = QgsOgcUtils::rectangleToGMLEnvelope( &crsCorrectedRect, doc, srsName, invertAxis, prec );
1245 if ( !envElem.isNull() )
1246 {
1247 if ( crs.isValid() && srsName.isEmpty() )
1248 {
1249 envElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1250 }
1251 bbElem.appendChild( envElem );
1252 doc.appendChild( bbElem );
1253 }
1254 }
1255 else
1256 {
1257 QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( rect, doc, prec );
1258 if ( !boxElem.isNull() )
1259 {
1260 if ( crs.isValid() )
1261 {
1262 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1263 }
1264 bbElem.appendChild( boxElem );
1265 doc.appendChild( bbElem );
1266 }
1267 }
1268 response.write( doc.toByteArray() );
1269 response.flush();
1270 }
1271 }
1272
1273 void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
1274 const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1275 {
1276 if ( !feature.isValid() )
1277 return;
1278
1279 if ( format == QgsWfsParameters::Format::GeoJSON )
1280 {
1281 QString fcString;
1282 if ( featIdx == 0 )
1283 fcString += QLatin1String( " " );
1284 else
1285 fcString += QLatin1String( " ," );
1286 mJsonExporter.setSourceCrs( params.crs );
1287 mJsonExporter.setIncludeGeometry( false );
1288 mJsonExporter.setIncludeAttributes( !params.attributeIndexes.isEmpty() );
1289 mJsonExporter.setAttributes( params.attributeIndexes );
1290 fcString += createFeatureGeoJSON( feature, params, pkAttributes );
1291 fcString += QLatin1String( "\n" );
1292
1293 response.write( fcString.toUtf8() );
1294 }
1295 else
1296 {
1297 QDomDocument gmlDoc;
1298 QDomElement featureElement;
1299 if ( format == QgsWfsParameters::Format::GML3 )
1300 {
1301 featureElement = createFeatureGML3( feature, gmlDoc, params, project, pkAttributes );
1302 gmlDoc.appendChild( featureElement );
1303 }
1304 else
1305 {
1306 featureElement = createFeatureGML2( feature, gmlDoc, params, project, pkAttributes );
1307 gmlDoc.appendChild( featureElement );
1308 }
1309 response.write( gmlDoc.toByteArray() );
1310 }
1311
1312 // Stream partial content
1313 response.flush();
1314 }
1315
1316 void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format )
1317 {
1318 QString fcString;
1319 if ( format == QgsWfsParameters::Format::GeoJSON )
1320 {
1321 fcString += QLatin1String( " ]\n" );
1322 fcString += QLatin1Char( '}' );
1323 }
1324 else
1325 {
1326 fcString = QStringLiteral( "</wfs:FeatureCollection>\n" );
1327 }
1328 response.write( fcString.toUtf8() );
1329 }
1330
1331
1332 QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams &params, const QgsAttributeList &pkAttributes )
1333 {
1334 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1335 //QgsJsonExporter force transform geometry to EPSG:4326
1336 //and the RFC 7946 GeoJSON specification recommends limiting coordinate precision to 6
1337 //Q_UNUSED( prec )
1338
1339 //copy feature so we can modify its geometry as required
1340 QgsFeature f( feature );
1341 QgsGeometry geom = feature.geometry();
1342 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1343 {
1344 mJsonExporter.setIncludeGeometry( true );
1345 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1346 {
1347 QgsRectangle box = geom.boundingBox();
1348 f.setGeometry( QgsGeometry::fromRect( box ) );
1349 }
1350 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1351 {
1352 f.setGeometry( geom.centroid() );
1353 }
1354 }
1355
1356 return mJsonExporter.exportFeature( f, QVariantMap(), id );
1357 }
1358
1359
1360 QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1361 {
1362 //gml:FeatureMember
1363 QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1364
1365 //qgs:%TYPENAME%
1366 QDomElement typeNameElement = doc.createElement( "qgs:" + params.typeName /*qgs:%TYPENAME%*/ );
1367 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1368 typeNameElement.setAttribute( QStringLiteral( "fid" ), id );
1369 featureElement.appendChild( typeNameElement );
1370
1371 //add geometry column (as gml)
1372 QgsGeometry geom = feature.geometry();
1373 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1374 {
1375 int prec = params.precision;
1376 QgsCoordinateReferenceSystem crs = params.crs;
1377 QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1378 try
1379 {
1380 QgsGeometry transformed = geom;
1381 if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1382 {
1383 geom = transformed;
1384 crs = params.outputCrs;
1385 if ( crs.isGeographic() && !params.crs.isGeographic() )
1386 prec = std::min( params.precision + 3, 6 );
1387 }
1388 }
1389 catch ( QgsCsException &cse )
1390 {
1391 Q_UNUSED( cse )
1392 }
1393
1394 QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1395 QDomElement gmlElem;
1396 QgsGeometry cloneGeom( geom );
1397 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1398 {
1399 cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1400 }
1401 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1402 {
1403 cloneGeom = geom.centroid();
1404 }
1405 else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1406 {
1407 cloneGeom.convertToMultiType();
1408 }
1409 const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1410 if ( abstractGeom )
1411 {
1412 gmlElem = abstractGeom->asGml2( doc, prec, "http://www.opengis.net/gml" );
1413 }
1414
1415 if ( !gmlElem.isNull() )
1416 {
1417 QgsRectangle box = geom.boundingBox();
1418 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1419 QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, prec );
1420
1421 if ( crs.isValid() )
1422 {
1423 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1424 gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1425 }
1426
1427 bbElem.appendChild( boxElem );
1428 typeNameElement.appendChild( bbElem );
1429
1430 geomElem.appendChild( gmlElem );
1431 typeNameElement.appendChild( geomElem );
1432 }
1433 }
1434
1435 //read all attribute values from the feature
1436 const QgsAttributes featureAttributes = feature.attributes();
1437 const QgsFields fields = feature.fields();
1438 for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1439 {
1440 int idx = params.attributeIndexes[i];
1441 if ( idx >= fields.count() )
1442 {
1443 continue;
1444 }
1445
1446 const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1447 typeNameElement.appendChild( fieldElem );
1448 }
1449
1450 return featureElement;
1451 }
1452
1453 QDomElement createFeatureGML3( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1454 {
1455 //gml:FeatureMember
1456 QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1457
1458 //qgs:%TYPENAME%
1459 QDomElement typeNameElement = doc.createElement( QStringLiteral( "qgs:" ) + params.typeName /*qgs:%TYPENAME%*/ );
1460 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1461 typeNameElement.setAttribute( QStringLiteral( "gml:id" ), id );
1462 featureElement.appendChild( typeNameElement );
1463
1464 //add geometry column (as gml)
1465 QgsGeometry geom = feature.geometry();
1466 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1467 {
1468 int prec = params.precision;
1469 QgsCoordinateReferenceSystem crs = params.crs;
1470 QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1471 try
1472 {
1473 QgsGeometry transformed = geom;
1474 if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1475 {
1476 geom = transformed;
1477 crs = params.outputCrs;
1478 if ( crs.isGeographic() && !params.crs.isGeographic() )
1479 prec = std::min( params.precision + 3, 6 );
1480 }
1481 }
1482 catch ( QgsCsException &cse )
1483 {
1484 Q_UNUSED( cse )
1485 }
1486
1487 QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1488 QDomElement gmlElem;
1489 QgsGeometry cloneGeom( geom );
1490 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1491 {
1492 cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1493 }
1494 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1495 {
1496 cloneGeom = geom.centroid();
1497 }
1498 else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1499 {
1500 cloneGeom.convertToMultiType();
1501 }
1502 const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1503 if ( abstractGeom )
1504 {
1505 gmlElem = abstractGeom->asGml3( doc, prec, "http://www.opengis.net/gml", params.hasAxisInverted ? QgsAbstractGeometry::AxisOrder::YX : QgsAbstractGeometry::AxisOrder::XY );
1506 }
1507
1508 if ( !gmlElem.isNull() )
1509 {
1510 QgsRectangle box = geom.boundingBox();
1511 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1512 QDomElement boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, params.srsName, params.hasAxisInverted, prec );
1513
1514 if ( crs.isValid() && params.srsName.isEmpty() )
1515 {
1516 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1517 gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1518 }
1519 else if ( !params.srsName.isEmpty() )
1520 {
1521 gmlElem.setAttribute( QStringLiteral( "srsName" ), params.srsName );
1522 }
1523
1524 bbElem.appendChild( boxElem );
1525 typeNameElement.appendChild( bbElem );
1526
1527 geomElem.appendChild( gmlElem );
1528 typeNameElement.appendChild( geomElem );
1529 }
1530 }
1531
1532 //read all attribute values from the feature
1533 const QgsAttributes featureAttributes = feature.attributes();
1534 const QgsFields fields = feature.fields();
1535 for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1536 {
1537 int idx = params.attributeIndexes[i];
1538 if ( idx >= fields.count() )
1539 {
1540 continue;
1541 }
1542
1543 const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1544 typeNameElement.appendChild( fieldElem );
1545 }
1546
1547 return featureElement;
1548 }
1549
1550 QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc )
1551 {
1553 const thread_local QRegularExpression sCleanTagNameRegExp( QStringLiteral( "[^\\w\\.-_]" ), QRegularExpression::PatternOption::UseUnicodePropertiesOption );
1554 const QString attributeName = field.name().replace( ' ', '_' ).replace( sCleanTagNameRegExp, QString() );
1555 QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:" ) + attributeName );
1556 if ( QgsVariantUtils::isNull( value ) )
1557 {
1558 fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
1559 }
1560 else
1561 {
1562 const QString fieldText = encodeValueToText( value, setup );
1563 //do we need CDATA
1564 if ( fieldText.indexOf( '<' ) != -1 || fieldText.indexOf( '&' ) != -1 )
1565 {
1566 fieldElem.appendChild( doc.createCDATASection( fieldText ) );
1567 }
1568 else
1569 {
1570 fieldElem.appendChild( doc.createTextNode( fieldText ) );
1571 }
1572 }
1573 return fieldElem;
1574 }
1575
1576 QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup )
1577 {
1578 if ( QgsVariantUtils::isNull( value ) )
1579 return QString();
1580
1581 if ( setup.type() == QStringLiteral( "DateTime" ) )
1582 {
1583 const QVariantMap config = setup.config();
1584 const QString fieldFormat = config.value( QStringLiteral( "field_format" ), QgsDateTimeFieldFormatter::defaultFormat( value.type() ) ).toString();
1585 QDateTime date = value.toDateTime();
1586
1587 if ( date.isValid() )
1588 {
1589 return date.toString( fieldFormat );
1590 }
1591 }
1592 else if ( setup.type() == QStringLiteral( "Range" ) )
1593 {
1594 const QVariantMap config = setup.config();
1595 if ( config.contains( QStringLiteral( "Precision" ) ) )
1596 {
1597 // if precision is defined, use it
1598 bool ok;
1599 int precision( config[ QStringLiteral( "Precision" ) ].toInt( &ok ) );
1600 if ( ok )
1601 return QString::number( value.toDouble(), 'f', precision );
1602 }
1603 }
1604
1605 switch ( value.type() )
1606 {
1607 case QVariant::Int:
1608 case QVariant::UInt:
1609 case QVariant::LongLong:
1610 case QVariant::ULongLong:
1611 case QVariant::Double:
1612 return value.toString();
1613
1614 case QVariant::Bool:
1615 return value.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" );
1616
1617 case QVariant::StringList:
1618 case QVariant::List:
1619 case QVariant::Map:
1620 return QgsJsonUtils::encodeValue( value );
1621
1622 default:
1623 case QVariant::String:
1624 return value.toString();
1625 }
1626 }
1627
1628
1629 } // namespace
1630
1631} // namespace QgsWfs
@ Success
Operation succeeded.
Abstract base class for all geometries.
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.
Definition: qgsattributes.h:59
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 axis is inverted (e.g., for WMS 1.3) for the CRS.
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.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
static QString defaultFormat(QVariant::Type type)
Gets the default format in function of the type.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
Defines a QGIS exception class.
Definition: qgsexception.h:35
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)
This class wraps a request for features to a vector layer (or directly its vector data provider).
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 & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & addOrderBy(const QString &expression, bool ascending=true)
Adds a new OrderByClause, appending it as the least important one.
Flags 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.
@ 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.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
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.
Definition: qgsfeature.cpp:219
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
ConfigurationFlags configurationFlags
Definition: qgsfield.h:64
@ HideFromWfs
Field is not available if layer is served as WFS from QGIS server.
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition: qgsfield.cpp:631
Container of fields for a vector layer.
Definition: qgsfields.h:45
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:168
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
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.
Handles exporting QgsFeature features to GeoJSON features.
Definition: qgsjsonutils.h:41
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
Base class for all map layer types.
Definition: qgsmaplayer.h:73
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
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:104
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:333
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() SIP_THROW(QgsServerException)
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)
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 QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
QgsStringMap attributeAliases() const
Returns a map of field name to attribute alias.
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(Type type) SIP_HOLDGIL
Returns true if the WKB type is a multi type.
Definition: qgswkbtypes.h:862
@ VectorLayer
Vector layer.
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.
Definition: qgswfsutils.cpp:69
QString implementationVersion()
Returns the highest version supported by this implementation.
Definition: qgswfsutils.cpp:32
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Service URL string.
Definition: qgswfsutils.cpp:37
const QString OGC_NAMESPACE
Definition: qgswfsutils.h:74
const QString GML_NAMESPACE
Definition: qgswfsutils.h:73
const QString WFS_NAMESPACE
Definition: qgswfsutils.h:72
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:75
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:2466
QMap< QString, QString > QgsStringMap
Definition: qgis.h:3022
QList< int > QgsAttributeList
Definition: qgsfield.h:26
const QgsField & field
Definition: qgsfield.h:463
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