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