QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
qgswfsgetfeature.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgswfsgetfeature.cpp
3 -------------------------
4 begin : December 20 , 2016
5 copyright : (C) 2007 by Marco Hugentobler (original code)
6 (C) 2012 by René-Luc D'Hont (original code)
7 (C) 2014 by Alessandro Pasotti (original code)
8 (C) 2017 by David Marteau
9 email : marco dot hugentobler at karto dot baug dot ethz dot ch
10 a dot pasotti at itopen dot it
11 david dot marteau at 3liz dot com
12 ***************************************************************************/
13
14/***************************************************************************
15 * *
16 * This program is free software; you can redistribute it and/or modify *
17 * it under the terms of the GNU General Public License as published by *
18 * the Free Software Foundation; either version 2 of the License, or *
19 * (at your option) any later version. *
20 * *
21 ***************************************************************************/
22#include "qgswfsutils.h"
24#include "qgsserverfeatureid.h"
25#include "qgsfields.h"
27#include "qgsexpression.h"
28#include "qgsgeometry.h"
29#include "qgsmaplayer.h"
30#include "qgsfeatureiterator.h"
32#include "qgsvectorlayer.h"
33#include "qgsfilterrestorer.h"
34#include "qgsproject.h"
35#include "qgsogcutils.h"
36#include "qgsjsonutils.h"
38#include "qgswkbtypes.h"
39
40#include "qgswfsgetfeature.h"
41
42#include <QRegularExpression>
43
44namespace QgsWfs
45{
46
47 namespace
48 {
49 struct createFeatureParams
50 {
52
54
56
57 const QString &typeName;
58
60
61 const QString &geometryName;
62
64
66
67 const QString &srsName;
68
70 };
71
72 QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams &params, const QgsAttributeList &pkAttributes );
73
74 QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc );
75
76 QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup );
77
78 QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes );
79
80 QDomElement createFeatureGML3( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes );
81
82 void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project,
83 QgsWfsParameters::Format format, int numberOfFeatures, const QStringList &typeNames, const QgsServerSettings *serverSettings );
84
85 void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project,
87 QgsRectangle *rect, const QStringList &typeNames, const QgsServerSettings *settings );
88
89 void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
90 const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes = QgsAttributeList() );
91
92 void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format );
93
94 QgsServerRequest::Parameters mRequestParameters;
95 QgsWfsParameters mWfsParameters;
96 /* GeoJSON Exporter */
97 QgsJsonExporter mJsonExporter;
98 }
99
100 void writeGetFeature( QgsServerInterface *serverIface, const QgsProject *project,
101 const QString &version, const QgsServerRequest &request,
102 QgsServerResponse &response )
103 {
104 Q_UNUSED( version )
105
106 mRequestParameters = request.parameters();
107 mWfsParameters = QgsWfsParameters( QUrlQuery( request.url() ) );
108 mWfsParameters.dump();
109 getFeatureRequest aRequest;
110
111 QDomDocument doc;
112 QString errorMsg;
113
114 if ( doc.setContent( request.data(), true, &errorMsg ) )
115 {
116 QDomElement docElem = doc.documentElement();
117 aRequest = parseGetFeatureRequestBody( docElem, project );
118 }
119 else
120 {
121 aRequest = parseGetFeatureParameters( project );
122 }
123
124 // store typeName
125 QStringList typeNameList;
126
127 // Request metadata
128 bool onlyOneLayer = ( aRequest.queries.size() == 1 );
129 QgsRectangle requestRect;
131 int requestPrecision = 6;
132 if ( !onlyOneLayer )
133 requestCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) );
134
135 QList<getFeatureQuery>::iterator qIt = aRequest.queries.begin();
136 for ( ; qIt != aRequest.queries.end(); ++qIt )
137 {
138 typeNameList << ( *qIt ).typeName;
139 }
140
141 // get layers and
142 // update the request metadata
143 QStringList wfsLayerIds = QgsServerProjectUtils::wfsLayerIds( *project );
144 QMap<QString, QgsMapLayer *> mapLayerMap;
145 for ( int i = 0; i < wfsLayerIds.size(); ++i )
146 {
147 QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) );
148 if ( !layer )
149 {
150 continue;
151 }
152 if ( layer->type() != QgsMapLayerType::VectorLayer )
153 {
154 continue;
155 }
156
157 QString name = layerTypeName( layer );
158
159 if ( typeNameList.contains( name ) )
160 {
161 // store layers
162 mapLayerMap[name] = layer;
163 // update request metadata
164 if ( onlyOneLayer )
165 {
166 requestRect = layer->extent();
167 requestCrs = layer->crs();
168 }
169 else
170 {
171 QgsCoordinateTransform transform( layer->crs(), requestCrs, project );
172 try
173 {
174 if ( requestRect.isEmpty() )
175 {
176 requestRect = transform.transform( layer->extent() );
177 }
178 else
179 {
180 requestRect.combineExtentWith( transform.transform( layer->extent() ) );
181 }
182 }
183 catch ( QgsException &cse )
184 {
185 Q_UNUSED( cse )
186 requestRect = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
187 }
188 }
189 }
190 }
191
192 // check if all typename are valid
193 for ( const QString &typeName : typeNameList )
194 {
195 if ( !mapLayerMap.contains( typeName ) )
196 {
197 throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' could not be found" ).arg( typeName ) );
198 }
199 }
200
201#ifdef HAVE_SERVER_PYTHON_PLUGINS
202 QgsAccessControl *accessControl = serverIface->accessControls();
203 //scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope
204 //there's LOTS of potential exit paths here, so we avoid having to restore the filters manually
205 std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer() );
206#else
207 ( void )serverIface;
208#endif
209
210 // features counters
211 long sentFeatures = 0;
212 long iteratedFeatures = 0;
213 // sent features
214 QgsFeature feature;
215 qIt = aRequest.queries.begin();
216 for ( ; qIt != aRequest.queries.end(); ++qIt )
217 {
218 getFeatureQuery &query = *qIt;
219 QString typeName = query.typeName;
220
221 QgsMapLayer *layer = mapLayerMap[typeName];
222#ifdef HAVE_SERVER_PYTHON_PLUGINS
223 if ( accessControl && !accessControl->layerReadPermission( layer ) )
224 {
225 throw QgsSecurityAccessException( QStringLiteral( "Feature access permission denied" ) );
226 }
227#endif
228 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
229 if ( !vlayer )
230 {
231 throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' layer error" ).arg( typeName ) );
232 }
233
234 //test provider
235 QgsVectorDataProvider *provider = vlayer->dataProvider();
236 if ( !provider )
237 {
238 throw QgsRequestNotWellFormedException( QStringLiteral( "TypeName '%1' layer's provider error" ).arg( typeName ) );
239 }
240#ifdef HAVE_SERVER_PYTHON_PLUGINS
241 if ( accessControl )
242 {
243 QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( accessControl, vlayer, filterRestorer->originalFilters() );
244 }
245#endif
246 //is there alias info for this vector layer?
247 QMap< int, QString > layerAliasInfo;
248 QgsStringMap aliasMap = vlayer->attributeAliases();
249 QgsStringMap::const_iterator aliasIt = aliasMap.constBegin();
250 for ( ; aliasIt != aliasMap.constEnd(); ++aliasIt )
251 {
252 int attrIndex = vlayer->fields().lookupField( aliasIt.key() );
253 if ( attrIndex != -1 )
254 {
255 layerAliasInfo.insert( attrIndex, aliasIt.value() );
256 }
257 }
258
259 // get propertyList from query
260 const QStringList propertyList = query.propertyList;
261
262 //Using pending attributes and pending fields
263 QgsAttributeList attrIndexes = vlayer->attributeList();
264 const QgsFields fields = vlayer->fields();
265 bool withGeom = true;
266 if ( !propertyList.isEmpty() && propertyList.first() != QLatin1String( "*" ) )
267 {
268 withGeom = false;
269 QStringList::const_iterator plstIt;
270 QList<int> idxList;
271 // build corresponding propertyname
272 QList<QString> propertynames;
273 QList<QString> fieldnames;
274 for ( const QgsField &field : fields )
275 {
276 fieldnames.append( field.name() );
277 const thread_local QRegularExpression sCleanTagNameRegExp( QStringLiteral( "[^\\w\\.-_]" ), QRegularExpression::PatternOption::UseUnicodePropertiesOption );
278 propertynames.append( field.name().replace( ' ', '_' ).replace( sCleanTagNameRegExp, QString() ) );
279 }
280 QString fieldName;
281 for ( plstIt = propertyList.constBegin(); plstIt != propertyList.constEnd(); ++plstIt )
282 {
283 fieldName = *plstIt;
284 int fieldNameIdx = propertynames.indexOf( fieldName );
285 if ( fieldNameIdx == -1 )
286 {
287 fieldNameIdx = fieldnames.indexOf( fieldName );
288 }
289 if ( fieldNameIdx > -1 )
290 {
291 idxList.append( fieldNameIdx );
292 }
293 else if ( fieldName == QLatin1String( "geometry" ) )
294 {
295 withGeom = true;
296 }
297 }
298 if ( !idxList.isEmpty() )
299 {
300 attrIndexes = idxList;
301 }
302 }
303
304 //excluded attributes for this layer
305 if ( !attrIndexes.isEmpty() )
306 {
307 for ( const QgsField &field : fields )
308 {
310 {
311 int fieldNameIdx = fields.indexOf( field.name() );
312 if ( fieldNameIdx > -1 && attrIndexes.contains( fieldNameIdx ) )
313 {
314 attrIndexes.removeOne( fieldNameIdx );
315 }
316 }
317 }
318 }
319
320 // update request
321 QgsFeatureRequest featureRequest = query.featureRequest;
322
323 // expression context
324 QgsExpressionContext expressionContext;
325 expressionContext << QgsExpressionContextUtils::globalScope()
328 featureRequest.setExpressionContext( expressionContext );
329
330 if ( !query.serverFids.isEmpty() )
331 {
333 }
334
335 // geometry flags
336 if ( vlayer->wkbType() == QgsWkbTypes::NoGeometry )
337 featureRequest.setFlags( featureRequest.flags() | QgsFeatureRequest::NoGeometry );
338 else
339 featureRequest.setFlags( featureRequest.flags() | ( withGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) );
340
341 // subset of attributes
342 featureRequest.setSubsetOfAttributes( attrIndexes );
343 // 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 for ( auto param : query.queryItems() )
1094 {
1095 if ( sParamFilter.contains( param.first.toUpper() ) )
1096 query.removeAllQueryItems( param.first );
1097 }
1098
1099 query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
1100 query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
1101 if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1102 {
1103 if ( format == QgsWfsParameters::Format::GML2 )
1104 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
1105 else
1106 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
1107 }
1108 else
1109 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );
1110
1111 mapUrl.setQuery( query );
1112
1113 hrefString = mapUrl.toString();
1114
1115 QString wfsSchema;
1116 if ( mWfsParameters.version().isEmpty() || mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1117 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" );
1118 else
1119 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
1120
1121 //wfs:FeatureCollection valid
1122 fcString = QStringLiteral( "<wfs:FeatureCollection" );
1123 fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
1124 fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
1125 fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
1126 fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
1127 fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
1128 fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
1129 fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
1130 fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " " + wfsSchema + " " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
1131 fcString += "\n timeStamp=\"" + now.toString( Qt::ISODate ) + "\"";
1132 fcString += "\n numberOfFeatures=\"" + QString::number( numberOfFeatures ) + "\"";
1133 fcString += QLatin1String( ">\n" );
1134 fcString += QLatin1String( "</wfs:FeatureCollection>" );
1135 }
1136
1137 response.write( fcString.toUtf8() );
1138 response.flush();
1139 }
1140
1141 void startGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
1142 int prec, QgsCoordinateReferenceSystem &crs, QgsRectangle *rect, const QStringList &typeNames, const QgsServerSettings *settings )
1143 {
1144 QString fcString;
1145
1146 std::unique_ptr< QgsRectangle > transformedRect;
1147
1148 if ( format == QgsWfsParameters::Format::GeoJSON )
1149 {
1150 response.setHeader( "Content-Type", "application/vnd.geo+json; charset=utf-8" );
1151
1152 if ( crs.isValid() && !rect->isEmpty() )
1153 {
1154 QgsGeometry exportGeom = QgsGeometry::fromRect( *rect );
1155 QgsCoordinateTransform transform;
1156 transform.setSourceCrs( crs );
1157 transform.setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
1158 try
1159 {
1160 if ( exportGeom.transform( transform ) == Qgis::GeometryOperationResult::Success )
1161 {
1162 transformedRect.reset( new QgsRectangle( exportGeom.boundingBox() ) );
1163 rect = transformedRect.get();
1164 }
1165 }
1166 catch ( QgsException &cse )
1167 {
1168 Q_UNUSED( cse )
1169 }
1170 }
1171 // EPSG:4326 max extent is -180, -90, 180, 90
1172 rect = new QgsRectangle( rect->intersect( QgsRectangle( -180.0, -90.0, 180.0, 90.0 ) ) );
1173
1174 fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
1175 fcString += " \"bbox\": [ " + qgsDoubleToString( rect->xMinimum(), prec ) + ", " + qgsDoubleToString( rect->yMinimum(), prec ) + ", " + qgsDoubleToString( rect->xMaximum(), prec ) + ", " + qgsDoubleToString( rect->yMaximum(), prec ) + "],\n";
1176 fcString += QLatin1String( " \"features\": [\n" );
1177 response.write( fcString.toUtf8() );
1178 }
1179 else
1180 {
1181 if ( format == QgsWfsParameters::Format::GML2 )
1182 response.setHeader( "Content-Type", "text/xml; subtype=gml/2.1.2; charset=utf-8" );
1183 else
1184 response.setHeader( "Content-Type", "text/xml; subtype=gml/3.1.1; charset=utf-8" );
1185
1186 //Prepare url
1187 QString hrefString = serviceUrl( request, project, *settings );
1188
1189 QUrl mapUrl( hrefString );
1190
1191 QUrlQuery query( mapUrl );
1192 query.addQueryItem( QStringLiteral( "SERVICE" ), QStringLiteral( "WFS" ) );
1193 //Set version
1194 if ( mWfsParameters.version().isEmpty() )
1195 query.addQueryItem( QStringLiteral( "VERSION" ), implementationVersion() );
1196 else if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1197 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.1.0" ) );
1198 else
1199 query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );
1200
1201 const auto queryItems {query.queryItems()};
1202 for ( auto param : std::as_const( queryItems ) )
1203 {
1204 if ( sParamFilter.contains( param.first.toUpper() ) )
1205 query.removeAllQueryItems( param.first );
1206 }
1207
1208 query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
1209 query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
1210 if ( mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1211 {
1212 if ( format == QgsWfsParameters::Format::GML2 )
1213 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/2.1.2" ) );
1214 else
1215 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "text/xml; subtype=gml/3.1.1" ) );
1216 }
1217 else
1218 query.addQueryItem( QStringLiteral( "OUTPUTFORMAT" ), QStringLiteral( "XMLSCHEMA" ) );
1219
1220 mapUrl.setQuery( query );
1221
1222 hrefString = mapUrl.toString();
1223
1224 QString wfsSchema;
1225 if ( mWfsParameters.version().isEmpty() || mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) )
1226 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" );
1227 else
1228 wfsSchema = QStringLiteral( "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" );
1229
1230 //wfs:FeatureCollection valid
1231 fcString = QStringLiteral( "<wfs:FeatureCollection" );
1232 fcString += " xmlns:wfs=\"" + WFS_NAMESPACE + "\"";
1233 fcString += " xmlns:ogc=\"" + OGC_NAMESPACE + "\"";
1234 fcString += " xmlns:gml=\"" + GML_NAMESPACE + "\"";
1235 fcString += QLatin1String( " xmlns:ows=\"http://www.opengis.net/ows\"" );
1236 fcString += QLatin1String( " xmlns:xlink=\"http://www.w3.org/1999/xlink\"" );
1237 fcString += " xmlns:qgs=\"" + QGS_NAMESPACE + "\"";
1238 fcString += QLatin1String( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" );
1239 fcString += " xsi:schemaLocation=\"" + WFS_NAMESPACE + " " + wfsSchema + " " + QGS_NAMESPACE + " " + hrefString.replace( QLatin1String( "&" ), QLatin1String( "&amp;" ) ) + "\"";
1240 fcString += QLatin1String( ">\n" );
1241
1242 response.write( fcString.toUtf8() );
1243 response.flush();
1244
1245 QDomDocument doc;
1246 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1247 if ( format == QgsWfsParameters::Format::GML3 )
1248 {
1249 // For WFS 1.1 we honor requested CRS and axis order
1250 const QString srsName {request.serverParameters().value( QStringLiteral( "SRSNAME" ) )};
1251 const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
1252 crs.hasAxisInverted() &&
1253 ! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
1254
1255 // If requested SRS (srsName) is different from rect CRS (crs) we need to transform the envelope
1256 QgsCoordinateTransform transform;
1257 transform.setSourceCrs( crs );
1259 QgsRectangle crsCorrectedRect { rect ? *rect : QgsRectangle() };
1260
1261 try
1262 {
1263 crsCorrectedRect = transform.transformBoundingBox( crsCorrectedRect );
1264 }
1265 catch ( QgsException &cse )
1266 {
1267 Q_UNUSED( cse )
1268 }
1269
1270 QDomElement envElem = QgsOgcUtils::rectangleToGMLEnvelope( &crsCorrectedRect, doc, srsName, invertAxis, prec );
1271 if ( !envElem.isNull() )
1272 {
1273 if ( crs.isValid() && srsName.isEmpty() )
1274 {
1275 envElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1276 }
1277 bbElem.appendChild( envElem );
1278 doc.appendChild( bbElem );
1279 }
1280 }
1281 else
1282 {
1283 QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( rect, doc, prec );
1284 if ( !boxElem.isNull() )
1285 {
1286 if ( crs.isValid() )
1287 {
1288 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1289 }
1290 bbElem.appendChild( boxElem );
1291 doc.appendChild( bbElem );
1292 }
1293 }
1294 response.write( doc.toByteArray() );
1295 response.flush();
1296 }
1297 }
1298
1299 void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, const QgsFeature &feature, int featIdx,
1300 const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1301 {
1302 if ( !feature.isValid() )
1303 return;
1304
1305 if ( format == QgsWfsParameters::Format::GeoJSON )
1306 {
1307 QString fcString;
1308 if ( featIdx == 0 )
1309 fcString += QLatin1String( " " );
1310 else
1311 fcString += QLatin1String( " ," );
1312 mJsonExporter.setSourceCrs( params.crs );
1313 mJsonExporter.setIncludeGeometry( false );
1314 mJsonExporter.setIncludeAttributes( !params.attributeIndexes.isEmpty() );
1315 mJsonExporter.setAttributes( params.attributeIndexes );
1316 fcString += createFeatureGeoJSON( feature, params, pkAttributes );
1317 fcString += QLatin1String( "\n" );
1318
1319 response.write( fcString.toUtf8() );
1320 }
1321 else
1322 {
1323 QDomDocument gmlDoc;
1324 QDomElement featureElement;
1325 if ( format == QgsWfsParameters::Format::GML3 )
1326 {
1327 featureElement = createFeatureGML3( feature, gmlDoc, params, project, pkAttributes );
1328 gmlDoc.appendChild( featureElement );
1329 }
1330 else
1331 {
1332 featureElement = createFeatureGML2( feature, gmlDoc, params, project, pkAttributes );
1333 gmlDoc.appendChild( featureElement );
1334 }
1335 response.write( gmlDoc.toByteArray() );
1336 }
1337
1338 // Stream partial content
1339 response.flush();
1340 }
1341
1342 void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format )
1343 {
1344 QString fcString;
1345 if ( format == QgsWfsParameters::Format::GeoJSON )
1346 {
1347 fcString += QLatin1String( " ]\n" );
1348 fcString += QLatin1Char( '}' );
1349 }
1350 else
1351 {
1352 fcString = QStringLiteral( "</wfs:FeatureCollection>\n" );
1353 }
1354 response.write( fcString.toUtf8() );
1355 }
1356
1357
1358 QString createFeatureGeoJSON( const QgsFeature &feature, const createFeatureParams &params, const QgsAttributeList &pkAttributes )
1359 {
1360 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1361 //QgsJsonExporter force transform geometry to EPSG:4326
1362 //and the RFC 7946 GeoJSON specification recommends limiting coordinate precision to 6
1363 //Q_UNUSED( prec )
1364
1365 //copy feature so we can modify its geometry as required
1366 QgsFeature f( feature );
1367 QgsGeometry geom = feature.geometry();
1368 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1369 {
1370 mJsonExporter.setIncludeGeometry( true );
1371 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1372 {
1373 QgsRectangle box = geom.boundingBox();
1374 f.setGeometry( QgsGeometry::fromRect( box ) );
1375 }
1376 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1377 {
1378 f.setGeometry( geom.centroid() );
1379 }
1380 }
1381
1382 return mJsonExporter.exportFeature( f, QVariantMap(), id );
1383 }
1384
1385
1386 QDomElement createFeatureGML2( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1387 {
1388 //gml:FeatureMember
1389 QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1390
1391 //qgs:%TYPENAME%
1392 QDomElement typeNameElement = doc.createElement( "qgs:" + params.typeName /*qgs:%TYPENAME%*/ );
1393 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1394 typeNameElement.setAttribute( QStringLiteral( "fid" ), id );
1395 featureElement.appendChild( typeNameElement );
1396
1397 //add geometry column (as gml)
1398 QgsGeometry geom = feature.geometry();
1399 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1400 {
1401 int prec = params.precision;
1402 QgsCoordinateReferenceSystem crs = params.crs;
1403 QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1404 try
1405 {
1406 QgsGeometry transformed = geom;
1407 if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1408 {
1409 geom = transformed;
1410 crs = params.outputCrs;
1411 if ( crs.isGeographic() && !params.crs.isGeographic() )
1412 prec = std::min( params.precision + 3, 6 );
1413 }
1414 }
1415 catch ( QgsCsException &cse )
1416 {
1417 Q_UNUSED( cse )
1418 }
1419
1420 QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1421 QDomElement gmlElem;
1422 QgsGeometry cloneGeom( geom );
1423 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1424 {
1425 cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1426 }
1427 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1428 {
1429 cloneGeom = geom.centroid();
1430 }
1431 else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1432 {
1433 cloneGeom.convertToMultiType();
1434 }
1435 const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1436 if ( abstractGeom )
1437 {
1438 gmlElem = abstractGeom->asGml2( doc, prec, "http://www.opengis.net/gml" );
1439 }
1440
1441 if ( !gmlElem.isNull() )
1442 {
1443 QgsRectangle box = geom.boundingBox();
1444 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1445 QDomElement boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, prec );
1446
1447 if ( crs.isValid() )
1448 {
1449 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1450 gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1451 }
1452
1453 bbElem.appendChild( boxElem );
1454 typeNameElement.appendChild( bbElem );
1455
1456 geomElem.appendChild( gmlElem );
1457 typeNameElement.appendChild( geomElem );
1458 }
1459 }
1460
1461 //read all attribute values from the feature
1462 const QgsAttributes featureAttributes = feature.attributes();
1463 const QgsFields fields = feature.fields();
1464 for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1465 {
1466 int idx = params.attributeIndexes[i];
1467 if ( idx >= fields.count() )
1468 {
1469 continue;
1470 }
1471
1472 const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1473 typeNameElement.appendChild( fieldElem );
1474 }
1475
1476 return featureElement;
1477 }
1478
1479 QDomElement createFeatureGML3( const QgsFeature &feature, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project, const QgsAttributeList &pkAttributes )
1480 {
1481 //gml:FeatureMember
1482 QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1483
1484 //qgs:%TYPENAME%
1485 QDomElement typeNameElement = doc.createElement( QStringLiteral( "qgs:" ) + params.typeName /*qgs:%TYPENAME%*/ );
1486 QString id = QStringLiteral( "%1.%2" ).arg( params.typeName, QgsServerFeatureId::getServerFid( feature, pkAttributes ) );
1487 typeNameElement.setAttribute( QStringLiteral( "gml:id" ), id );
1488 featureElement.appendChild( typeNameElement );
1489
1490 //add geometry column (as gml)
1491 QgsGeometry geom = feature.geometry();
1492 if ( !geom.isNull() && params.withGeom && params.geometryName != QLatin1String( "NONE" ) )
1493 {
1494 int prec = params.precision;
1495 QgsCoordinateReferenceSystem crs = params.crs;
1496 QgsCoordinateTransform mTransform( crs, params.outputCrs, project );
1497 try
1498 {
1499 QgsGeometry transformed = geom;
1500 if ( transformed.transform( mTransform ) == Qgis::GeometryOperationResult::Success )
1501 {
1502 geom = transformed;
1503 crs = params.outputCrs;
1504 if ( crs.isGeographic() && !params.crs.isGeographic() )
1505 prec = std::min( params.precision + 3, 6 );
1506 }
1507 }
1508 catch ( QgsCsException &cse )
1509 {
1510 Q_UNUSED( cse )
1511 }
1512
1513 QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
1514 QDomElement gmlElem;
1515 QgsGeometry cloneGeom( geom );
1516 if ( params.geometryName == QLatin1String( "EXTENT" ) )
1517 {
1518 cloneGeom = QgsGeometry::fromRect( geom.boundingBox() );
1519 }
1520 else if ( params.geometryName == QLatin1String( "CENTROID" ) )
1521 {
1522 cloneGeom = geom.centroid();
1523 }
1524 else if ( params.forceGeomToMulti && ! QgsWkbTypes::isMultiType( geom.wkbType() ) )
1525 {
1526 cloneGeom.convertToMultiType();
1527 }
1528 const QgsAbstractGeometry *abstractGeom = cloneGeom.constGet();
1529 if ( abstractGeom )
1530 {
1531 gmlElem = abstractGeom->asGml3( doc, prec, "http://www.opengis.net/gml", params.hasAxisInverted ? QgsAbstractGeometry::AxisOrder::YX : QgsAbstractGeometry::AxisOrder::XY );
1532 }
1533
1534 if ( !gmlElem.isNull() )
1535 {
1536 QgsRectangle box = geom.boundingBox();
1537 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
1538 QDomElement boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, params.srsName, params.hasAxisInverted, prec );
1539
1540 if ( crs.isValid() && params.srsName.isEmpty() )
1541 {
1542 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1543 gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1544 }
1545 else if ( !params.srsName.isEmpty() )
1546 {
1547 gmlElem.setAttribute( QStringLiteral( "srsName" ), params.srsName );
1548 }
1549
1550 bbElem.appendChild( boxElem );
1551 typeNameElement.appendChild( bbElem );
1552
1553 geomElem.appendChild( gmlElem );
1554 typeNameElement.appendChild( geomElem );
1555 }
1556 }
1557
1558 //read all attribute values from the feature
1559 const QgsAttributes featureAttributes = feature.attributes();
1560 const QgsFields fields = feature.fields();
1561 for ( int i = 0; i < params.attributeIndexes.count(); ++i )
1562 {
1563 int idx = params.attributeIndexes[i];
1564 if ( idx >= fields.count() )
1565 {
1566 continue;
1567 }
1568
1569 const QDomElement fieldElem = createFieldElement( fields.at( idx ), featureAttributes[idx], doc );
1570 typeNameElement.appendChild( fieldElem );
1571 }
1572
1573 return featureElement;
1574 }
1575
1576 QDomElement createFieldElement( const QgsField &field, const QVariant &value, QDomDocument &doc )
1577 {
1579 const thread_local QRegularExpression sCleanTagNameRegExp( QStringLiteral( "[^\\w\\.-_]" ), QRegularExpression::PatternOption::UseUnicodePropertiesOption );
1580 const QString attributeName = field.name().replace( ' ', '_' ).replace( sCleanTagNameRegExp, QString() );
1581 QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:" ) + attributeName );
1582 if ( QgsVariantUtils::isNull( value ) )
1583 {
1584 fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
1585 }
1586 else
1587 {
1588 const QString fieldText = encodeValueToText( value, setup );
1589 //do we need CDATA
1590 if ( fieldText.indexOf( '<' ) != -1 || fieldText.indexOf( '&' ) != -1 )
1591 {
1592 fieldElem.appendChild( doc.createCDATASection( fieldText ) );
1593 }
1594 else
1595 {
1596 fieldElem.appendChild( doc.createTextNode( fieldText ) );
1597 }
1598 }
1599 return fieldElem;
1600 }
1601
1602 QString encodeValueToText( const QVariant &value, const QgsEditorWidgetSetup &setup )
1603 {
1604 if ( QgsVariantUtils::isNull( value ) )
1605 return QString();
1606
1607 if ( setup.type() == QStringLiteral( "DateTime" ) )
1608 {
1609 // For time fields use const TIME_FORMAT
1610 if ( value.type() == QVariant::Time )
1611 {
1612 return value.toTime().toString( QgsDateTimeFieldFormatter::TIME_FORMAT );
1613 }
1614
1615 // Get editor widget setup config
1616 const QVariantMap config = setup.config();
1617 // Get field format, for ISO format then use const display format
1618 // else use field format saved in editor widget setup config
1619 const QString fieldFormat =
1620 config.value( QStringLiteral( "field_iso_format" ), false ).toBool() ?
1622 config.value( QStringLiteral( "field_format" ), QgsDateTimeFieldFormatter::defaultFormat( value.type() ) ).toString();
1623
1624 // Convert value to date time
1625 QDateTime date = value.toDateTime();
1626 // if not valid try to convert to date with field format
1627 if ( !date.isValid() )
1628 {
1629 date = QDateTime::fromString( value.toString(), fieldFormat );
1630 }
1631 // if the date is valid, convert to string with field format
1632 if ( date.isValid() )
1633 {
1634 return date.toString( fieldFormat );
1635 }
1636 // else provide the value as string
1637 return value.toString();
1638 }
1639 else if ( setup.type() == QStringLiteral( "Range" ) )
1640 {
1641 const QVariantMap config = setup.config();
1642 if ( config.contains( QStringLiteral( "Precision" ) ) )
1643 {
1644 // if precision is defined, use it
1645 bool ok;
1646 int precision( config[ QStringLiteral( "Precision" ) ].toInt( &ok ) );
1647 if ( ok )
1648 return QString::number( value.toDouble(), 'f', precision );
1649 }
1650 }
1651
1652 switch ( value.type() )
1653 {
1654 case QVariant::Int:
1655 case QVariant::UInt:
1656 case QVariant::LongLong:
1657 case QVariant::ULongLong:
1658 case QVariant::Double:
1659 return value.toString();
1660
1661 case QVariant::Bool:
1662 return value.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" );
1663
1664 case QVariant::StringList:
1665 case QVariant::List:
1666 case QVariant::Map:
1667 return QgsJsonUtils::encodeValue( value );
1668
1669 default:
1670 case QVariant::String:
1671 return value.toString();
1672 }
1673 }
1674
1675
1676 } // namespace
1677
1678} // namespace QgsWfs
@ Success
Operation succeeded.
Abstract base class for all geometries.
@ YX
Y comes before X (or lat before lon)
virtual QDomElement asGml2(QDomDocument &doc, int precision=17, const QString &ns="gml", AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const =0
Returns a GML2 representation of the geometry.
virtual void swapXy()=0
Swaps the x and y coordinates from the geometry.
virtual QDomElement asGml3(QDomDocument &doc, int precision=17, const QString &ns="gml", AxisOrder axisOrder=QgsAbstractGeometry::AxisOrder::XY) const =0
Returns a GML3 representation of the geometry.
A helper class that centralizes restrictions given by all the access control filter plugins.
QStringList layerAttributes(const QgsVectorLayer *layer, const QStringList &attributes) const override
Returns the authorized layer attributes.
void filterFeatures(const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures) const override
Filter the features of the layer.
bool layerReadPermission(const QgsMapLayer *layer) const
Returns the layer read right.
A vector of attributes.
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromUserInput(const QString &definition)
Set up this CRS from various text formats.
bool hasAxisInverted() const
Returns whether 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.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
static const QString DISPLAY_FOR_ISO_FORMAT
static QString defaultFormat(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.
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.
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:672
Container of fields for a vector layer.
Definition qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition qgsfields.cpp:59
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
int count() const
Returns number of items.
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
A geometry is the spatial representation of a feature.
QgsWkbTypes::Type wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Handles exporting QgsFeature features to GeoJSON features.
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
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.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
bool isEmpty() const
Returns true if the rectangle is empty.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
QgsServerInterface Class defining interfaces exposed by QGIS Server and made available to plugins.
virtual QgsAccessControl * accessControls() const =0
Gets the registered access control filters.
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
QString value(const QString &key) const
Returns the value of a parameter.
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
QgsServerParameters serverParameters() const
Returns parameters.
QgsServerRequest::Parameters parameters() const
Returns a map of query parameters with keys converted to uppercase.
QMap< QString, QString > Parameters
virtual QByteArray data() const
Returns post/put data Check for QByteArray::isNull() to check if data is available.
QgsServerResponse Class defining response interface passed to services QgsService::executeRequest() m...
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
virtual void flush()
Flushes the current output buffer to the network.
virtual void setHeader(const QString &key, const QString &value)=0
Set Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
Provides a way to retrieve settings by prioritizing according to environment variables,...
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
This is the base class for vector data providers.
virtual QgsAttributeList pkAttributeIndexes() const
Returns list of indexes of fields that make up the primary key.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
QgsStringMap attributeAliases() const
Returns a map of field name to attribute alias.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
Exception thrown in case of malformed request.
Exception thrown when data access violates access controls.
Provides an interface to retrieve and manipulate WFS parameters received from the client.
Format
Output format for the response.
static bool isMultiType(Type type)
Returns true if the WKB type is a multi type.
@ 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.
QString implementationVersion()
Returns the highest version supported by this implementation.
QString serviceUrl(const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings)
Service URL string.
const QString OGC_NAMESPACE
Definition qgswfsutils.h:74
const QString GML_NAMESPACE
Definition qgswfsutils.h:73
const QString WFS_NAMESPACE
Definition qgswfsutils.h:72
getFeatureRequest parseGetFeatureRequestBody(QDomElement &docElem, const QgsProject *project)
Transform RequestBody root element to getFeatureRequest.
getFeatureQuery parseQueryElement(QDomElement &queryElem, const QgsProject *project)
Transform Query element to getFeatureQuery.
const QString QGS_NAMESPACE
Definition qgswfsutils.h:75
getFeatureRequest parseGetFeatureParameters(const QgsProject *project)
Transform parameters to getFeatureRequest.
void parseSortByElement(QDomElement &sortByElem, QgsFeatureRequest &featureRequest, const QString &typeName)
Add SortBy element to featureRequest.
void writeGetFeature(QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request, QgsServerResponse &response)
Output WFS GetFeature response.
QgsFeatureRequest parseFilterElement(const QString &typeName, QDomElement &filterElem, QgsProject *project)
Transform a Filter element to a feature request.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:2466
QMap< QString, QString > QgsStringMap
Definition qgis.h:3022
QList< int > QgsAttributeList
Definition qgsfield.h:26
const QgsField & field
Definition qgsfield.h:476
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