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