QGIS API Documentation 3.29.0-Master (8c80f25a4f)
qgswmsrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgswmsrenderer.cpp
3 -------------------
4 begin : May 14, 2006
5 copyright : (C) 2006 by Marco Hugentobler
6 (C) 2017 by David Marteau
7 email : marco dot hugentobler at karto dot baug dot ethz dot ch
8 david dot marteau at 3liz dot com
9 ***************************************************************************/
10
11/***************************************************************************
12 * *
13 * This program is free software; you can redistribute it and/or modify *
14 * it under the terms of the GNU General Public License as published by *
15 * the Free Software Foundation; either version 2 of the License, or *
16 * (at your option) any later version. *
17 * *
18 ***************************************************************************/
19
20#include "qgswmsutils.h"
21#include "qgsjsonutils.h"
22#include "qgswmsrenderer.h"
23#include "qgsfilterrestorer.h"
24#include "qgsexception.h"
25#include "qgsfields.h"
26#include "qgsfieldformatter.h"
28#include "qgsfeatureiterator.h"
29#include "qgsgeometry.h"
31#include "qgslayertree.h"
32#include "qgslayoututils.h"
33#include "qgslayertreemodel.h"
34#include "qgslegendrenderer.h"
35#include "qgsmaplayer.h"
36#include "qgsmaplayerlegend.h"
38#include "qgsmaptopixel.h"
39#include "qgsproject.h"
41#include "qgsrasterlayer.h"
42#include "qgsrasterrenderer.h"
43#include "qgsscalecalculator.h"
47#include "qgsvectorlayer.h"
48#include "qgsvectortilelayer.h"
49#include "qgsmessagelog.h"
50#include "qgsrenderer.h"
51#include "qgsfeature.h"
52#include "qgsaccesscontrol.h"
53#include "qgsfeaturerequest.h"
57#include "qgsserverfeatureid.h"
59#include "qgswkbtypes.h"
61#include "qgsannotation.h"
64#include "qgspallabeling.h"
65#include "qgswmsrestorer.h"
66#include "qgsdxfexport.h"
67#include "qgssymbollayerutils.h"
68#include "qgsserverexception.h"
69#include "qgsserverapiutils.h"
71#include "qgsfeaturestore.h"
75#include "qgsdimensionfilter.h"
76#include "qgstextdocument.h"
78
79#include <QImage>
80#include <QPainter>
81#include <QStringList>
82#include <QTemporaryFile>
83#include <QDir>
84#include <QUrl>
85#include <nlohmann/json.hpp>
86
87//for printing
88#include "qgslayoutatlas.h"
89#include "qgslayoutmanager.h"
90#include "qgslayoutexporter.h"
91#include "qgslayoutsize.h"
94#include "qgsprintlayout.h"
96#include "qgslayoutitempage.h"
97#include "qgslayoutitemlabel.h"
98#include "qgslayoutitemlegend.h"
99#include "qgslayoutitemmap.h"
100#include "qgslayoutitemmapgrid.h"
101#include "qgslayoutframe.h"
102#include "qgslayoutitemhtml.h"
104#include "qgsogcutils.h"
105#include "qgsunittypes.h"
106
107namespace QgsWms
108{
110 : mContext( context )
111 {
112 mProject = mContext.project();
113
114 mWmsParameters = mContext.parameters();
115 mWmsParameters.dump();
116 }
117
119 {
120 removeTemporaryLayers();
121 }
122
124 {
125 // get layers
126 std::unique_ptr<QgsWmsRestorer> restorer;
127 restorer.reset( new QgsWmsRestorer( mContext ) );
128
129 // configure layers
130 QList<QgsMapLayer *> layers = mContext.layersToRender();
131 configureLayers( layers );
132
133 // init renderer
134 QgsLegendSettings settings = legendSettings();
135 QgsLegendRenderer renderer( &model, settings );
136
137 // create image
138 std::unique_ptr<QImage> image;
139 const qreal dpmm = mContext.dotsPerMm();
140 const QSizeF minSize = renderer.minimumSize();
141 const QSize size( static_cast<int>( minSize.width() * dpmm ), static_cast<int>( minSize.height() * dpmm ) );
142 if ( !mContext.isValidWidthHeight( size.width(), size.height() ) )
143 {
144 throw QgsServerException( QStringLiteral( "Legend image is too large" ) );
145 }
146 image.reset( createImage( size ) );
147
148 // configure painter
149 QPainter painter( image.get() );
152 QgsScopedRenderContextScaleToMm scaleContext( context );
153 // QGIS 4.0 -- take from real render context instead
155 context.setRendererScale( settings.mapScale() );
156 context.setMapToPixel( QgsMapToPixel( 1 / ( settings.mmPerMapUnit() * context.scaleFactor() ) ) );
158
159 // rendering
160 renderer.drawLegend( context );
161 painter.end();
162
163 return image.release();
164 }
165
167 {
168 // get layers
169 std::unique_ptr<QgsWmsRestorer> restorer;
170 restorer.reset( new QgsWmsRestorer( mContext ) );
171
172 // configure layers
173 QList<QgsMapLayer *> layers = mContext.layersToRender();
174 configureLayers( layers );
175
176 // create image
177 const QSize size( mWmsParameters.widthAsInt(), mWmsParameters.heightAsInt() );
178 //test if legend image is larger than max width/height
179 if ( !mContext.isValidWidthHeight( size.width(), size.height() ) )
180 {
181 throw QgsServerException( QStringLiteral( "Legend image is too large" ) );
182 }
183 std::unique_ptr<QImage> image( createImage( size ) );
184
185 // configure painter
186 const qreal dpmm = mContext.dotsPerMm();
187 std::unique_ptr<QPainter> painter;
188 painter.reset( new QPainter( image.get() ) );
189 painter->setRenderHint( QPainter::Antialiasing, true );
190 painter->scale( dpmm, dpmm );
191
192 // rendering
193 QgsLegendSettings settings = legendSettings();
195 ctx.painter = painter.get();
196 nodeModel.drawSymbol( settings, &ctx, size.height() / dpmm );
197 painter->end();
198
199 return image.release();
200 }
201
203 {
204 // get layers
205 std::unique_ptr<QgsWmsRestorer> restorer;
206 restorer.reset( new QgsWmsRestorer( mContext ) );
207
208 // configure layers
209 QList<QgsMapLayer *> layers = mContext.layersToRender();
210 configureLayers( layers );
211
212 // init renderer
213 QgsLegendSettings settings = legendSettings();
214 QgsLegendRenderer renderer( &model, settings );
215
216 // rendering
217 QgsRenderContext renderContext;
218 return renderer.exportLegendToJson( renderContext );
219 }
220
221 void QgsRenderer::runHitTest( const QgsMapSettings &mapSettings, HitTest &hitTest ) const
222 {
224
225 for ( const QString &id : mapSettings.layerIds() )
226 {
227 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mProject->mapLayer( id ) );
228 if ( !vl || !vl->renderer() )
229 continue;
230
231 if ( vl->hasScaleBasedVisibility() && vl->isInScaleRange( mapSettings.scale() ) )
232 {
233 hitTest[vl] = SymbolSet(); // no symbols -> will not be shown
234 continue;
235 }
236
237 QgsCoordinateTransform tr = mapSettings.layerTransform( vl );
238 context.setCoordinateTransform( tr );
239 context.setExtent( tr.transformBoundingBox( mapSettings.extent(), Qgis::TransformDirection::Reverse ) );
240
241 SymbolSet &usedSymbols = hitTest[vl];
242 runHitTestLayer( vl, usedSymbols, context );
243 }
244 }
245
246 void QgsRenderer::runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols, QgsRenderContext &context ) const
247 {
248 std::unique_ptr< QgsFeatureRenderer > r( vl->renderer()->clone() );
249 bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRenderer::MoreSymbolsPerFeature;
250 r->startRender( context, vl->fields() );
251 QgsFeature f;
252 QgsFeatureRequest request( context.extent() );
253 request.setFlags( QgsFeatureRequest::ExactIntersect );
254 QgsFeatureIterator fi = vl->getFeatures( request );
255 while ( fi.nextFeature( f ) )
256 {
257 context.expressionContext().setFeature( f );
258 if ( moreSymbolsPerFeature )
259 {
260 for ( QgsSymbol *s : r->originalSymbolsForFeature( f, context ) )
261 usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( s ) );
262 }
263 else
264 usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( r->originalSymbolForFeature( f, context ) ) );
265 }
266 r->stopRender( context );
267 }
268
270 {
271 // check size
272 if ( ! mContext.isValidWidthHeight() )
273 {
275 QStringLiteral( "The requested map size is too large" ) );
276 }
277
278 // init layer restorer before doing anything
279 std::unique_ptr<QgsWmsRestorer> restorer;
280 restorer.reset( new QgsWmsRestorer( mContext ) );
281
282 // configure layers
283 QgsMapSettings mapSettings;
285 QList<QgsMapLayer *> layers = mContext.layersToRender();
286 configureLayers( layers, &mapSettings );
287
288 // create the output image and the painter
289 std::unique_ptr<QPainter> painter;
290 std::unique_ptr<QImage> image( createImage( mContext.mapSize() ) );
291
292 // configure map settings (background, DPI, ...)
293 configureMapSettings( image.get(), mapSettings );
294
295 // add layers to map settings
296 mapSettings.setLayers( layers );
297
298 // run hit tests
300 runHitTest( mapSettings, symbols );
301
302 return symbols;
303 }
304
306 {
307 // init layer restorer before doing anything
308 std::unique_ptr<QgsWmsRestorer> restorer;
309 restorer.reset( new QgsWmsRestorer( mContext ) );
310
311 // GetPrint request needs a template parameter
312 const QString templateName = mWmsParameters.composerTemplate();
313 if ( templateName.isEmpty() )
314 {
317 }
318 else if ( QgsServerProjectUtils::wmsRestrictedComposers( *mProject ).contains( templateName ) )
319 {
321 mWmsParameters[QgsWmsParameter::TEMPLATE ] );
322 }
323
324 // check template
325 const QgsLayoutManager *lManager = mProject->layoutManager();
326 QgsPrintLayout *sourceLayout( dynamic_cast<QgsPrintLayout *>( lManager->layoutByName( templateName ) ) );
327 if ( !sourceLayout )
328 {
330 mWmsParameters[QgsWmsParameter::TEMPLATE ] );
331 }
332
333 // Check that layout has at least one page
334 if ( sourceLayout->pageCollection()->pageCount() < 1 )
335 {
337 QStringLiteral( "The template has no pages" ) );
338 }
339
340 std::unique_ptr<QgsPrintLayout> layout( sourceLayout->clone() );
341
342 //atlas print?
343 QgsLayoutAtlas *atlas = nullptr;
344 QStringList atlasPk = mWmsParameters.atlasPk();
345 if ( !atlasPk.isEmpty() ) //atlas print requested?
346 {
347 atlas = layout->atlas();
348 if ( !atlas || !atlas->enabled() )
349 {
350 //error
352 QStringLiteral( "The template has no atlas enabled" ) );
353 }
354
355 QgsVectorLayer *cLayer = atlas->coverageLayer();
356 if ( !cLayer )
357 {
359 QStringLiteral( "The atlas has no coverage layer" ) );
360 }
361
362 int maxAtlasFeatures = QgsServerProjectUtils::wmsMaxAtlasFeatures( *mProject );
363 if ( atlasPk.size() == 1 && atlasPk.at( 0 ) == QLatin1String( "*" ) )
364 {
365 atlas->setFilterFeatures( false );
366 atlas->updateFeatures();
367 if ( atlas->count() > maxAtlasFeatures )
368 {
370 QString( "The project configuration allows printing maximum %1 atlas features at a time" ).arg( maxAtlasFeatures ) );
371 }
372 }
373 else
374 {
375 const QgsAttributeList pkIndexes = cLayer->primaryKeyAttributes();
376 if ( pkIndexes.size() == 0 )
377 {
378 QgsDebugMsgLevel( QStringLiteral( "Atlas print: layer %1 has no primary key attributes" ).arg( cLayer->name() ), 2 );
379 }
380
381 // Handles the pk-less case
382 const int pkIndexesSize {std::max< int >( pkIndexes.size(), 1 )};
383
384 QStringList pkAttributeNames;
385 for ( int pkIndex : std::as_const( pkIndexes ) )
386 {
387 pkAttributeNames.append( cLayer->fields().at( pkIndex ).name() );
388 }
389
390 const int nAtlasFeatures = atlasPk.size() / pkIndexesSize;
391 if ( nAtlasFeatures * pkIndexesSize != atlasPk.size() ) //Test if atlasPk.size() is a multiple of pkIndexesSize. Bail out if not
392 {
394 QStringLiteral( "Wrong number of ATLAS_PK parameters" ) );
395 }
396
397 //number of atlas features might be restricted
398 if ( nAtlasFeatures > maxAtlasFeatures )
399 {
401 QString( "%1 atlas features have been requested, but the project configuration only allows printing %2 atlas features at a time" )
402 .arg( nAtlasFeatures ).arg( maxAtlasFeatures ) );
403 }
404
405 QString filterString;
406 int currentAtlasPk = 0;
407
408 for ( int i = 0; i < nAtlasFeatures; ++i )
409 {
410 if ( i > 0 )
411 {
412 filterString.append( " OR " );
413 }
414
415 filterString.append( "( " );
416
417 // If the layer has no PK attributes, assume FID
418 if ( pkAttributeNames.isEmpty() )
419 {
420 filterString.append( QStringLiteral( "$id = %1" ).arg( atlasPk.at( currentAtlasPk ) ) );
421 ++currentAtlasPk;
422 }
423 else
424 {
425 for ( int j = 0; j < pkIndexes.size(); ++j )
426 {
427 if ( j > 0 )
428 {
429 filterString.append( " AND " );
430 }
431 filterString.append( QgsExpression::createFieldEqualityExpression( pkAttributeNames.at( j ), atlasPk.at( currentAtlasPk ) ) );
432 ++currentAtlasPk;
433 }
434 }
435
436 filterString.append( " )" );
437 }
438
439 atlas->setFilterFeatures( true );
440 QString errorString;
441 atlas->setFilterExpression( filterString, errorString );
442 if ( !errorString.isEmpty() )
443 {
444 throw QgsException( QStringLiteral( "An error occurred during the Atlas print: %1" ).arg( errorString ) );
445 }
446 }
447 }
448
449 // configure layers
450 QgsMapSettings mapSettings;
452 QList<QgsMapLayer *> layers = mContext.layersToRender();
453 configureLayers( layers, &mapSettings );
454
455 // configure map settings (background, DPI, ...)
456 std::unique_ptr<QImage> image( new QImage() );
457 configureMapSettings( image.get(), mapSettings );
458
459 // add layers to map settings
460 mapSettings.setLayers( layers );
461
462 // configure layout
463 configurePrintLayout( layout.get(), mapSettings, atlas );
464
465 QgsLayoutRenderContext &layoutRendererContext = layout->renderContext();
467 const QList<QgsMapLayer *> lyrs = mapSettings.layers();
468
469#ifdef HAVE_SERVER_PYTHON_PLUGINS
470 mContext.accessControl()->resolveFilterFeatures( lyrs );
471 filters.addProvider( mContext.accessControl() );
472#endif
473
474 QHash<const QgsVectorLayer *, QStringList> fltrs;
475 for ( QgsMapLayer *l : lyrs )
476 {
477 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( l ) )
478 {
479 fltrs.insert( vl, dimensionFilter( vl ) );
480 }
481 }
482
483 QgsDimensionFilter dimFilter( fltrs );
484 filters.addProvider( &dimFilter );
485 layoutRendererContext.setFeatureFilterProvider( &filters );
486
487 // Get the temporary output file
488 const QgsWmsParameters::Format format = mWmsParameters.format();
489 const QString extension = QgsWmsParameters::formatAsString( format ).toLower();
490
491 QTemporaryFile tempOutputFile( QDir::tempPath() + '/' + QStringLiteral( "XXXXXX.%1" ).arg( extension ) );
492 if ( !tempOutputFile.open() )
493 {
494 throw QgsException( QStringLiteral( "Could not open temporary file for the GetPrint request." ) );
495
496 }
497
498 QString exportError;
499 if ( format == QgsWmsParameters::SVG )
500 {
501 // Settings for the layout exporter
503 if ( !mWmsParameters.dpi().isEmpty() )
504 {
505 bool ok;
506 double dpi( mWmsParameters.dpi().toDouble( &ok ) );
507 if ( ok )
508 exportSettings.dpi = dpi;
509 }
510 // Set scales
511 exportSettings.predefinedMapScales = QgsLayoutUtils::predefinedScales( layout.get( ) );
512 // Draw selections
514 if ( atlas )
515 {
516 //export first page of atlas
517 atlas->beginRender();
518 if ( atlas->next() )
519 {
520 QgsLayoutExporter atlasSvgExport( atlas->layout() );
521 atlasSvgExport.exportToSvg( tempOutputFile.fileName(), exportSettings );
522 }
523 }
524 else
525 {
526 QgsLayoutExporter exporter( layout.get() );
527 exporter.exportToSvg( tempOutputFile.fileName(), exportSettings );
528 }
529 }
530 else if ( format == QgsWmsParameters::PNG || format == QgsWmsParameters::JPG )
531 {
532 // Settings for the layout exporter
534
535 // Get the dpi from input or use the default
536 double dpi( layout->renderContext().dpi( ) );
537 if ( !mWmsParameters.dpi().isEmpty() )
538 {
539 bool ok;
540 double _dpi = mWmsParameters.dpi().toDouble( &ok );
541 if ( ok )
542 dpi = _dpi;
543 }
544 exportSettings.dpi = dpi;
545 // Set scales
546 exportSettings.predefinedMapScales = QgsLayoutUtils::predefinedScales( layout.get( ) );
547 // Draw selections
549 // Destination image size in px
550 QgsLayoutSize layoutSize( layout->pageCollection()->page( 0 )->sizeWithUnits() );
551
552 QgsLayoutMeasurement width( layout->convertFromLayoutUnits( layoutSize.width(), QgsUnitTypes::LayoutUnit::LayoutMillimeters ) );
553 QgsLayoutMeasurement height( layout->convertFromLayoutUnits( layoutSize.height(), QgsUnitTypes::LayoutUnit::LayoutMillimeters ) );
554
555 const QSize imageSize = QSize( static_cast<int>( width.length() * dpi / 25.4 ), static_cast<int>( height.length() * dpi / 25.4 ) );
556
557 const QString paramWidth = mWmsParameters.width();
558 const QString paramHeight = mWmsParameters.height();
559
560 // Prefer width and height from the http request
561 // Fallback to predefined values from layout
562 // Preserve aspect ratio if only one value is specified
563 if ( !paramWidth.isEmpty() && !paramHeight.isEmpty() )
564 {
565 exportSettings.imageSize = QSize( paramWidth.toInt(), paramHeight.toInt() );
566 }
567 else if ( !paramWidth.isEmpty() && paramHeight.isEmpty() )
568 {
569 exportSettings.imageSize = QSize( paramWidth.toInt(), static_cast<double>( paramWidth.toInt() ) / imageSize.width() * imageSize.height() );
570 }
571 else if ( paramWidth.isEmpty() && !paramHeight.isEmpty() )
572 {
573 exportSettings.imageSize = QSize( static_cast<double>( paramHeight.toInt() ) / imageSize.height() * imageSize.width(), paramHeight.toInt() );
574 }
575 else
576 {
577 exportSettings.imageSize = imageSize;
578 }
579
580 // Export first page only (unless it's a pdf, see below)
581 exportSettings.pages.append( 0 );
582 if ( atlas )
583 {
584 //only can give back one page in server rendering
585 atlas->beginRender();
586 if ( atlas->next() )
587 {
588 QgsLayoutExporter atlasPngExport( atlas->layout() );
589 atlasPngExport.exportToImage( tempOutputFile.fileName(), exportSettings );
590 }
591 else
592 {
593 throw QgsServiceException( QStringLiteral( "Bad request" ), QStringLiteral( "Atlas error: empty atlas." ), QString(), 400 );
594 }
595 }
596 else
597 {
598 QgsLayoutExporter exporter( layout.get() );
599 exporter.exportToImage( tempOutputFile.fileName(), exportSettings );
600 }
601 }
602 else if ( format == QgsWmsParameters::PDF )
603 {
604 // Settings for the layout exporter
606 // TODO: handle size from input ?
607 if ( !mWmsParameters.dpi().isEmpty() )
608 {
609 bool ok;
610 double dpi( mWmsParameters.dpi().toDouble( &ok ) );
611 if ( ok )
612 exportSettings.dpi = dpi;
613 }
614 // Draw selections
616 // Print as raster
617 exportSettings.rasterizeWholeImage = layout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
618 // Set scales
619 exportSettings.predefinedMapScales = QgsLayoutUtils::predefinedScales( layout.get( ) );
620
621 // Export all pages
622 if ( atlas )
623 {
624 QgsLayoutExporter::exportToPdf( atlas, tempOutputFile.fileName(), exportSettings, exportError );
625 }
626 else
627 {
628 QgsLayoutExporter exporter( layout.get() );
629 exporter.exportToPdf( tempOutputFile.fileName(), exportSettings );
630 }
631 }
632 else //unknown format
633 {
635 mWmsParameters[QgsWmsParameter::FORMAT] );
636 }
637
638 if ( atlas )
639 {
640 handlePrintErrors( atlas->layout() );
641 }
642 else
643 {
644 handlePrintErrors( layout.get() );
645 }
646
647 return tempOutputFile.readAll();
648 }
649
650 bool QgsRenderer::configurePrintLayout( QgsPrintLayout *c, const QgsMapSettings &mapSettings, bool atlasPrint )
651 {
652
653 c->renderContext().setSelectionColor( mapSettings.selectionColor() );
654 // Maps are configured first
655 QList<QgsLayoutItemMap *> maps;
656 c->layoutItems<QgsLayoutItemMap>( maps );
657 // Layout maps now use a string UUID as "id", let's assume that the first map
658 // has id 0 and so on ...
659 int mapId = 0;
660
661 for ( const auto &map : std::as_const( maps ) )
662 {
663 QgsWmsParametersComposerMap cMapParams = mWmsParameters.composerMapParameters( mapId );
664 mapId++;
665
666 // If there are no configured layers, we take layers from unprefixed LAYER(S) if any
667 if ( cMapParams.mLayers.isEmpty() )
668 {
669 cMapParams.mLayers = mWmsParameters.composerMapParameters( -1 ).mLayers;
670 }
671
672 if ( !atlasPrint || !map->atlasDriven() ) //No need to extent, scal, rotation set with atlas feature
673 {
674 //map extent is mandatory
675 if ( !cMapParams.mHasExtent )
676 {
677 //remove map from composition if not referenced by the request
678 c->removeLayoutItem( map );
679 continue;
680 }
681 // Change CRS of map set to "project CRS" to match requested CRS
682 // (if map has a valid preset crs then we keep this crs and don't use the
683 // requested crs for this map item)
684 if ( mapSettings.destinationCrs().isValid() && !map->presetCrs().isValid() )
685 map->setCrs( mapSettings.destinationCrs() );
686
687 QgsRectangle r( cMapParams.mExtent );
688 if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) &&
689 mapSettings.destinationCrs().hasAxisInverted() )
690 {
691 r.invert();
692 }
693 map->setExtent( r );
694
695 // scale
696 if ( cMapParams.mScale > 0 )
697 {
698 map->setScale( static_cast<double>( cMapParams.mScale ) );
699 }
700
701 // rotation
702 if ( cMapParams.mRotation )
703 {
704 map->setMapRotation( cMapParams.mRotation );
705 }
706 }
707
708 if ( !map->keepLayerSet() )
709 {
710 QList<QgsMapLayer *> layerSet;
711
712 for ( const auto &layer : std::as_const( cMapParams.mLayers ) )
713 {
714 if ( mContext.isValidGroup( layer.mNickname ) )
715 {
716 QList<QgsMapLayer *> layersFromGroup;
717
718 const QList<QgsMapLayer *> cLayersFromGroup = mContext.layersFromGroup( layer.mNickname );
719 for ( QgsMapLayer *layerFromGroup : cLayersFromGroup )
720 {
721
722 if ( ! layerFromGroup )
723 {
724 continue;
725 }
726
727 layersFromGroup.push_front( layerFromGroup );
728 }
729
730 if ( !layersFromGroup.isEmpty() )
731 {
732 layerSet.append( layersFromGroup );
733 }
734 }
735 else
736 {
737 QgsMapLayer *mlayer = mContext.layer( layer.mNickname );
738
739 if ( ! mlayer )
740 {
741 continue;
742 }
743
744 setLayerStyle( mlayer, layer.mStyle );
745 layerSet << mlayer;
746 }
747 }
748
749 std::reverse( layerSet.begin(), layerSet.end() );
750
751 // If the map is set to follow preset we need to disable follow preset and manually
752 // configure the layers here or the map item internal logic will override and get
753 // the layers from the map theme.
754 QMap<QString, QString> layersStyle;
755 if ( map->followVisibilityPreset() )
756 {
757 const QString presetName = map->followVisibilityPresetName();
758 if ( layerSet.isEmpty() )
759 {
760 // Get the layers from the theme
761 const QgsExpressionContext ex { map->createExpressionContext() };
762 layerSet = map->layersToRender( &ex );
763 }
764 // Disable the theme
765 map->setFollowVisibilityPreset( false );
766
767 // Collect the style of each layer in the theme that has been disabled
768 const QList<QgsMapThemeCollection::MapThemeLayerRecord> mapThemeRecords = QgsProject::instance()->mapThemeCollection()->mapThemeState( presetName ).layerRecords();
769 for ( const auto &layerMapThemeRecord : std::as_const( mapThemeRecords ) )
770 {
771 if ( layerSet.contains( layerMapThemeRecord.layer() ) )
772 {
773 layersStyle.insert( layerMapThemeRecord.layer()->id(),
774 layerMapThemeRecord.layer()->styleManager()->style( layerMapThemeRecord.currentStyle ).xmlData() );
775 }
776 }
777 }
778
779 // Handle highlight layers
780 const QList< QgsMapLayer *> highlights = highlightLayers( cMapParams.mHighlightLayers );
781 for ( const auto &hl : std::as_const( highlights ) )
782 {
783 layerSet.prepend( hl );
784 }
785
786 map->setLayers( layerSet );
787 map->setKeepLayerSet( true );
788
789 // Set style override if a particular style should be used due to a map theme.
790 // It will actualize linked legend symbols too.
791 if ( !layersStyle.isEmpty() )
792 {
793 map->setLayerStyleOverrides( layersStyle );
794 map->setKeepLayerStyles( true );
795 }
796 }
797
798 //grid space x / y
799 if ( cMapParams.mGridX > 0 && cMapParams.mGridY > 0 )
800 {
801 map->grid()->setIntervalX( static_cast<double>( cMapParams.mGridX ) );
802 map->grid()->setIntervalY( static_cast<double>( cMapParams.mGridY ) );
803 }
804 }
805
806 // Labels
807 QList<QgsLayoutItemLabel *> labels;
808 c->layoutItems<QgsLayoutItemLabel>( labels );
809 for ( const auto &label : std::as_const( labels ) )
810 {
811 bool ok = false;
812 const QString labelId = label->id();
813 const QString labelParam = mWmsParameters.layoutParameter( labelId, ok );
814
815 if ( !ok )
816 continue;
817
818 if ( labelParam.isEmpty() )
819 {
820 //remove exported labels referenced in the request
821 //but with empty string
822 c->removeItem( label );
823 delete label;
824 continue;
825 }
826
827 label->setText( labelParam );
828 }
829
830 // HTMLs
831 QList<QgsLayoutItemHtml *> htmls;
832 c->layoutObjects<QgsLayoutItemHtml>( htmls );
833 for ( const auto &html : std::as_const( htmls ) )
834 {
835 if ( html->frameCount() == 0 )
836 continue;
837
838 QgsLayoutFrame *htmlFrame = html->frame( 0 );
839 bool ok = false;
840 const QString htmlId = htmlFrame->id();
841 const QString url = mWmsParameters.layoutParameter( htmlId, ok );
842
843 if ( !ok )
844 {
845 html->update();
846 continue;
847 }
848
849 //remove exported Htmls referenced in the request
850 //but with empty string
851 if ( url.isEmpty() )
852 {
853 c->removeMultiFrame( html );
854 delete html;
855 continue;
856 }
857
858 QUrl newUrl( url );
859 html->setUrl( newUrl );
860 html->update();
861 }
862
863
864 // legends
865 QList<QgsLayoutItemLegend *> legends;
866 c->layoutItems<QgsLayoutItemLegend>( legends );
867 for ( const auto &legend : std::as_const( legends ) )
868 {
869 if ( legend->autoUpdateModel() )
870 {
871 // the legend has an auto-update model
872 // we will update it with map's layers
873 const QgsLayoutItemMap *map = legend->linkedMap();
874 if ( !map )
875 {
876 continue;
877 }
878
879 legend->setAutoUpdateModel( false );
880
881 // get model and layer tree root of the legend
882 QgsLegendModel *model = legend->model();
883 QStringList layerSet;
884 QList<QgsMapLayer *> mapLayers;
885 if ( map->layers().isEmpty() )
886 {
887 // in QGIS desktop, each layer has its legend, including invisible layers
888 // and using maptheme, legend items are automatically filtered
889 mapLayers = mProject->mapLayers( true ).values();
890 }
891 else
892 {
893 mapLayers = map->layers();
894 }
895 const QList<QgsMapLayer *> layerList = mapLayers;
896 for ( const auto &layer : layerList )
897 layerSet << layer->id();
898
899 //setLayerIdsToLegendModel( model, layerSet, map->scale() );
900
901 // get model and layer tree root of the legend
902 QgsLayerTree *root = model->rootGroup();
903
904 // get layerIds find in the layer tree root
905 const QStringList layerIds = root->findLayerIds();
906
907 // find the layer in the layer tree
908 // remove it if the layer id is not in map layerIds
909 for ( const auto &layerId : layerIds )
910 {
911 QgsLayerTreeLayer *nodeLayer = root->findLayer( layerId );
912 if ( !nodeLayer )
913 {
914 continue;
915 }
916 if ( !layerSet.contains( layerId ) )
917 {
918 qobject_cast<QgsLayerTreeGroup *>( nodeLayer->parent() )->removeChildNode( nodeLayer );
919 }
920 else
921 {
922 QgsMapLayer *layer = nodeLayer->layer();
923 if ( !layer->isInScaleRange( map->scale() ) )
924 {
925 qobject_cast<QgsLayerTreeGroup *>( nodeLayer->parent() )->removeChildNode( nodeLayer );
926 }
927 }
928 }
930 }
931 }
932 return true;
933 }
934
936 {
937 // check size
938 if ( ! mContext.isValidWidthHeight() )
939 {
941 QStringLiteral( "The requested map size is too large" ) );
942 }
943
944 // init layer restorer before doing anything
945 std::unique_ptr<QgsWmsRestorer> restorer;
946 restorer.reset( new QgsWmsRestorer( mContext ) );
947
948 // configure layers
949 QList<QgsMapLayer *> layers = mContext.layersToRender();
950
951 QgsMapSettings mapSettings;
953 configureLayers( layers, &mapSettings );
954
955 // create the output image and the painter
956 std::unique_ptr<QPainter> painter;
957 std::unique_ptr<QImage> image( createImage( mContext.mapSize() ) );
958
959 // configure map settings (background, DPI, ...)
960 configureMapSettings( image.get(), mapSettings );
961
962 // add layers to map settings
963 mapSettings.setLayers( layers );
964
965 // rendering step for layers
966 painter.reset( layersRendering( mapSettings, *image ) );
967
968 // rendering step for annotations
969 annotationsRendering( painter.get(), mapSettings );
970
971 // painting is terminated
972 painter->end();
973
974 // scale output image if necessary (required by WMS spec)
975 QImage *scaledImage = scaleImage( image.get() );
976 if ( scaledImage )
977 image.reset( scaledImage );
978
979 // return
980 return image.release();
981 }
982
983 std::unique_ptr<QgsDxfExport> QgsRenderer::getDxf()
984 {
985 // init layer restorer before doing anything
986 std::unique_ptr<QgsWmsRestorer> restorer;
987 restorer.reset( new QgsWmsRestorer( mContext ) );
988
989 // configure layers
990 QList<QgsMapLayer *> layers = mContext.layersToRender();
991 configureLayers( layers );
992
993 // get dxf layers
994 const QStringList attributes = mWmsParameters.dxfLayerAttributes();
995 QList< QgsDxfExport::DxfLayer > dxfLayers;
996 int layerIdx = -1;
997 for ( QgsMapLayer *layer : layers )
998 {
999 layerIdx++;
1000 if ( layer->type() != QgsMapLayerType::VectorLayer )
1001 continue;
1002
1003 // cast for dxf layers
1004 QgsVectorLayer *vlayer = static_cast<QgsVectorLayer *>( layer );
1005
1006 // get the layer attribute used in dxf
1007 int layerAttribute = -1;
1008 if ( attributes.size() > layerIdx )
1009 {
1010 layerAttribute = vlayer->fields().indexFromName( attributes[ layerIdx ] );
1011 }
1012
1013 dxfLayers.append( QgsDxfExport::DxfLayer( vlayer, layerAttribute ) );
1014 }
1015
1016 //map extent
1017 QgsRectangle mapExtent = mWmsParameters.bboxAsRectangle();
1018
1019 QString crs = mWmsParameters.crs();
1020 if ( crs.compare( QStringLiteral( "CRS:84" ), Qt::CaseInsensitive ) == 0 )
1021 {
1022 crs = QStringLiteral( "EPSG:4326" );
1023 mapExtent.invert();
1024 }
1025 else if ( crs.isEmpty() )
1026 {
1027 crs = QStringLiteral( "EPSG:4326" );
1028 }
1029
1031
1032 if ( !outputCRS.isValid() )
1033 {
1035 QgsWmsParameter parameter;
1036
1037 if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) )
1038 {
1040 parameter = mWmsParameters[ QgsWmsParameter::CRS ];
1041 }
1042 else
1043 {
1045 parameter = mWmsParameters[ QgsWmsParameter::SRS ];
1046 }
1047
1048 throw QgsBadRequestException( code, parameter );
1049 }
1050
1051 //then set destinationCrs
1052
1053 // Change x- and y- of BBOX for WMS 1.3.0 if axis inverted
1054 if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) && outputCRS.hasAxisInverted() )
1055 {
1056 mapExtent.invert();
1057 }
1058
1059
1060 // add layers to dxf
1061 std::unique_ptr<QgsDxfExport> dxf = std::make_unique<QgsDxfExport>();
1062 dxf->setExtent( mapExtent );
1063 dxf->setDestinationCrs( outputCRS );
1064 dxf->addLayers( dxfLayers );
1065 dxf->setLayerTitleAsName( mWmsParameters.dxfUseLayerTitleAsName() );
1066 dxf->setSymbologyExport( mWmsParameters.dxfMode() );
1067 if ( mWmsParameters.dxfFormatOptions().contains( QgsWmsParameters::DxfFormatOption::SCALE ) )
1068 {
1069 dxf->setSymbologyScale( mWmsParameters.dxfScale() );
1070 }
1071
1072 dxf->setForce2d( mWmsParameters.isForce2D() );
1073 QgsDxfExport::Flags flags;
1074 if ( mWmsParameters.noMText() )
1075 flags.setFlag( QgsDxfExport::Flag::FlagNoMText );
1076
1077 dxf->setFlags( flags );
1078
1079 return dxf;
1080 }
1081
1082 static void infoPointToMapCoordinates( int i, int j, QgsPointXY *infoPoint, const QgsMapSettings &mapSettings )
1083 {
1084 //check if i, j are in the pixel range of the image
1085 if ( i < 0 || i > mapSettings.outputSize().width() )
1086 {
1088 param.mValue = i;
1090 param );
1091 }
1092
1093 if ( j < 0 || j > mapSettings.outputSize().height() )
1094 {
1095 QgsWmsParameter param( QgsWmsParameter::J );
1096 param.mValue = j;
1098 param );
1099 }
1100
1101 double xRes = mapSettings.extent().width() / mapSettings.outputSize().width();
1102 double yRes = mapSettings.extent().height() / mapSettings.outputSize().height();
1103 infoPoint->setX( mapSettings.extent().xMinimum() + i * xRes + xRes / 2.0 );
1104 infoPoint->setY( mapSettings.extent().yMaximum() - j * yRes - yRes / 2.0 );
1105 }
1106
1107 QByteArray QgsRenderer::getFeatureInfo( const QString &version )
1108 {
1109 // Verifying Mandatory parameters
1110 // The QUERY_LAYERS parameter is Mandatory
1111 if ( mWmsParameters.queryLayersNickname().isEmpty() )
1112 {
1114 mWmsParameters[QgsWmsParameter::QUERY_LAYERS] );
1115 }
1116
1117 // The I/J parameters are Mandatory if they are not replaced by X/Y or FILTER or FILTER_GEOM
1118 const bool ijDefined = !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty();
1119 const bool xyDefined = !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty();
1120 const bool filtersDefined = !mWmsParameters.filters().isEmpty();
1121 const bool filterGeomDefined = !mWmsParameters.filterGeom().isEmpty();
1122
1123 if ( !ijDefined && !xyDefined && !filtersDefined && !filterGeomDefined )
1124 {
1125 QgsWmsParameter parameter = mWmsParameters[QgsWmsParameter::I];
1126
1127 if ( mWmsParameters.j().isEmpty() )
1128 parameter = mWmsParameters[QgsWmsParameter::J];
1129
1131 }
1132
1133 const QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat();
1134 if ( infoFormat == QgsWmsParameters::Format::NONE )
1135 {
1137 mWmsParameters[QgsWmsParameter::INFO_FORMAT] );
1138 }
1139
1140 // create the mapSettings and the output image
1141 std::unique_ptr<QImage> outputImage( createImage( mContext.mapSize() ) );
1142
1143 // init layer restorer before doing anything
1144 std::unique_ptr<QgsWmsRestorer> restorer;
1145 restorer.reset( new QgsWmsRestorer( mContext ) );
1146
1147 // The CRS parameter is considered as mandatory in configureMapSettings
1148 // but in the case of filter parameter, CRS parameter has not to be mandatory
1149 bool mandatoryCrsParam = true;
1150 if ( filtersDefined && !ijDefined && !xyDefined && mWmsParameters.crs().isEmpty() )
1151 {
1152 mandatoryCrsParam = false;
1153 }
1154
1155 // configure map settings (background, DPI, ...)
1156 QgsMapSettings mapSettings;
1158 configureMapSettings( outputImage.get(), mapSettings, mandatoryCrsParam );
1159
1160 // compute scale denominator
1161 QgsScaleCalculator scaleCalc( ( outputImage->logicalDpiX() + outputImage->logicalDpiY() ) / 2, mapSettings.destinationCrs().mapUnits() );
1162 const double scaleDenominator = scaleCalc.calculate( mWmsParameters.bboxAsRectangle(), outputImage->width() );
1163
1164 // configure layers
1165 QgsWmsRenderContext context = mContext;
1166 context.setScaleDenominator( scaleDenominator );
1167
1168 QList<QgsMapLayer *> layers = context.layersToRender();
1169 configureLayers( layers, &mapSettings );
1170
1171 // add layers to map settings
1172 mapSettings.setLayers( layers );
1173
1174#ifdef HAVE_SERVER_PYTHON_PLUGINS
1175 mContext.accessControl()->resolveFilterFeatures( mapSettings.layers() );
1176#endif
1177
1178 QDomDocument result = featureInfoDocument( layers, mapSettings, outputImage.get(), version );
1179
1180 QByteArray ba;
1181
1182 if ( infoFormat == QgsWmsParameters::Format::TEXT )
1183 ba = convertFeatureInfoToText( result );
1184 else if ( infoFormat == QgsWmsParameters::Format::HTML )
1185 ba = convertFeatureInfoToHtml( result );
1186 else if ( infoFormat == QgsWmsParameters::Format::JSON )
1187 ba = convertFeatureInfoToJson( layers, result );
1188 else
1189 ba = result.toByteArray();
1190
1191 return ba;
1192 }
1193
1194 QImage *QgsRenderer::createImage( const QSize &size ) const
1195 {
1196 std::unique_ptr<QImage> image;
1197
1198 // use alpha channel only if necessary because it slows down performance
1199 QgsWmsParameters::Format format = mWmsParameters.format();
1200 bool transparent = mWmsParameters.transparentAsBool();
1201
1202 if ( transparent && format != QgsWmsParameters::JPG )
1203 {
1204 image = std::make_unique<QImage>( size, QImage::Format_ARGB32_Premultiplied );
1205 image->fill( 0 );
1206 }
1207 else
1208 {
1209 image = std::make_unique<QImage>( size, QImage::Format_RGB32 );
1210 image->fill( mWmsParameters.backgroundColorAsColor() );
1211 }
1212
1213 // Check that image was correctly created
1214 if ( image->isNull() )
1215 {
1216 throw QgsException( QStringLiteral( "createImage: image could not be created, check for out of memory conditions" ) );
1217 }
1218
1219 const int dpm = static_cast<int>( mContext.dotsPerMm() * 1000.0 );
1220 image->setDotsPerMeterX( dpm );
1221 image->setDotsPerMeterY( dpm );
1222
1223 return image.release();
1224 }
1225
1226 void QgsRenderer::configureMapSettings( const QPaintDevice *paintDevice, QgsMapSettings &mapSettings, bool mandatoryCrsParam )
1227 {
1228 if ( !paintDevice )
1229 {
1230 throw QgsException( QStringLiteral( "configureMapSettings: no paint device" ) );
1231 }
1232
1233 mapSettings.setOutputSize( QSize( paintDevice->width(), paintDevice->height() ) );
1234 // Recalculate from input DPI: do not take the (integer) value from paint device
1235 // because it loose precision!
1236 mapSettings.setOutputDpi( mContext.dotsPerMm() * 25.4 );
1237
1238 //map extent
1239 QgsRectangle mapExtent = mWmsParameters.bboxAsRectangle();
1240 if ( !mWmsParameters.bbox().isEmpty() && mapExtent.isEmpty() )
1241 {
1243 mWmsParameters[QgsWmsParameter::BBOX] );
1244 }
1245
1246 QString crs = mWmsParameters.crs();
1247 if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
1248 {
1249 crs = QString( "EPSG:4326" );
1250 mapExtent.invert();
1251 }
1252 else if ( crs.isEmpty() && !mandatoryCrsParam )
1253 {
1254 crs = QString( "EPSG:4326" );
1255 }
1256
1258
1259 //wms spec says that CRS parameter is mandatory.
1261 if ( !outputCRS.isValid() )
1262 {
1264 QgsWmsParameter parameter;
1265
1266 if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) )
1267 {
1269 parameter = mWmsParameters[ QgsWmsParameter::CRS ];
1270 }
1271 else
1272 {
1274 parameter = mWmsParameters[ QgsWmsParameter::SRS ];
1275 }
1276
1277 throw QgsBadRequestException( code, parameter );
1278 }
1279
1280 //then set destinationCrs
1281 mapSettings.setDestinationCrs( outputCRS );
1282
1283 // Change x- and y- of BBOX for WMS 1.3.0 if axis inverted
1284 if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) && outputCRS.hasAxisInverted() )
1285 {
1286 mapExtent.invert();
1287 }
1288
1289 mapSettings.setExtent( mapExtent );
1290
1291 // set the extent buffer
1292 mapSettings.setExtentBuffer( mContext.mapTileBuffer( paintDevice->width() ) );
1293
1294 /* Define the background color
1295 * Transparent or colored
1296 */
1297 QgsWmsParameters::Format format = mWmsParameters.format();
1298 bool transparent = mWmsParameters.transparentAsBool();
1299 QColor backgroundColor = mWmsParameters.backgroundColorAsColor();
1300
1301 //set background color
1302 if ( transparent && format != QgsWmsParameters::JPG )
1303 {
1304 mapSettings.setBackgroundColor( QColor( 0, 0, 0, 0 ) );
1305 }
1306 else if ( backgroundColor.isValid() )
1307 {
1308 mapSettings.setBackgroundColor( backgroundColor );
1309 }
1310
1311 // add context from project (global variables, ...)
1312 QgsExpressionContext context = mProject->createExpressionContext();
1313 context << QgsExpressionContextUtils::mapSettingsScope( mapSettings );
1314 mapSettings.setExpressionContext( context );
1315
1316 // add labeling engine settings
1317 mapSettings.setLabelingEngineSettings( mProject->labelingEngineSettings() );
1318
1319 // enable rendering optimization
1321
1323
1324 // set selection color
1325 mapSettings.setSelectionColor( mProject->selectionColor() );
1326
1327 // Set WMS temporal properties
1328 // Note that this cannot parse multiple time instants while the vector dimensions implementation can
1329 const QString timeString { mWmsParameters.dimensionValues().value( QStringLiteral( "TIME" ), QString() ) };
1330 if ( ! timeString.isEmpty() )
1331 {
1332 bool isValidTemporalRange { true };
1333 QgsDateTimeRange range;
1334 // First try with a simple date/datetime instant
1335 const QDateTime dt { QDateTime::fromString( timeString, Qt::DateFormat::ISODateWithMs ) };
1336 if ( dt.isValid() )
1337 {
1338 range = QgsDateTimeRange( dt, dt );
1339 }
1340 else // parse as an interval
1341 {
1342 try
1343 {
1345 }
1346 catch ( const QgsServerApiBadRequestException &ex )
1347 {
1348 isValidTemporalRange = false;
1349 QgsMessageLog::logMessage( QStringLiteral( "Could not parse TIME parameter into a temporal range" ), "Server", Qgis::MessageLevel::Warning );
1350 }
1351 }
1352
1353 if ( isValidTemporalRange )
1354 {
1355 mIsTemporal = true;
1356 mapSettings.setIsTemporal( true );
1357 mapSettings.setTemporalRange( range );
1358 }
1359
1360 }
1361 }
1362
1363 QDomDocument QgsRenderer::featureInfoDocument( QList<QgsMapLayer *> &layers, const QgsMapSettings &mapSettings,
1364 const QImage *outputImage, const QString &version ) const
1365 {
1366 const QStringList queryLayers = mContext.flattenedQueryLayers( mContext.parameters().queryLayersNickname() );
1367
1368 bool ijDefined = ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() );
1369
1370 bool xyDefined = ( !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty() );
1371
1372 bool filtersDefined = !mWmsParameters.filters().isEmpty();
1373
1374 bool filterGeomDefined = !mWmsParameters.filterGeom().isEmpty();
1375
1376 int featureCount = mWmsParameters.featureCountAsInt();
1377 if ( featureCount < 1 )
1378 {
1379 featureCount = 1;
1380 }
1381
1382 int i = mWmsParameters.iAsInt();
1383 int j = mWmsParameters.jAsInt();
1384 if ( xyDefined && !ijDefined )
1385 {
1386 i = mWmsParameters.xAsInt();
1387 j = mWmsParameters.yAsInt();
1388 }
1389 int width = mWmsParameters.widthAsInt();
1390 int height = mWmsParameters.heightAsInt();
1391 if ( ( i != -1 && j != -1 && width != 0 && height != 0 ) && ( width != outputImage->width() || height != outputImage->height() ) )
1392 {
1393 i *= ( outputImage->width() / static_cast<double>( width ) );
1394 j *= ( outputImage->height() / static_cast<double>( height ) );
1395 }
1396
1397 // init search variables
1398 std::unique_ptr<QgsRectangle> featuresRect;
1399 std::unique_ptr<QgsGeometry> filterGeom;
1400 std::unique_ptr<QgsPointXY> infoPoint;
1401
1402 if ( i != -1 && j != -1 )
1403 {
1404 infoPoint.reset( new QgsPointXY() );
1405 infoPointToMapCoordinates( i, j, infoPoint.get(), mapSettings );
1406 }
1407 else if ( filtersDefined )
1408 {
1409 featuresRect.reset( new QgsRectangle() );
1410 }
1411 else if ( filterGeomDefined )
1412 {
1413 filterGeom.reset( new QgsGeometry( QgsGeometry::fromWkt( mWmsParameters.filterGeom() ) ) );
1414 }
1415
1416 QDomDocument result;
1417 const QDomNode header = result.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"UTF-8\"" ) );
1418 result.appendChild( header );
1419
1420 QDomElement getFeatureInfoElement;
1421 QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat();
1422 if ( infoFormat == QgsWmsParameters::Format::GML )
1423 {
1424 getFeatureInfoElement = result.createElement( QStringLiteral( "wfs:FeatureCollection" ) );
1425 getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:wfs" ), QStringLiteral( "http://www.opengis.net/wfs" ) );
1426 getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
1427 getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) );
1428 getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ows" ), QStringLiteral( "http://www.opengis.net/ows" ) );
1429 getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
1430 getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:qgs" ), QStringLiteral( "http://qgis.org/gml" ) );
1431 getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
1432 getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd http://qgis.org/gml" ) );
1433 }
1434 else
1435 {
1436 QString featureInfoElemName = QgsServerProjectUtils::wmsFeatureInfoDocumentElement( *mProject );
1437 if ( featureInfoElemName.isEmpty() )
1438 {
1439 featureInfoElemName = QStringLiteral( "GetFeatureInfoResponse" );
1440 }
1441 QString featureInfoElemNs = QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( *mProject );
1442 if ( featureInfoElemNs.isEmpty() )
1443 {
1444 getFeatureInfoElement = result.createElement( featureInfoElemName );
1445 }
1446 else
1447 {
1448 getFeatureInfoElement = result.createElementNS( featureInfoElemNs, featureInfoElemName );
1449 }
1450 //feature info schema
1451 QString featureInfoSchema = QgsServerProjectUtils::wmsFeatureInfoSchema( *mProject );
1452 if ( !featureInfoSchema.isEmpty() )
1453 {
1454 getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
1455 getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), featureInfoSchema );
1456 }
1457 }
1458 result.appendChild( getFeatureInfoElement );
1459
1460 //Render context is needed to determine feature visibility for vector layers
1461 QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings );
1462
1463 bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *mProject );
1464
1465 //layers can have assigned a different name for GetCapabilities
1466 QHash<QString, QString> layerAliasMap = QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( *mProject );
1467
1468 for ( const QString &queryLayer : queryLayers )
1469 {
1470 bool validLayer = false;
1471 bool queryableLayer = true;
1472 for ( QgsMapLayer *layer : std::as_const( layers ) )
1473 {
1474 if ( queryLayer == mContext.layerNickname( *layer ) )
1475 {
1476 validLayer = true;
1477 queryableLayer = layer->flags().testFlag( QgsMapLayer::Identifiable );
1478 if ( !queryableLayer )
1479 {
1480 break;
1481 }
1482
1483 QDomElement layerElement;
1484 if ( infoFormat == QgsWmsParameters::Format::GML )
1485 {
1486 layerElement = getFeatureInfoElement;
1487 }
1488 else
1489 {
1490 layerElement = result.createElement( QStringLiteral( "Layer" ) );
1491 QString layerName = queryLayer;
1492
1493 //check if the layer is given a different name for GetFeatureInfo output
1494 QHash<QString, QString>::const_iterator layerAliasIt = layerAliasMap.constFind( layerName );
1495 if ( layerAliasIt != layerAliasMap.constEnd() )
1496 {
1497 layerName = layerAliasIt.value();
1498 }
1499
1500 layerElement.setAttribute( QStringLiteral( "name" ), layerName );
1501 getFeatureInfoElement.appendChild( layerElement );
1502 if ( sia2045 ) //the name might not be unique after alias replacement
1503 {
1504 layerElement.setAttribute( QStringLiteral( "id" ), layer->id() );
1505 }
1506 }
1507
1508 if ( layer->type() == QgsMapLayerType::VectorLayer )
1509 {
1510 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
1511 if ( vectorLayer )
1512 {
1513 ( void )featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, featuresRect.get(), filterGeom.get() );
1514 break;
1515 }
1516 }
1517 else
1518 {
1519 QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer );
1520 if ( !rasterLayer )
1521 {
1522 break;
1523 }
1524 if ( !infoPoint )
1525 {
1526 break;
1527 }
1528 QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( layer, *( infoPoint.get() ) );
1529 if ( !rasterLayer->extent().contains( layerInfoPoint ) )
1530 {
1531 break;
1532 }
1533 if ( infoFormat == QgsWmsParameters::Format::GML )
1534 {
1535 layerElement = result.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1536 getFeatureInfoElement.appendChild( layerElement );
1537 }
1538
1539 ( void )featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, result, layerElement, version );
1540 }
1541 break;
1542 }
1543 }
1544 if ( !validLayer && !mContext.isValidLayer( queryLayer ) && !mContext.isValidGroup( queryLayer ) )
1545 {
1546 QgsWmsParameter param( QgsWmsParameter::LAYER );
1547 param.mValue = queryLayer;
1549 param );
1550 }
1551 else if ( ( validLayer && !queryableLayer ) || ( !validLayer && mContext.isValidGroup( queryLayer ) ) )
1552 {
1553 QgsWmsParameter param( QgsWmsParameter::LAYER );
1554 param.mValue = queryLayer;
1555 // Check if this layer belongs to a group and the group has any queryable layers
1556 bool hasGroupAndQueryable { false };
1557 if ( ! mContext.parameters().queryLayersNickname().contains( queryLayer ) )
1558 {
1559 // Find which group this layer belongs to
1560 const QStringList constNicks { mContext.parameters().queryLayersNickname() };
1561 for ( const QString &ql : constNicks )
1562 {
1563 if ( mContext.layerGroups().contains( ql ) )
1564 {
1565 const QList<QgsMapLayer *> constLayers { mContext.layerGroups()[ql] };
1566 for ( const QgsMapLayer *ml : constLayers )
1567 {
1568 if ( ( ! ml->shortName().isEmpty() && ml->shortName() == queryLayer ) || ( ml->name() == queryLayer ) )
1569 {
1570 param.mValue = ql;
1571 }
1572 if ( ml->flags().testFlag( QgsMapLayer::Identifiable ) )
1573 {
1574 hasGroupAndQueryable = true;
1575 break;
1576 }
1577 }
1578 break;
1579 }
1580 }
1581 }
1582 // Only throw if it's not a group or the group has no queryable children
1583 if ( ! hasGroupAndQueryable )
1584 {
1586 param );
1587 }
1588 }
1589 }
1590
1591 if ( featuresRect )
1592 {
1593 if ( infoFormat == QgsWmsParameters::Format::GML )
1594 {
1595 QDomElement bBoxElem = result.createElement( QStringLiteral( "gml:boundedBy" ) );
1596 QDomElement boxElem;
1597 int gmlVersion = mWmsParameters.infoFormatVersion();
1598 if ( gmlVersion < 3 )
1599 {
1600 boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect.get(), result, 8 );
1601 }
1602 else
1603 {
1604 boxElem = QgsOgcUtils::rectangleToGMLEnvelope( featuresRect.get(), result, 8 );
1605 }
1606
1607 QgsCoordinateReferenceSystem crs = mapSettings.destinationCrs();
1608 if ( crs.isValid() )
1609 {
1610 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
1611 }
1612 bBoxElem.appendChild( boxElem );
1613 getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
1614 }
1615 else
1616 {
1617 QDomElement bBoxElem = result.createElement( QStringLiteral( "BoundingBox" ) );
1618 bBoxElem.setAttribute( QStringLiteral( "CRS" ), mapSettings.destinationCrs().authid() );
1619 bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( featuresRect->xMinimum(), 8 ) );
1620 bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( featuresRect->xMaximum(), 8 ) );
1621 bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( featuresRect->yMinimum(), 8 ) );
1622 bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( featuresRect->yMaximum(), 8 ) );
1623 getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
1624 }
1625 }
1626
1627 if ( sia2045 && infoFormat == QgsWmsParameters::Format::XML )
1628 {
1629 convertFeatureInfoToSia2045( result );
1630 }
1631
1632 return result;
1633 }
1634
1635 bool QgsRenderer::featureInfoFromVectorLayer( QgsVectorLayer *layer,
1636 const QgsPointXY *infoPoint,
1637 int nFeatures,
1638 QDomDocument &infoDocument,
1639 QDomElement &layerElement,
1640 const QgsMapSettings &mapSettings,
1641 QgsRenderContext &renderContext,
1642 const QString &version,
1643 QgsRectangle *featureBBox,
1644 QgsGeometry *filterGeom ) const
1645 {
1646 if ( !layer )
1647 {
1648 return false;
1649 }
1650
1651 QgsFeatureRequest fReq;
1652
1653 // Transform filter geometry to layer CRS
1654 std::unique_ptr<QgsGeometry> layerFilterGeom;
1655 if ( filterGeom )
1656 {
1657 layerFilterGeom.reset( new QgsGeometry( *filterGeom ) );
1658 layerFilterGeom->transform( QgsCoordinateTransform( mapSettings.destinationCrs(), layer->crs(), fReq.transformContext() ) );
1659 }
1660
1661 //we need a selection rect (0.01 of map width)
1662 QgsRectangle mapRect = mapSettings.extent();
1663 QgsRectangle layerRect = mapSettings.mapToLayerCoordinates( layer, mapRect );
1664
1665
1666 QgsRectangle searchRect;
1667
1668 //info point could be 0 in case there is only an attribute filter
1669 if ( infoPoint )
1670 {
1671 searchRect = featureInfoSearchRect( layer, mapSettings, renderContext, *infoPoint );
1672 }
1673 else if ( layerFilterGeom )
1674 {
1675 searchRect = layerFilterGeom->boundingBox();
1676 }
1677 else if ( !mWmsParameters.bbox().isEmpty() )
1678 {
1679 searchRect = layerRect;
1680 }
1681
1682 //do a select with searchRect and go through all the features
1683
1684 QgsFeature feature;
1685 QgsAttributes featureAttributes;
1686 int featureCounter = 0;
1687 layer->updateFields();
1688 const QgsFields fields = layer->fields();
1689 bool addWktGeometry = ( QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) && mWmsParameters.withGeometry() );
1690 bool segmentizeWktGeometry = QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( *mProject );
1691
1692 bool hasGeometry = QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) || addWktGeometry || featureBBox || layerFilterGeom;
1694
1695 if ( ! searchRect.isEmpty() )
1696 {
1697 fReq.setFilterRect( searchRect );
1698 }
1699 else
1700 {
1702 }
1703
1704
1705 if ( layerFilterGeom )
1706 {
1707 fReq.setFilterExpression( QString( "intersects( $geometry, geom_from_wkt('%1') )" ).arg( layerFilterGeom->asWkt() ) );
1708 }
1709
1710 mFeatureFilter.filterFeatures( layer, fReq );
1711
1712#ifdef HAVE_SERVER_PYTHON_PLUGINS
1713 mContext.accessControl()->filterFeatures( layer, fReq );
1714
1715 QStringList attributes;
1716 for ( const QgsField &field : fields )
1717 {
1718 attributes.append( field.name() );
1719 }
1720 attributes = mContext.accessControl()->layerAttributes( layer, attributes );
1721 fReq.setSubsetOfAttributes( attributes, layer->fields() );
1722#endif
1723
1724 QgsFeatureIterator fit = layer->getFeatures( fReq );
1725 std::unique_ptr< QgsFeatureRenderer > r2( layer->renderer() ? layer->renderer()->clone() : nullptr );
1726 if ( r2 )
1727 {
1728 r2->startRender( renderContext, layer->fields() );
1729 }
1730
1731 bool featureBBoxInitialized = false;
1732 while ( fit.nextFeature( feature ) )
1733 {
1734 if ( layer->wkbType() == QgsWkbTypes::NoGeometry && ! searchRect.isEmpty() )
1735 {
1736 break;
1737 }
1738
1739 ++featureCounter;
1740 if ( featureCounter > nFeatures )
1741 {
1742 break;
1743 }
1744
1745 renderContext.expressionContext().setFeature( feature );
1746
1747 if ( layer->wkbType() != QgsWkbTypes::NoGeometry && ! searchRect.isEmpty() )
1748 {
1749 if ( !r2 )
1750 {
1751 continue;
1752 }
1753
1754 //check if feature is rendered at all
1755 bool render = r2->willRenderFeature( feature, renderContext );
1756 if ( !render )
1757 {
1758 continue;
1759 }
1760 }
1761
1762 QgsRectangle box;
1763 if ( layer->wkbType() != QgsWkbTypes::NoGeometry && hasGeometry )
1764 {
1765 box = mapSettings.layerExtentToOutputExtent( layer, feature.geometry().boundingBox() );
1766 if ( featureBBox ) //extend feature info bounding box if requested
1767 {
1768 if ( !featureBBoxInitialized && featureBBox->isEmpty() )
1769 {
1770 *featureBBox = box;
1771 featureBBoxInitialized = true;
1772 }
1773 else
1774 {
1775 featureBBox->combineExtentWith( box );
1776 }
1777 }
1778 }
1779
1781 if ( layer->crs() != mapSettings.destinationCrs() )
1782 {
1783 outputCrs = mapSettings.destinationCrs();
1784 }
1785
1786 if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML )
1787 {
1788 bool withGeom = layer->wkbType() != QgsWkbTypes::NoGeometry && addWktGeometry;
1789 int gmlVersion = mWmsParameters.infoFormatVersion();
1790 QString typeName = mContext.layerNickname( *layer );
1791 QDomElement elem = createFeatureGML(
1792 &feature, layer, infoDocument, outputCrs, mapSettings, typeName, withGeom, gmlVersion
1793#ifdef HAVE_SERVER_PYTHON_PLUGINS
1794 , &attributes
1795#endif
1796 );
1797 QDomElement featureMemberElem = infoDocument.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
1798 featureMemberElem.appendChild( elem );
1799 layerElement.appendChild( featureMemberElem );
1800 continue;
1801 }
1802 else
1803 {
1804 QDomElement featureElement = infoDocument.createElement( QStringLiteral( "Feature" ) );
1805 featureElement.setAttribute( QStringLiteral( "id" ), QgsServerFeatureId::getServerFid( feature, layer->dataProvider()->pkAttributeIndexes() ) );
1806 layerElement.appendChild( featureElement );
1807
1808 featureAttributes = feature.attributes();
1809 QgsEditFormConfig editConfig = layer->editFormConfig();
1811 {
1812 writeAttributesTabLayout( editConfig, layer, fields, featureAttributes, infoDocument, featureElement, renderContext
1813#ifdef HAVE_SERVER_PYTHON_PLUGINS
1814 , &attributes
1815#endif
1816 );
1817 }
1818 else
1819 {
1820 for ( int i = 0; i < featureAttributes.count(); ++i )
1821 {
1822 writeVectorLayerAttribute( i, layer, fields, featureAttributes, infoDocument, featureElement, renderContext
1823#ifdef HAVE_SERVER_PYTHON_PLUGINS
1824 , &attributes
1825#endif
1826 );
1827 }
1828 }
1829
1830 //add maptip attribute based on html/expression (in case there is no maptip attribute)
1831 QString mapTip = layer->mapTipTemplate();
1832 if ( !mapTip.isEmpty() && mWmsParameters.withMapTip() )
1833 {
1834 QDomElement maptipElem = infoDocument.createElement( QStringLiteral( "Attribute" ) );
1835 maptipElem.setAttribute( QStringLiteral( "name" ), QStringLiteral( "maptip" ) );
1836 QgsExpressionContext context { renderContext.expressionContext() };
1838 maptipElem.setAttribute( QStringLiteral( "value" ), QgsExpression::replaceExpressionText( mapTip, &context ) );
1839 featureElement.appendChild( maptipElem );
1840 }
1841
1842 //append feature bounding box to feature info xml
1844 layer->wkbType() != QgsWkbTypes::NoGeometry && hasGeometry )
1845 {
1846 QDomElement bBoxElem = infoDocument.createElement( QStringLiteral( "BoundingBox" ) );
1847 bBoxElem.setAttribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS", outputCrs.authid() );
1848 bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( box.xMinimum(), mContext.precision() ) );
1849 bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( box.xMaximum(), mContext.precision() ) );
1850 bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( box.yMinimum(), mContext.precision() ) );
1851 bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( box.yMaximum(), mContext.precision() ) );
1852 featureElement.appendChild( bBoxElem );
1853 }
1854
1855 //also append the wkt geometry as an attribute
1856 if ( layer->wkbType() != QgsWkbTypes::NoGeometry && addWktGeometry && hasGeometry )
1857 {
1858 QgsGeometry geom = feature.geometry();
1859 if ( !geom.isNull() )
1860 {
1861 if ( layer->crs() != outputCrs )
1862 {
1863 QgsCoordinateTransform transform = mapSettings.layerTransform( layer );
1864 if ( transform.isValid() )
1865 geom.transform( transform );
1866 }
1867
1868 if ( segmentizeWktGeometry )
1869 {
1870 const QgsAbstractGeometry *abstractGeom = geom.constGet();
1871 if ( abstractGeom )
1872 {
1873 if ( QgsWkbTypes::isCurvedType( abstractGeom->wkbType() ) )
1874 {
1875 QgsAbstractGeometry *segmentizedGeom = abstractGeom->segmentize();
1876 geom.set( segmentizedGeom );
1877 }
1878 }
1879 }
1880 QDomElement geometryElement = infoDocument.createElement( QStringLiteral( "Attribute" ) );
1881 geometryElement.setAttribute( QStringLiteral( "name" ), QStringLiteral( "geometry" ) );
1882 geometryElement.setAttribute( QStringLiteral( "value" ), geom.asWkt( mContext.precision() ) );
1883 geometryElement.setAttribute( QStringLiteral( "type" ), QStringLiteral( "derived" ) );
1884 featureElement.appendChild( geometryElement );
1885 }
1886 }
1887 }
1888 }
1889 if ( r2 )
1890 {
1891 r2->stopRender( renderContext );
1892 }
1893
1894 return true;
1895 }
1896
1897 void QgsRenderer::writeAttributesTabGroup( const QgsAttributeEditorElement *group, QgsVectorLayer *layer, const QgsFields &fields, QgsAttributes &featureAttributes, QDomDocument &doc, QDomElement &parentElem, QgsRenderContext &renderContext, QStringList *attributes ) const
1898 {
1899 const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( group );
1900 if ( container )
1901 {
1902 QString groupName = container->name();
1903 QDomElement nameElem;
1904
1905 if ( !groupName.isEmpty() )
1906 {
1907 nameElem = doc.createElement( groupName );
1908 parentElem.appendChild( nameElem );
1909 }
1910
1911 const QList<QgsAttributeEditorElement *> children = container->children();
1912 for ( const QgsAttributeEditorElement *child : children )
1913 {
1914 if ( child->type() == QgsAttributeEditorElement::AeTypeContainer )
1915 {
1916 writeAttributesTabGroup( child, layer, fields, featureAttributes, doc, nameElem.isNull() ? parentElem : nameElem, renderContext );
1917 }
1918 else if ( child->type() == QgsAttributeEditorElement::AeTypeField )
1919 {
1920 const QgsAttributeEditorField *editorField = dynamic_cast<const QgsAttributeEditorField *>( child );
1921 if ( editorField )
1922 {
1923 const int idx { fields.indexFromName( editorField->name() ) };
1924 if ( idx >= 0 )
1925 {
1926 writeVectorLayerAttribute( idx, layer, fields, featureAttributes, doc, nameElem.isNull() ? parentElem : nameElem, renderContext, attributes );
1927 }
1928 }
1929 }
1930 }
1931 }
1932 }
1933
1934 void QgsRenderer::writeAttributesTabLayout( QgsEditFormConfig &config, QgsVectorLayer *layer, const QgsFields &fields, QgsAttributes &featureAttributes, QDomDocument &doc, QDomElement &featureElem, QgsRenderContext &renderContext, QStringList *attributes ) const
1935 {
1936 QgsAttributeEditorContainer *editorContainer = config.invisibleRootContainer();
1937 if ( !editorContainer )
1938 {
1939 return;
1940 }
1941
1942 writeAttributesTabGroup( editorContainer, layer, fields, featureAttributes, doc, featureElem, renderContext, attributes );
1943 }
1944
1945 void QgsRenderer::writeVectorLayerAttribute( int attributeIndex, QgsVectorLayer *layer, const QgsFields &fields, QgsAttributes &featureAttributes, QDomDocument &doc, QDomElement &featureElem, QgsRenderContext &renderContext, QStringList *attributes ) const
1946 {
1947#ifndef HAVE_SERVER_PYTHON_PLUGINS
1948 Q_UNUSED( attributes );
1949#endif
1950
1951 if ( !layer )
1952 {
1953 return;
1954 }
1955
1956 //skip attribute if it is explicitly excluded from WMS publication
1957 if ( fields.at( attributeIndex ).configurationFlags().testFlag( QgsField::ConfigurationFlag::HideFromWms ) )
1958 {
1959 return;
1960 }
1961#ifdef HAVE_SERVER_PYTHON_PLUGINS
1962 //skip attribute if it is excluded by access control
1963 if ( attributes && !attributes->contains( fields.at( attributeIndex ).name() ) )
1964 {
1965 return;
1966 }
1967#endif
1968
1969 QString attributeName = layer->attributeDisplayName( attributeIndex );
1970 QDomElement attributeElement = doc.createElement( QStringLiteral( "Attribute" ) );
1971 attributeElement.setAttribute( QStringLiteral( "name" ), attributeName );
1972 const QgsEditorWidgetSetup setup = layer->editorWidgetSetup( attributeIndex );
1973 attributeElement.setAttribute( QStringLiteral( "value" ),
1975 replaceValueMapAndRelation(
1976 layer, attributeIndex,
1977 featureAttributes[attributeIndex] ),
1978 &renderContext.expressionContext() )
1979 );
1980 featureElem.appendChild( attributeElement );
1981 }
1982
1983 bool QgsRenderer::featureInfoFromRasterLayer( QgsRasterLayer *layer,
1984 const QgsMapSettings &mapSettings,
1985 const QgsPointXY *infoPoint,
1986 QDomDocument &infoDocument,
1987 QDomElement &layerElement,
1988 const QString &version ) const
1989 {
1990 Q_UNUSED( version )
1991
1992 if ( !infoPoint || !layer || !layer->dataProvider() )
1993 {
1994 return false;
1995 }
1996
1997 QgsMessageLog::logMessage( QStringLiteral( "infoPoint: %1 %2" ).arg( infoPoint->x() ).arg( infoPoint->y() ) );
1998
2001 {
2002 return false;
2003 }
2004
2005 const QgsRaster::IdentifyFormat identifyFormat(
2006 static_cast<bool>( layer->dataProvider()->capabilities() & QgsRasterDataProvider::IdentifyFeature )
2007 ? QgsRaster::IdentifyFormat::IdentifyFormatFeature
2008 : QgsRaster::IdentifyFormat::IdentifyFormatValue );
2009
2010 QgsRasterIdentifyResult identifyResult;
2011 if ( layer->crs() != mapSettings.destinationCrs() )
2012 {
2013 const QgsRectangle extent { mapSettings.extent() };
2014 const QgsCoordinateTransform transform { mapSettings.destinationCrs(), layer->crs(), mapSettings.transformContext() };
2015 if ( ! transform.isValid() )
2016 {
2017 throw QgsBadRequestException( QgsServiceException::OGC_InvalidCRS, QStringLiteral( "CRS transform error from %1 to %2 in layer %3" )
2018 .arg( mapSettings.destinationCrs().authid() )
2019 .arg( layer->crs().authid() )
2020 .arg( layer->name() ) );
2021 }
2022 identifyResult = layer->dataProvider()->identify( *infoPoint, identifyFormat, transform.transform( extent ), mapSettings.outputSize().width(), mapSettings.outputSize().height() );
2023 }
2024 else
2025 {
2026 identifyResult = layer->dataProvider()->identify( *infoPoint, identifyFormat, mapSettings.extent(), mapSettings.outputSize().width(), mapSettings.outputSize().height() );
2027 }
2028
2029 if ( !identifyResult.isValid() )
2030 return false;
2031
2032 QMap<int, QVariant> attributes = identifyResult.results();
2033
2034 if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML )
2035 {
2036 QgsFeature feature;
2037 QgsFields fields;
2038 QgsCoordinateReferenceSystem layerCrs = layer->crs();
2039 int gmlVersion = mWmsParameters.infoFormatVersion();
2040 QString typeName = mContext.layerNickname( *layer );
2041
2042 if ( identifyFormat == QgsRaster::IdentifyFormatValue )
2043 {
2044 feature.initAttributes( attributes.count() );
2045 int index = 0;
2046 for ( auto it = attributes.constBegin(); it != attributes.constEnd(); ++it )
2047 {
2048 fields.append( QgsField( layer->bandName( it.key() ), QVariant::Double ) );
2049 feature.setAttribute( index++, QString::number( it.value().toDouble() ) );
2050 }
2051 feature.setFields( fields );
2052 QDomElement elem = createFeatureGML(
2053 &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr );
2054 layerElement.appendChild( elem );
2055 }
2056 else
2057 {
2058 const auto values = identifyResult.results();
2059 for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
2060 {
2061 QVariant value = it.value();
2062 if ( value.type() == QVariant::Bool && !value.toBool() )
2063 {
2064 // sublayer not visible or not queryable
2065 continue;
2066 }
2067
2068 if ( value.type() == QVariant::String )
2069 {
2070 continue;
2071 }
2072
2073 // list of feature stores for a single sublayer
2074 const QgsFeatureStoreList featureStoreList = it.value().value<QgsFeatureStoreList>();
2075
2076 for ( const QgsFeatureStore &featureStore : featureStoreList )
2077 {
2078 const QgsFeatureList storeFeatures = featureStore.features();
2079 for ( const QgsFeature &feature : storeFeatures )
2080 {
2081 QDomElement elem = createFeatureGML(
2082 &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr );
2083 layerElement.appendChild( elem );
2084 }
2085 }
2086 }
2087 }
2088 }
2089 else
2090 {
2091 if ( identifyFormat == QgsRaster::IdentifyFormatValue )
2092 {
2093 for ( auto it = attributes.constBegin(); it != attributes.constEnd(); ++it )
2094 {
2095 QDomElement attributeElement = infoDocument.createElement( QStringLiteral( "Attribute" ) );
2096 attributeElement.setAttribute( QStringLiteral( "name" ), layer->bandName( it.key() ) );
2097
2098 QString value;
2099 if ( ! QgsVariantUtils::isNull( it.value() ) )
2100 {
2101 value = QString::number( it.value().toDouble() );
2102 }
2103
2104 attributeElement.setAttribute( QStringLiteral( "value" ), value );
2105 layerElement.appendChild( attributeElement );
2106 }
2107 }
2108 else // feature
2109 {
2110 const auto values = identifyResult.results();
2111 for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
2112 {
2113 QVariant value = it.value();
2114 if ( value.type() == QVariant::Bool && !value.toBool() )
2115 {
2116 // sublayer not visible or not queryable
2117 continue;
2118 }
2119
2120 if ( value.type() == QVariant::String )
2121 {
2122 continue;
2123 }
2124
2125 // list of feature stores for a single sublayer
2126 const QgsFeatureStoreList featureStoreList = it.value().value<QgsFeatureStoreList>();
2127 for ( const QgsFeatureStore &featureStore : featureStoreList )
2128 {
2129 const QgsFeatureList storeFeatures = featureStore.features();
2130 for ( const QgsFeature &feature : storeFeatures )
2131 {
2132 for ( const auto &fld : feature.fields() )
2133 {
2134 const auto val { feature.attribute( fld.name() )};
2135 if ( val.isValid() )
2136 {
2137 QDomElement attributeElement = infoDocument.createElement( QStringLiteral( "Attribute" ) );
2138 attributeElement.setAttribute( QStringLiteral( "name" ), fld.name() );
2139 attributeElement.setAttribute( QStringLiteral( "value" ), val.toString() );
2140 layerElement.appendChild( attributeElement );
2141 }
2142 }
2143 }
2144 }
2145 }
2146 }
2147 }
2148 return true;
2149 }
2150
2151 bool QgsRenderer::testFilterStringSafety( const QString &filter ) const
2152 {
2153 //; too dangerous for sql injections
2154 if ( filter.contains( QLatin1String( ";" ) ) )
2155 {
2156 return false;
2157 }
2158
2159#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
2160 QStringList tokens = filter.split( ' ', QString::SkipEmptyParts );
2161#else
2162 QStringList tokens = filter.split( ' ', Qt::SkipEmptyParts );
2163#endif
2164 groupStringList( tokens, QStringLiteral( "'" ) );
2165 groupStringList( tokens, QStringLiteral( "\"" ) );
2166
2167 for ( auto tokenIt = tokens.constBegin() ; tokenIt != tokens.constEnd(); ++tokenIt )
2168 {
2169 //allowlist of allowed characters and keywords
2170 if ( tokenIt->compare( QLatin1String( "," ) ) == 0
2171 || tokenIt->compare( QLatin1String( "(" ) ) == 0
2172 || tokenIt->compare( QLatin1String( ")" ) ) == 0
2173 || tokenIt->compare( QLatin1String( "=" ) ) == 0
2174 || tokenIt->compare( QLatin1String( "!=" ) ) == 0
2175 || tokenIt->compare( QLatin1String( "<" ) ) == 0
2176 || tokenIt->compare( QLatin1String( "<=" ) ) == 0
2177 || tokenIt->compare( QLatin1String( ">" ) ) == 0
2178 || tokenIt->compare( QLatin1String( ">=" ) ) == 0
2179 || tokenIt->compare( QLatin1String( "%" ) ) == 0
2180 || tokenIt->compare( QLatin1String( "IS" ), Qt::CaseInsensitive ) == 0
2181 || tokenIt->compare( QLatin1String( "NOT" ), Qt::CaseInsensitive ) == 0
2182 || tokenIt->compare( QLatin1String( "NULL" ), Qt::CaseInsensitive ) == 0
2183 || tokenIt->compare( QLatin1String( "AND" ), Qt::CaseInsensitive ) == 0
2184 || tokenIt->compare( QLatin1String( "OR" ), Qt::CaseInsensitive ) == 0
2185 || tokenIt->compare( QLatin1String( "IN" ), Qt::CaseInsensitive ) == 0
2186 || tokenIt->compare( QLatin1String( "LIKE" ), Qt::CaseInsensitive ) == 0
2187 || tokenIt->compare( QLatin1String( "ILIKE" ), Qt::CaseInsensitive ) == 0
2188 || tokenIt->compare( QLatin1String( "DMETAPHONE" ), Qt::CaseInsensitive ) == 0
2189 || tokenIt->compare( QLatin1String( "SOUNDEX" ), Qt::CaseInsensitive ) == 0
2190 || mContext.settings().allowedExtraSqlTokens().contains( *tokenIt, Qt::CaseSensitivity::CaseInsensitive ) )
2191 {
2192 continue;
2193 }
2194
2195 //numbers are OK
2196 bool isNumeric;
2197 tokenIt->toDouble( &isNumeric );
2198 if ( isNumeric )
2199 {
2200 continue;
2201 }
2202
2203 //numeric strings need to be quoted once either with single or with double quotes
2204
2205 //empty strings are OK
2206 if ( *tokenIt == QLatin1String( "''" ) )
2207 {
2208 continue;
2209 }
2210
2211 //single quote
2212 if ( tokenIt->size() > 2
2213 && ( *tokenIt )[0] == QChar( '\'' )
2214 && ( *tokenIt )[tokenIt->size() - 1] == QChar( '\'' )
2215 && ( *tokenIt )[1] != QChar( '\'' )
2216 && ( *tokenIt )[tokenIt->size() - 2] != QChar( '\'' ) )
2217 {
2218 continue;
2219 }
2220
2221 //double quote
2222 if ( tokenIt->size() > 2
2223 && ( *tokenIt )[0] == QChar( '"' )
2224 && ( *tokenIt )[tokenIt->size() - 1] == QChar( '"' )
2225 && ( *tokenIt )[1] != QChar( '"' )
2226 && ( *tokenIt )[tokenIt->size() - 2] != QChar( '"' ) )
2227 {
2228 continue;
2229 }
2230
2231 return false;
2232 }
2233
2234 return true;
2235 }
2236
2237 void QgsRenderer::groupStringList( QStringList &list, const QString &groupString )
2238 {
2239 //group contents within single quotes together
2240 bool groupActive = false;
2241 int startGroup = -1;
2242 QString concatString;
2243
2244 for ( int i = 0; i < list.size(); ++i )
2245 {
2246 QString &str = list[i];
2247 if ( str.startsWith( groupString ) )
2248 {
2249 startGroup = i;
2250 groupActive = true;
2251 concatString.clear();
2252 }
2253
2254 if ( groupActive )
2255 {
2256 if ( i != startGroup )
2257 {
2258 concatString.append( " " );
2259 }
2260 concatString.append( str );
2261 }
2262
2263 if ( str.endsWith( groupString ) )
2264 {
2265 int endGroup = i;
2266 groupActive = false;
2267
2268 if ( startGroup != -1 )
2269 {
2270 list[startGroup] = concatString;
2271 for ( int j = startGroup + 1; j <= endGroup; ++j )
2272 {
2273 list.removeAt( startGroup + 1 );
2274 --i;
2275 }
2276 }
2277
2278 concatString.clear();
2279 startGroup = -1;
2280 }
2281 }
2282 }
2283
2284 void QgsRenderer::convertFeatureInfoToSia2045( QDomDocument &doc ) const
2285 {
2286 QDomDocument SIAInfoDoc;
2287 QDomElement infoDocElement = doc.documentElement();
2288 QDomElement SIAInfoDocElement = SIAInfoDoc.importNode( infoDocElement, false ).toElement();
2289 SIAInfoDoc.appendChild( SIAInfoDocElement );
2290
2291 QString currentAttributeName;
2292 QString currentAttributeValue;
2293 QDomElement currentAttributeElem;
2294 QString currentLayerName;
2295 QDomElement currentLayerElem;
2296 QDomNodeList layerNodeList = infoDocElement.elementsByTagName( QStringLiteral( "Layer" ) );
2297 for ( int i = 0; i < layerNodeList.size(); ++i )
2298 {
2299 currentLayerElem = layerNodeList.at( i ).toElement();
2300 currentLayerName = currentLayerElem.attribute( QStringLiteral( "name" ) );
2301
2302 QDomElement currentFeatureElem;
2303
2304 QDomNodeList featureList = currentLayerElem.elementsByTagName( QStringLiteral( "Feature" ) );
2305 if ( featureList.isEmpty() )
2306 {
2307 //raster?
2308 QDomNodeList attributeList = currentLayerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2309 QDomElement rasterLayerElem;
2310 if ( !attributeList.isEmpty() )
2311 {
2312 rasterLayerElem = SIAInfoDoc.createElement( currentLayerName );
2313 }
2314 for ( int j = 0; j < attributeList.size(); ++j )
2315 {
2316 currentAttributeElem = attributeList.at( j ).toElement();
2317 currentAttributeName = currentAttributeElem.attribute( QStringLiteral( "name" ) );
2318 currentAttributeValue = currentAttributeElem.attribute( QStringLiteral( "value" ) );
2319 QDomElement outAttributeElem = SIAInfoDoc.createElement( currentAttributeName );
2320 QDomText outAttributeText = SIAInfoDoc.createTextNode( currentAttributeValue );
2321 outAttributeElem.appendChild( outAttributeText );
2322 rasterLayerElem.appendChild( outAttributeElem );
2323 }
2324 if ( !attributeList.isEmpty() )
2325 {
2326 SIAInfoDocElement.appendChild( rasterLayerElem );
2327 }
2328 }
2329 else //vector
2330 {
2331 //property attributes
2332 QSet<QString> layerPropertyAttributes;
2333 QString currentLayerId = currentLayerElem.attribute( QStringLiteral( "id" ) );
2334 if ( !currentLayerId.isEmpty() )
2335 {
2336 QgsMapLayer *currentLayer = mProject->mapLayer( currentLayerId );
2337 if ( currentLayer )
2338 {
2339 QString WMSPropertyAttributesString = currentLayer->customProperty( QStringLiteral( "WMSPropertyAttributes" ) ).toString();
2340 if ( !WMSPropertyAttributesString.isEmpty() )
2341 {
2342 QStringList propertyList = WMSPropertyAttributesString.split( QStringLiteral( "//" ) );
2343 for ( auto propertyIt = propertyList.constBegin() ; propertyIt != propertyList.constEnd(); ++propertyIt )
2344 {
2345 layerPropertyAttributes.insert( *propertyIt );
2346 }
2347 }
2348 }
2349 }
2350
2351 QDomElement propertyRefChild; //child to insert the next property after (or
2352 for ( int j = 0; j < featureList.size(); ++j )
2353 {
2354 QDomElement SIAFeatureElem = SIAInfoDoc.createElement( currentLayerName );
2355 currentFeatureElem = featureList.at( j ).toElement();
2356 QDomNodeList attributeList = currentFeatureElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2357
2358 for ( int k = 0; k < attributeList.size(); ++k )
2359 {
2360 currentAttributeElem = attributeList.at( k ).toElement();
2361 currentAttributeName = currentAttributeElem.attribute( QStringLiteral( "name" ) );
2362 currentAttributeValue = currentAttributeElem.attribute( QStringLiteral( "value" ) );
2363 if ( layerPropertyAttributes.contains( currentAttributeName ) )
2364 {
2365 QDomElement propertyElem = SIAInfoDoc.createElement( QStringLiteral( "property" ) );
2366 QDomElement identifierElem = SIAInfoDoc.createElement( QStringLiteral( "identifier" ) );
2367 QDomText identifierText = SIAInfoDoc.createTextNode( currentAttributeName );
2368 identifierElem.appendChild( identifierText );
2369 QDomElement valueElem = SIAInfoDoc.createElement( QStringLiteral( "value" ) );
2370 QDomText valueText = SIAInfoDoc.createTextNode( currentAttributeValue );
2371 valueElem.appendChild( valueText );
2372 propertyElem.appendChild( identifierElem );
2373 propertyElem.appendChild( valueElem );
2374 if ( propertyRefChild.isNull() )
2375 {
2376 SIAFeatureElem.insertBefore( propertyElem, QDomNode() );
2377 propertyRefChild = propertyElem;
2378 }
2379 else
2380 {
2381 SIAFeatureElem.insertAfter( propertyElem, propertyRefChild );
2382 }
2383 }
2384 else
2385 {
2386 QDomElement SIAAttributeElem = SIAInfoDoc.createElement( currentAttributeName );
2387 QDomText SIAAttributeText = SIAInfoDoc.createTextNode( currentAttributeValue );
2388 SIAAttributeElem.appendChild( SIAAttributeText );
2389 SIAFeatureElem.appendChild( SIAAttributeElem );
2390 }
2391 }
2392 SIAInfoDocElement.appendChild( SIAFeatureElem );
2393 }
2394 }
2395 }
2396 doc = SIAInfoDoc;
2397 }
2398
2399 QByteArray QgsRenderer::convertFeatureInfoToHtml( const QDomDocument &doc ) const
2400 {
2401 QString featureInfoString;
2402
2403 //the HTML head
2404 featureInfoString.append( "<HEAD>\n" );
2405 featureInfoString.append( "<TITLE> GetFeatureInfo results </TITLE>\n" );
2406 featureInfoString.append( "<META http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n" );
2407 featureInfoString.append( "</HEAD>\n" );
2408
2409 //start the html body
2410 featureInfoString.append( "<BODY>\n" );
2411
2412 QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
2413
2414 //layer loop
2415 for ( int i = 0; i < layerList.size(); ++i )
2416 {
2417 QDomElement layerElem = layerList.at( i ).toElement();
2418
2419 featureInfoString.append( "<TABLE border=1 width=100%>\n" );
2420 featureInfoString.append( "<TR><TH width=25%>Layer</TH><TD>" + layerElem.attribute( QStringLiteral( "name" ) ) + "</TD></TR>\n" );
2421 featureInfoString.append( "</BR>" );
2422
2423 //feature loop (for vector layers)
2424 QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
2425 QDomElement currentFeatureElement;
2426
2427 if ( !featureNodeList.isEmpty() ) //vector layer
2428 {
2429 for ( int j = 0; j < featureNodeList.size(); ++j )
2430 {
2431 QDomElement featureElement = featureNodeList.at( j ).toElement();
2432 featureInfoString.append( "<TABLE border=1 width=100%>\n" );
2433 featureInfoString.append( "<TR><TH>Feature</TH><TD>" + featureElement.attribute( QStringLiteral( "id" ) ) +
2434 "</TD></TR>\n" );
2435
2436 //attribute loop
2437 QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) );
2438 for ( int k = 0; k < attributeNodeList.size(); ++k )
2439 {
2440 QDomElement attributeElement = attributeNodeList.at( k ).toElement();
2441 featureInfoString.append( "<TR><TH>" + attributeElement.attribute( QStringLiteral( "name" ) ) +
2442 "</TH><TD>" + attributeElement.attribute( QStringLiteral( "value" ) ) + "</TD></TR>\n" );
2443 }
2444
2445 featureInfoString.append( "</TABLE>\n</BR>\n" );
2446 }
2447 }
2448 else //raster layer
2449 {
2450 QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2451 for ( int j = 0; j < attributeNodeList.size(); ++j )
2452 {
2453 QDomElement attributeElement = attributeNodeList.at( j ).toElement();
2454 QString value = attributeElement.attribute( QStringLiteral( "value" ) );
2455 if ( value.isEmpty() )
2456 {
2457 value = QStringLiteral( "no data" );
2458 }
2459 featureInfoString.append( "<TR><TH>" + attributeElement.attribute( QStringLiteral( "name" ) ) +
2460 "</TH><TD>" + value + "</TD></TR>\n" );
2461 }
2462 }
2463
2464 featureInfoString.append( "</TABLE>\n<BR></BR>\n" );
2465 }
2466
2467 //start the html body
2468 featureInfoString.append( "</BODY>\n" );
2469
2470 return featureInfoString.toUtf8();
2471 }
2472
2473 QByteArray QgsRenderer::convertFeatureInfoToText( const QDomDocument &doc ) const
2474 {
2475 QString featureInfoString;
2476
2477 //the Text head
2478 featureInfoString.append( "GetFeatureInfo results\n" );
2479 featureInfoString.append( "\n" );
2480
2481 QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
2482
2483 //layer loop
2484 for ( int i = 0; i < layerList.size(); ++i )
2485 {
2486 QDomElement layerElem = layerList.at( i ).toElement();
2487
2488 featureInfoString.append( "Layer '" + layerElem.attribute( QStringLiteral( "name" ) ) + "'\n" );
2489
2490 //feature loop (for vector layers)
2491 QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
2492 QDomElement currentFeatureElement;
2493
2494 if ( !featureNodeList.isEmpty() ) //vector layer
2495 {
2496 for ( int j = 0; j < featureNodeList.size(); ++j )
2497 {
2498 QDomElement featureElement = featureNodeList.at( j ).toElement();
2499 featureInfoString.append( "Feature " + featureElement.attribute( QStringLiteral( "id" ) ) + "\n" );
2500
2501 //attribute loop
2502 QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) );
2503 for ( int k = 0; k < attributeNodeList.size(); ++k )
2504 {
2505 QDomElement attributeElement = attributeNodeList.at( k ).toElement();
2506 featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" +
2507 attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" );
2508 }
2509 }
2510 }
2511 else //raster layer
2512 {
2513 QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2514 for ( int j = 0; j < attributeNodeList.size(); ++j )
2515 {
2516 QDomElement attributeElement = attributeNodeList.at( j ).toElement();
2517 QString value = attributeElement.attribute( QStringLiteral( "value" ) );
2518 if ( value.isEmpty() )
2519 {
2520 value = QStringLiteral( "no data" );
2521 }
2522 featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" +
2523 value + "'\n" );
2524 }
2525 }
2526
2527 featureInfoString.append( "\n" );
2528 }
2529
2530 return featureInfoString.toUtf8();
2531 }
2532
2533 QByteArray QgsRenderer::convertFeatureInfoToJson( const QList<QgsMapLayer *> &layers, const QDomDocument &doc ) const
2534 {
2535 json json
2536 {
2537 { "type", "FeatureCollection" },
2538 { "features", json::array() },
2539 };
2540 const bool withGeometry = ( QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) && mWmsParameters.withGeometry() );
2541
2542 const QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
2543 for ( int i = 0; i < layerList.size(); ++i )
2544 {
2545 const QDomElement layerElem = layerList.at( i ).toElement();
2546 const QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
2547
2548 QgsMapLayer *layer = nullptr;
2549 for ( QgsMapLayer *l : layers )
2550 {
2551 if ( mContext.layerNickname( *l ).compare( layerName ) == 0 )
2552 {
2553 layer = l;
2554 }
2555 }
2556
2557 if ( !layer )
2558 continue;
2559
2560 if ( layer->type() == QgsMapLayerType::VectorLayer )
2561 {
2562 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
2563
2564 // search features to export
2565 QgsFeatureList features;
2566 QgsAttributeList attributes;
2567 const QDomNodeList featuresNode = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
2568 if ( featuresNode.isEmpty() )
2569 continue;
2570
2571 QMap<QgsFeatureId, QString> fidMap;
2572
2573 for ( int j = 0; j < featuresNode.size(); ++j )
2574 {
2575 const QDomElement featureNode = featuresNode.at( j ).toElement();
2576 const QString fid = featureNode.attribute( QStringLiteral( "id" ) );
2577 QgsFeature feature;
2578 const QString expression { QgsServerFeatureId::getExpressionFromServerFid( fid, static_cast<QgsVectorDataProvider *>( layer->dataProvider() ) ) };
2579 if ( expression.isEmpty() )
2580 {
2581 feature = vl->getFeature( fid.toLongLong() );
2582 }
2583 else
2584 {
2585 QgsFeatureRequest request { QgsExpression( expression )};
2586 request.setFlags( QgsFeatureRequest::Flag::NoGeometry );
2587 vl->getFeatures( request ).nextFeature( feature );
2588 }
2589
2590 fidMap.insert( feature.id(), fid );
2591
2592 QString wkt;
2593 if ( withGeometry )
2594 {
2595 const QDomNodeList attrs = featureNode.elementsByTagName( "Attribute" );
2596 for ( int k = 0; k < attrs.count(); k++ )
2597 {
2598 const QDomElement elm = attrs.at( k ).toElement();
2599 if ( elm.attribute( QStringLiteral( "name" ) ).compare( "geometry" ) == 0 )
2600 {
2601 wkt = elm.attribute( "value" );
2602 break;
2603 }
2604 }
2605
2606 if ( ! wkt.isEmpty() )
2607 {
2608 // CRS in WMS parameters may be different from the layer
2609 feature.setGeometry( QgsGeometry::fromWkt( wkt ) );
2610 }
2611 }
2612 features << feature;
2613
2614 // search attributes to export (one time only)
2615 if ( !attributes.isEmpty() )
2616 continue;
2617
2618 const QDomNodeList attributesNode = featureNode.elementsByTagName( QStringLiteral( "Attribute" ) );
2619 for ( int k = 0; k < attributesNode.size(); ++k )
2620 {
2621 const QDomElement attributeElement = attributesNode.at( k ).toElement();
2622 const QString fieldName = attributeElement.attribute( QStringLiteral( "name" ) );
2623
2624 attributes << feature.fieldNameIndex( fieldName );
2625 }
2626 }
2627
2628 // export
2629 QgsJsonExporter exporter( vl );
2630 exporter.setAttributeDisplayName( true );
2631 exporter.setAttributes( attributes );
2632 exporter.setIncludeGeometry( withGeometry );
2633 exporter.setTransformGeometries( false );
2634
2635 for ( const auto &feature : std::as_const( features ) )
2636 {
2637 const QString id = QStringLiteral( "%1.%2" ).arg( layerName ).arg( fidMap.value( feature.id() ) );
2638 json["features"].push_back( exporter.exportFeatureToJsonObject( feature, QVariantMap(), id ) );
2639 }
2640 }
2641 else // raster layer
2642 {
2643 auto properties = json::object();
2644 const QDomNodeList attributesNode = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2645 for ( int j = 0; j < attributesNode.size(); ++j )
2646 {
2647 const QDomElement attrElmt = attributesNode.at( j ).toElement();
2648 const QString name = attrElmt.attribute( QStringLiteral( "name" ) );
2649
2650 QString value = attrElmt.attribute( QStringLiteral( "value" ) );
2651 if ( value.isEmpty() )
2652 {
2653 value = QStringLiteral( "null" );
2654 }
2655
2656 properties[name.toStdString()] = value.toStdString();
2657 }
2658
2659 json["features"].push_back(
2660 {
2661 {"type", "Feature" },
2662 {"id", layerName.toStdString() },
2663 {"properties", properties }
2664 } );
2665 }
2666 }
2667#ifdef QGISDEBUG
2668 // This is only useful to generate human readable reference files for tests
2669 return QByteArray::fromStdString( json.dump( 2 ) );
2670#else
2671 return QByteArray::fromStdString( json.dump() );
2672#endif
2673 }
2674
2675 QDomElement QgsRenderer::createFeatureGML(
2676 const QgsFeature *feat,
2677 QgsVectorLayer *layer,
2678 QDomDocument &doc,
2680 const QgsMapSettings &mapSettings,
2681 const QString &typeName,
2682 bool withGeom,
2683 int version,
2684 QStringList *attributes ) const
2685 {
2686 //qgs:%TYPENAME%
2687 QDomElement typeNameElement = doc.createElement( "qgs:" + typeName /*qgs:%TYPENAME%*/ );
2688 QString fid;
2689 if ( layer && layer->dataProvider() )
2691 else
2692 fid = FID_TO_STRING( feat->id() );
2693
2694 typeNameElement.setAttribute( QStringLiteral( "fid" ), QStringLiteral( "%1.%2" ).arg( typeName, fid ) );
2695
2696 QgsCoordinateTransform transform;
2697 if ( layer && layer->crs() != crs )
2698 {
2699 transform = mapSettings.layerTransform( layer );
2700 }
2701
2702 QgsGeometry geom = feat->geometry();
2703
2704 QgsExpressionContext expressionContext;
2705 expressionContext << QgsExpressionContextUtils::globalScope()
2707 if ( layer )
2708 expressionContext << QgsExpressionContextUtils::layerScope( layer );
2709 expressionContext.setFeature( *feat );
2710
2711 // always add bounding box info if feature contains geometry and has been
2712 // explicitly configured in the project
2714 !geom.isNull() && geom.type() != QgsWkbTypes::UnknownGeometry &&
2716 {
2717 QgsRectangle box = feat->geometry().boundingBox();
2718 if ( transform.isValid() )
2719 {
2720 try
2721 {
2722 box = transform.transformBoundingBox( box );
2723 }
2724 catch ( QgsCsException &e )
2725 {
2726 QgsMessageLog::logMessage( QStringLiteral( "Transform error caught: %1" ).arg( e.what() ) );
2727 }
2728 }
2729
2730 QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
2731 QDomElement boxElem;
2732 if ( version < 3 )
2733 {
2734 boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, mContext.precision() );
2735 }
2736 else
2737 {
2738 boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, mContext.precision() );
2739 }
2740
2741 if ( crs.isValid() )
2742 {
2743 boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
2744 }
2745 bbElem.appendChild( boxElem );
2746 typeNameElement.appendChild( bbElem );
2747 }
2748
2749 if ( withGeom && !geom.isNull() )
2750 {
2751 //add geometry column (as gml)
2752
2753 if ( transform.isValid() )
2754 {
2755 geom.transform( transform );
2756 }
2757
2758 QDomElement geomElem = doc.createElement( QStringLiteral( "qgs:geometry" ) );
2759 QDomElement gmlElem;
2760 if ( version < 3 )
2761 {
2762 gmlElem = QgsOgcUtils::geometryToGML( geom, doc, mContext.precision() );
2763 }
2764 else
2765 {
2766 gmlElem = QgsOgcUtils::geometryToGML( geom, doc, QStringLiteral( "GML3" ), mContext.precision() );
2767 }
2768
2769 if ( !gmlElem.isNull() )
2770 {
2771 if ( crs.isValid() )
2772 {
2773 gmlElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
2774 }
2775 geomElem.appendChild( gmlElem );
2776 typeNameElement.appendChild( geomElem );
2777 }
2778 }
2779
2780 //read all allowed attribute values from the feature
2781 QgsAttributes featureAttributes = feat->attributes();
2782 QgsFields fields = feat->fields();
2783 for ( int i = 0; i < fields.count(); ++i )
2784 {
2785 QString attributeName = fields.at( i ).name();
2786 //skip attribute if it is explicitly excluded from WMS publication
2787 if ( fields.at( i ).configurationFlags().testFlag( QgsField::ConfigurationFlag::HideFromWms ) )
2788 {
2789 continue;
2790 }
2791 //skip attribute if it is excluded by access control
2792 if ( attributes && !attributes->contains( attributeName ) )
2793 {
2794 continue;
2795 }
2796
2797 QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ) );
2798 QString fieldTextString = featureAttributes.at( i ).toString();
2799 if ( layer )
2800 {
2801 fieldTextString = QgsExpression::replaceExpressionText( replaceValueMapAndRelation( layer, i, fieldTextString ), &expressionContext );
2802 }
2803 QDomText fieldText = doc.createTextNode( fieldTextString );
2804 fieldElem.appendChild( fieldText );
2805 typeNameElement.appendChild( fieldElem );
2806 }
2807
2808 //add maptip attribute based on html/expression (in case there is no maptip attribute)
2809 if ( layer )
2810 {
2811 QString mapTip = layer->mapTipTemplate();
2812
2813 if ( !mapTip.isEmpty() && mWmsParameters.withMapTip() )
2814 {
2815 QString fieldTextString = QgsExpression::replaceExpressionText( mapTip, &expressionContext );
2816 QDomElement fieldElem = doc.createElement( QStringLiteral( "qgs:maptip" ) );
2817 QDomText maptipText = doc.createTextNode( fieldTextString );
2818 fieldElem.appendChild( maptipText );
2819 typeNameElement.appendChild( fieldElem );
2820 }
2821 }
2822
2823 return typeNameElement;
2824 }
2825
2826 QString QgsRenderer::replaceValueMapAndRelation( QgsVectorLayer *vl, int idx, const QVariant &attributeVal )
2827 {
2828 const QgsEditorWidgetSetup setup = vl->editorWidgetSetup( idx );
2830 QString value( fieldFormatter->representValue( vl, idx, setup.config(), QVariant(), attributeVal ) );
2831
2832 if ( setup.config().value( QStringLiteral( "AllowMulti" ) ).toBool() && value.startsWith( QLatin1Char( '{' ) ) && value.endsWith( QLatin1Char( '}' ) ) )
2833 {
2834 value = value.mid( 1, value.size() - 2 );
2835 }
2836 return value;
2837 }
2838
2839 QgsRectangle QgsRenderer::featureInfoSearchRect( QgsVectorLayer *ml, const QgsMapSettings &mapSettings, const QgsRenderContext &rct, const QgsPointXY &infoPoint ) const
2840 {
2841 if ( !ml )
2842 {
2843 return QgsRectangle();
2844 }
2845
2846 double mapUnitTolerance = 0.0;
2848 {
2849 if ( ! mWmsParameters.polygonTolerance().isEmpty()
2850 && mWmsParameters.polygonToleranceAsInt() > 0 )
2851 {
2852 mapUnitTolerance = mWmsParameters.polygonToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
2853 }
2854 else
2855 {
2856 mapUnitTolerance = mapSettings.extent().width() / 400.0;
2857 }
2858 }
2859 else if ( ml->geometryType() == QgsWkbTypes::LineGeometry )
2860 {
2861 if ( ! mWmsParameters.lineTolerance().isEmpty()
2862 && mWmsParameters.lineToleranceAsInt() > 0 )
2863 {
2864 mapUnitTolerance = mWmsParameters.lineToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
2865 }
2866 else
2867 {
2868 mapUnitTolerance = mapSettings.extent().width() / 200.0;
2869 }
2870 }
2871 else //points
2872 {
2873 if ( ! mWmsParameters.pointTolerance().isEmpty()
2874 && mWmsParameters.pointToleranceAsInt() > 0 )
2875 {
2876 mapUnitTolerance = mWmsParameters.pointToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
2877 }
2878 else
2879 {
2880 mapUnitTolerance = mapSettings.extent().width() / 100.0;
2881 }
2882 }
2883
2884 QgsRectangle mapRectangle( infoPoint.x() - mapUnitTolerance, infoPoint.y() - mapUnitTolerance,
2885 infoPoint.x() + mapUnitTolerance, infoPoint.y() + mapUnitTolerance );
2886 return ( mapSettings.mapToLayerCoordinates( ml, mapRectangle ) );
2887 }
2888
2889 QList<QgsMapLayer *> QgsRenderer::highlightLayers( QList<QgsWmsParametersHighlightLayer> params )
2890 {
2891 QList<QgsMapLayer *> highlightLayers;
2892
2893 // try to create highlight layer for each geometry
2894 QString crs = mWmsParameters.crs();
2895 for ( const QgsWmsParametersHighlightLayer &param : params )
2896 {
2897 // create sld document from symbology
2898 QDomDocument sldDoc;
2899 QString errorMsg;
2900 int errorLine;
2901 int errorColumn;
2902 if ( !sldDoc.setContent( param.mSld, true, &errorMsg, &errorLine, &errorColumn ) )
2903 {
2904 QgsMessageLog::logMessage( QStringLiteral( "Error parsing SLD for layer %1 at line %2, column %3:\n%4" )
2905 .arg( param.mName )
2906 .arg( errorLine )
2907 .arg( errorColumn )
2908 .arg( errorMsg ),
2909 QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
2910 continue;
2911 }
2912
2913 // create renderer from sld document
2914 std::unique_ptr<QgsFeatureRenderer> renderer;
2915 QDomElement el = sldDoc.documentElement();
2916 renderer.reset( QgsFeatureRenderer::loadSld( el, param.mGeom.type(), errorMsg ) );
2917 if ( !renderer )
2918 {
2919 QgsMessageLog::logMessage( errorMsg, "Server", Qgis::MessageLevel::Info );
2920 continue;
2921 }
2922
2923 // build url for vector layer
2924 const QString typeName = QgsWkbTypes::displayString( param.mGeom.wkbType() );
2925 QString url = typeName + "?crs=" + crs;
2926 if ( ! param.mLabel.isEmpty() )
2927 {
2928 url += "&field=label:string";
2929 }
2930
2931 // create vector layer
2933 std::unique_ptr<QgsVectorLayer> layer = std::make_unique<QgsVectorLayer>( url, param.mName, QLatin1String( "memory" ), options );
2934 if ( !layer->isValid() )
2935 {
2936 continue;
2937 }
2938
2939 // create feature with label if necessary
2940 QgsFeature fet( layer->fields() );
2941 if ( ! param.mLabel.isEmpty() )
2942 {
2943 fet.setAttribute( 0, param.mLabel );
2944
2945 // init labeling engine
2946 QgsPalLayerSettings palSettings;
2947 palSettings.fieldName = "label"; // defined in url
2948 palSettings.priority = 10; // always drawn
2950 palSettings.placementSettings().setAllowDegradedPlacement( true );
2951 palSettings.dist = param.mLabelDistance;
2952
2953 if ( !qgsDoubleNear( param.mLabelRotation, 0 ) )
2954 {
2956 palSettings.dataDefinedProperties().setProperty( pR, param.mLabelRotation );
2957 }
2958
2960 switch ( param.mGeom.type() )
2961 {
2963 {
2964 if ( param.mHali.isEmpty() || param.mVali.isEmpty() || QgsWkbTypes::flatType( param.mGeom.wkbType() ) != QgsWkbTypes::Point )
2965 {
2967 palSettings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlags() );
2968 }
2969 else //set label directly on point if there is hali/vali
2970 {
2971 QgsPointXY pt = param.mGeom.asPoint();
2973 QVariant x( pt.x() );
2974 palSettings.dataDefinedProperties().setProperty( pX, x );
2976 QVariant y( pt.y() );
2977 palSettings.dataDefinedProperties().setProperty( pY, y );
2979 palSettings.dataDefinedProperties().setProperty( pHali, param.mHali );
2981 palSettings.dataDefinedProperties().setProperty( pVali, param.mVali );
2982 }
2983
2984 break;
2985 }
2987 {
2988 QgsGeometry point = param.mGeom.pointOnSurface();
2989 QgsPointXY pt = point.asPoint();
2991
2993 QVariant x( pt.x() );
2994 palSettings.dataDefinedProperties().setProperty( pX, x );
2995
2997 QVariant y( pt.y() );
2998 palSettings.dataDefinedProperties().setProperty( pY, y );
2999
3001 QVariant hali( "Center" );
3002 palSettings.dataDefinedProperties().setProperty( pHali, hali );
3003
3005 QVariant vali( "Half" );
3006 palSettings.dataDefinedProperties().setProperty( pVali, vali );
3007 break;
3008 }
3009 default:
3010 {
3011 placement = Qgis::LabelPlacement::Line;
3012 palSettings.lineSettings().setPlacementFlags( QgsLabeling::LinePlacementFlag::AboveLine | QgsLabeling::LinePlacementFlag::MapOrientation );
3013 break;
3014 }
3015 }
3016 palSettings.placement = placement;
3017 QgsTextFormat textFormat;
3018 QgsTextBufferSettings bufferSettings;
3019
3020 if ( param.mColor.isValid() )
3021 {
3022 textFormat.setColor( param.mColor );
3023 }
3024
3025 if ( param.mSize > 0 )
3026 {
3027 textFormat.setSize( param.mSize );
3028 }
3029
3030 // no weight property in PAL settings or QgsTextFormat
3031 /* if ( param.fontWeight > 0 )
3032 {
3033 } */
3034
3035 if ( ! param.mFont.isEmpty() )
3036 {
3037 textFormat.setFont( param.mFont );
3038 }
3039
3040 if ( param.mBufferColor.isValid() )
3041 {
3042 bufferSettings.setColor( param.mBufferColor );
3043 }
3044
3045 if ( param.mBufferSize > 0 )
3046 {
3047 bufferSettings.setEnabled( true );
3048 bufferSettings.setSize( static_cast<double>( param.mBufferSize ) );
3049 }
3050
3051 textFormat.setBuffer( bufferSettings );
3052 palSettings.setFormat( textFormat );
3053
3054 QgsVectorLayerSimpleLabeling *simpleLabeling = new QgsVectorLayerSimpleLabeling( palSettings );
3055 layer->setLabeling( simpleLabeling );
3056 layer->setLabelsEnabled( true );
3057 }
3058 fet.setGeometry( param.mGeom );
3059
3060 // add feature to layer and set the SLD renderer
3061 layer->dataProvider()->addFeatures( QgsFeatureList() << fet );
3062 layer->setRenderer( renderer.release() );
3063
3064 // keep the vector as an highlight layer
3065 if ( layer->isValid() )
3066 {
3067 highlightLayers.append( layer.release() );
3068 }
3069 }
3070
3071 mTemporaryLayers.append( highlightLayers );
3072 return highlightLayers;
3073 }
3074
3075 void QgsRenderer::removeTemporaryLayers()
3076 {
3077 qDeleteAll( mTemporaryLayers );
3078 mTemporaryLayers.clear();
3079 }
3080
3081 QPainter *QgsRenderer::layersRendering( const QgsMapSettings &mapSettings, QImage &image ) const
3082 {
3083 QPainter *painter = nullptr;
3084
3086 filters.addProvider( &mFeatureFilter );
3087#ifdef HAVE_SERVER_PYTHON_PLUGINS
3088 mContext.accessControl()->resolveFilterFeatures( mapSettings.layers() );
3089 filters.addProvider( mContext.accessControl() );
3090#endif
3091 QgsMapRendererJobProxy renderJob( mContext.settings().parallelRendering(), mContext.settings().maxThreads(), &filters );
3092 renderJob.render( mapSettings, &image );
3093 painter = renderJob.takePainter();
3094
3095 if ( !renderJob.errors().isEmpty() )
3096 {
3097 const QgsMapRendererJob::Error e = renderJob.errors().at( 0 );
3098
3099 QString layerWMSName;
3100 QgsMapLayer *errorLayer = mProject->mapLayer( e.layerID );
3101 if ( errorLayer )
3102 {
3103 layerWMSName = mContext.layerNickname( *errorLayer );
3104 }
3105
3106 QString errorMessage = QStringLiteral( "Rendering error : '%1'" ).arg( e.message );
3107 if ( ! layerWMSName.isEmpty() )
3108 {
3109 errorMessage = QStringLiteral( "Rendering error : '%1' in layer '%2'" ).arg( e.message, layerWMSName );
3110 }
3111 throw QgsException( errorMessage );
3112 }
3113
3114 return painter;
3115 }
3116
3117 void QgsRenderer::setLayerOpacity( QgsMapLayer *layer, int opacity ) const
3118 {
3119 if ( opacity >= 0 && opacity <= 255 )
3120 {
3121 switch ( layer->type() )
3122 {
3124 {
3125 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
3126 vl->setOpacity( opacity / 255. );
3127 break;
3128 }
3129
3131 {
3132 QgsRasterLayer *rl = qobject_cast<QgsRasterLayer *>( layer );
3133 QgsRasterRenderer *rasterRenderer = rl->renderer();
3134 rasterRenderer->setOpacity( opacity / 255. );
3135 break;
3136 }
3137
3139 {
3140 QgsVectorTileLayer *vl = qobject_cast<QgsVectorTileLayer *>( layer );
3141 vl->setOpacity( opacity / 255. );
3142 break;
3143 }
3144
3150 break;
3151 }
3152 }
3153 }
3154
3155 void QgsRenderer::setLayerFilter( QgsMapLayer *layer, const QList<QgsWmsParametersFilter> &filters )
3156 {
3157
3158 if ( layer->type() == QgsMapLayerType::VectorLayer )
3159 {
3160 QgsVectorLayer *filteredLayer = qobject_cast<QgsVectorLayer *>( layer );
3161 QStringList expList;
3162 for ( const QgsWmsParametersFilter &filter : filters )
3163 {
3164 if ( filter.mType == QgsWmsParametersFilter::OGC_FE )
3165 {
3166 // OGC filter
3167 QDomDocument filterXml;
3168 QString errorMsg;
3169 if ( !filterXml.setContent( filter.mFilter, true, &errorMsg ) )
3170 {
3172 QStringLiteral( "Filter string rejected. Error message: %1. The XML string was: %2" ).arg( errorMsg, filter.mFilter ) );
3173 }
3174 QDomElement filterElem = filterXml.firstChildElement();
3175 std::unique_ptr<QgsExpression> filterExp( QgsOgcUtils::expressionFromOgcFilter( filterElem, filter.mVersion, filteredLayer ) );
3176
3177 if ( filterExp )
3178 {
3179 expList << filterExp->dump();
3180 }
3181 }
3182 else if ( filter.mType == QgsWmsParametersFilter::SQL )
3183 {
3184 // QGIS (SQL) filter
3185 if ( !testFilterStringSafety( filter.mFilter ) )
3186 {
3187 throw QgsSecurityException( QStringLiteral( "The filter string %1"
3188 " has been rejected because of security reasons."
3189 " Note: Text strings have to be enclosed in single or double quotes."
3190 " A space between each word / special character is mandatory."
3191 " Allowed Keywords and special characters are"
3192 " IS,NOT,NULL,AND,OR,IN,=,<,>=,>,>=,!=,',',(,),DMETAPHONE,SOUNDEX%2."
3193 " Not allowed are semicolons in the filter expression." ).arg(
3194 filter.mFilter, mContext.settings().allowedExtraSqlTokens().isEmpty() ?
3195 QString() :
3196 mContext.settings().allowedExtraSqlTokens().join( ',' ).prepend( ',' ) ) );
3197 }
3198
3199 QString newSubsetString = filter.mFilter;
3200 if ( !filteredLayer->subsetString().isEmpty() )
3201 {
3202 newSubsetString.prepend( ") AND (" );
3203 newSubsetString.append( ")" );
3204 newSubsetString.prepend( filteredLayer->subsetString() );
3205 newSubsetString.prepend( "(" );
3206 }
3207 if ( ! filteredLayer->setSubsetString( newSubsetString ) )
3208 {
3209 QgsMessageLog::logMessage( QStringLiteral( "Error setting subset string from filter for layer %1, filter: %2" ).arg( layer->name(), newSubsetString ),
3210 QStringLiteral( "Server" ),
3211 Qgis::MessageLevel::Warning );
3213 QStringLiteral( "Filter not valid for layer %1: check the filter syntax and the field names." ).arg( layer->name() ) );
3214
3215 }
3216 }
3217 }
3218
3219 expList.append( dimensionFilter( filteredLayer ) );
3220
3221 // Join and apply expressions provided by OGC filter and Dimensions
3222 QString exp;
3223 if ( expList.size() == 1 )
3224 {
3225 exp = expList[0];
3226 }
3227 else if ( expList.size() > 1 )
3228 {
3229 exp = QStringLiteral( "( %1 )" ).arg( expList.join( QLatin1String( " ) AND ( " ) ) );
3230 }
3231 if ( !exp.isEmpty() )
3232 {
3233 std::unique_ptr<QgsExpression> expression( new QgsExpression( exp ) );
3234 if ( expression )
3235 {
3236 mFeatureFilter.setFilter( filteredLayer, *expression );
3237 }
3238 }
3239 }
3240 }
3241
3242 QStringList QgsRenderer::dimensionFilter( QgsVectorLayer *layer ) const
3243 {
3244 QStringList expList;
3245 // WMS Dimension filters
3246 QgsMapLayerServerProperties *serverProperties = static_cast<QgsMapLayerServerProperties *>( layer->serverProperties() );
3247 const QList<QgsMapLayerServerProperties::WmsDimensionInfo> wmsDims = serverProperties->wmsDimensions();
3248 if ( wmsDims.isEmpty() )
3249 {
3250 return expList;
3251 }
3252
3253 QMap<QString, QString> dimParamValues = mContext.parameters().dimensionValues();
3254 for ( const QgsMapLayerServerProperties::WmsDimensionInfo &dim : wmsDims )
3255 {
3256 // Skip temporal properties for this layer, give precedence to the dimensions implementation
3257 if ( mIsTemporal && dim.name.toUpper() == QLatin1String( "TIME" ) && layer->temporalProperties()->isActive() )
3258 {
3259 layer->temporalProperties()->setIsActive( false );
3260 }
3261 // Check field index
3262 int fieldIndex = layer->fields().indexOf( dim.fieldName );
3263 if ( fieldIndex == -1 )
3264 {
3265 continue;
3266 }
3267 // Check end field index
3268 int endFieldIndex = -1;
3269 if ( !dim.endFieldName.isEmpty() )
3270 {
3271 endFieldIndex = layer->fields().indexOf( dim.endFieldName );
3272 if ( endFieldIndex == -1 )
3273 {
3274 continue;
3275 }
3276 }
3277 // Apply dimension filtering
3278 if ( !dimParamValues.contains( dim.name.toUpper() ) )
3279 {
3280 // Default value based on type configured by user
3281 QVariant defValue;
3282 if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::AllValues )
3283 {
3284 continue; // no filter by default for this dimension
3285 }
3286 else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::ReferenceValue )
3287 {
3288 defValue = dim.referenceValue;
3289 }
3290 else
3291 {
3292 // get unique values
3293 QSet<QVariant> uniqueValues = layer->uniqueValues( fieldIndex );
3294 if ( endFieldIndex != -1 )
3295 {
3296 uniqueValues.unite( layer->uniqueValues( endFieldIndex ) );
3297 }
3298 // sort unique values
3299 QList<QVariant> values = qgis::setToList( uniqueValues );
3300 std::sort( values.begin(), values.end() );
3301 if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MinValue )
3302 {
3303 defValue = values.first();
3304 }
3305 else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MaxValue )
3306 {
3307 defValue = values.last();
3308 }
3309 }
3310 // build expression
3311 if ( endFieldIndex == -1 )
3312 {
3313 expList << QgsExpression::createFieldEqualityExpression( dim.fieldName, defValue );
3314 }
3315 else
3316 {
3317 QStringList expElems;
3318 expElems << QgsExpression::quotedColumnRef( dim.fieldName )
3319 << QStringLiteral( "<=" ) << QgsExpression::quotedValue( defValue )
3320 << QStringLiteral( "AND" ) << QgsExpression::quotedColumnRef( dim.endFieldName )
3321 << QStringLiteral( ">=" ) << QgsExpression::quotedValue( defValue );
3322 expList << expElems.join( ' ' );
3323 }
3324 }
3325 else
3326 {
3327 // Get field to convert value provided in parameters
3328 QgsField dimField = layer->fields().at( fieldIndex );
3329 // Value provided in parameters
3330 QString dimParamValue = dimParamValues[dim.name.toUpper()];
3331 // The expression list for this dimension
3332 QStringList dimExplist;
3333 // Multiple values are separated by ,
3334 QStringList dimValues = dimParamValue.split( ',' );
3335 for ( int i = 0; i < dimValues.size(); ++i )
3336 {
3337 QString dimValue = dimValues[i];
3338 // Trim value if necessary
3339 if ( dimValue.size() > 1 )
3340 {
3341 dimValue = dimValue.trimmed();
3342 }
3343 // Range value is separated by / for example 0/1
3344 if ( dimValue.contains( '/' ) )
3345 {
3346 QStringList rangeValues = dimValue.split( '/' );
3347 // Check range value size
3348 if ( rangeValues.size() != 2 )
3349 {
3350 continue; // throw an error
3351 }
3352 // Get range values
3353 QVariant rangeMin = QVariant( rangeValues[0] );
3354 QVariant rangeMax = QVariant( rangeValues[1] );
3355 // Convert and check range values
3356 if ( !dimField.convertCompatible( rangeMin ) )
3357 {
3358 continue; // throw an error
3359 }
3360 if ( !dimField.convertCompatible( rangeMax ) )
3361 {
3362 continue; // throw an error
3363 }
3364 // Build expression for this range
3365 QStringList expElems;
3366 if ( endFieldIndex == -1 )
3367 {
3368 // The field values are between min and max range
3369 expElems << QgsExpression::quotedColumnRef( dim.fieldName )
3370 << QStringLiteral( ">=" ) << QgsExpression::quotedValue( rangeMin )
3371 << QStringLiteral( "AND" ) << QgsExpression::quotedColumnRef( dim.fieldName )
3372 << QStringLiteral( "<=" ) << QgsExpression::quotedValue( rangeMax );
3373 }
3374 else
3375 {
3376 // The start field or the end field are lesser than min range
3377 // or the start field or the end field are greater than min range
3378 expElems << QStringLiteral( "(" ) << QgsExpression::quotedColumnRef( dim.fieldName )
3379 << QStringLiteral( ">=" ) << QgsExpression::quotedValue( rangeMin )
3380 << QStringLiteral( "OR" ) << QgsExpression::quotedColumnRef( dim.endFieldName )
3381 << QStringLiteral( ">=" ) << QgsExpression::quotedValue( rangeMin )
3382 << QStringLiteral( ") AND (" ) << QgsExpression::quotedColumnRef( dim.fieldName )
3383 << QStringLiteral( "<=" ) << QgsExpression::quotedValue( rangeMax )
3384 << QStringLiteral( "OR" ) << QgsExpression::quotedColumnRef( dim.endFieldName )
3385 << QStringLiteral( "<=" ) << QgsExpression::quotedValue( rangeMax )
3386 << QStringLiteral( ")" );
3387 }
3388 dimExplist << expElems.join( ' ' );
3389 }
3390 else
3391 {
3392 QVariant dimVariant = QVariant( dimValue );
3393 if ( !dimField.convertCompatible( dimVariant ) )
3394 {
3395 continue; // throw an error
3396 }
3397 // Build expression for this value
3398 if ( endFieldIndex == -1 )
3399 {
3400 // Field is equal to
3401 dimExplist << QgsExpression::createFieldEqualityExpression( dim.fieldName, dimVariant );
3402 }
3403 else
3404 {
3405 // The start field is lesser or equal to
3406 // and the end field is greater or equal to
3407 QStringList expElems;
3408 expElems << QgsExpression::quotedColumnRef( dim.fieldName )
3409 << QStringLiteral( "<=" ) << QgsExpression::quotedValue( dimVariant )
3410 << QStringLiteral( "AND" ) << QgsExpression::quotedColumnRef( dim.endFieldName )
3411 << QStringLiteral( ">=" ) << QgsExpression::quotedValue( dimVariant );
3412 dimExplist << expElems.join( ' ' );
3413 }
3414 }
3415 }
3416 // Build the expression for this dimension
3417 if ( dimExplist.size() == 1 )
3418 {
3419 expList << dimExplist;
3420 }
3421 else if ( dimExplist.size() > 1 )
3422 {
3423 expList << QStringLiteral( "( %1 )" ).arg( dimExplist.join( QLatin1String( " ) OR ( " ) ) );
3424 }
3425 }
3426 }
3427 return expList;
3428 }
3429
3430 void QgsRenderer::setLayerSelection( QgsMapLayer *layer, const QStringList &fids ) const
3431 {
3432 if ( !fids.empty() && layer->type() == QgsMapLayerType::VectorLayer )
3433 {
3434 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
3435
3436 QgsFeatureRequest request;
3438 const QgsFeatureIds selectedIds = request.filterFids();
3439
3440 if ( selectedIds.empty() )
3441 {
3443 }
3444 else
3445 {
3446 vl->selectByIds( selectedIds );
3447 }
3448 }
3449 }
3450
3451 void QgsRenderer::setLayerAccessControlFilter( QgsMapLayer *layer ) const
3452 {
3453#ifdef HAVE_SERVER_PYTHON_PLUGINS
3454 QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( mContext.accessControl(), layer );
3455#else
3456 Q_UNUSED( layer )
3457#endif
3458 }
3459
3460 void QgsRenderer::updateExtent( const QgsMapLayer *layer, QgsMapSettings &mapSettings ) const
3461 {
3462 QgsRectangle layerExtent = mapSettings.layerToMapCoordinates( layer, layer->extent() );
3463 QgsRectangle mapExtent = mapSettings.extent();
3464 if ( !layerExtent.isEmpty() )
3465 {
3466 mapExtent.combineExtentWith( layerExtent );
3467 mapSettings.setExtent( mapExtent );
3468 }
3469 }
3470
3471 void QgsRenderer::annotationsRendering( QPainter *painter, const QgsMapSettings &mapSettings ) const
3472 {
3473 const QgsAnnotationManager *annotationManager = mProject->annotationManager();
3474 const QList< QgsAnnotation * > annotations = annotationManager->annotations();
3475
3476 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( painter );
3478 for ( QgsAnnotation *annotation : annotations )
3479 {
3480 if ( !annotation || !annotation->isVisible() )
3481 continue;
3482
3483 //consider item position
3484 double offsetX = 0;
3485 double offsetY = 0;
3486 if ( annotation->hasFixedMapPosition() )
3487 {
3488 QgsPointXY mapPos = annotation->mapPosition();
3489 if ( mapSettings.destinationCrs() != annotation->mapPositionCrs() )
3490 {
3491 QgsCoordinateTransform coordTransform( annotation->mapPositionCrs(), mapSettings.destinationCrs(), mapSettings.transformContext() );
3492 try
3493 {
3494 mapPos = coordTransform.transform( mapPos );
3495 }
3496 catch ( const QgsCsException &e )
3497 {
3498 QgsMessageLog::logMessage( QStringLiteral( "Error transforming coordinates of annotation item: %1" ).arg( e.what() ) );
3499 }
3500 }
3501 const QgsPointXY devicePos = mapSettings.mapToPixel().transform( mapPos );
3502 offsetX = devicePos.x();
3503 offsetY = devicePos.y();
3504 }
3505 else
3506 {
3507 const QPointF relativePos = annotation->relativePosition();
3508 offsetX = mapSettings.outputSize().width() * relativePos.x();
3509 offsetY = mapSettings.outputSize().height() * relativePos.y();
3510 }
3511
3512 painter->save();
3513 painter->translate( offsetX, offsetY );
3514 annotation->render( renderContext );
3515 painter->restore();
3516 }
3517 }
3518
3519 QImage *QgsRenderer::scaleImage( const QImage *image ) const
3520 {
3521 // Test if width / height ratio of image is the same as the ratio of
3522 // WIDTH / HEIGHT parameters. If not, the image has to be scaled (required
3523 // by WMS spec)
3524 QImage *scaledImage = nullptr;
3525 const int width = mWmsParameters.widthAsInt();
3526 const int height = mWmsParameters.heightAsInt();
3527 if ( width != image->width() || height != image->height() )
3528 {
3529 scaledImage = new QImage( image->scaled( width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
3530 }
3531
3532 return scaledImage;
3533 }
3534
3535 void QgsRenderer::handlePrintErrors( const QgsLayout *layout ) const
3536 {
3537 if ( !layout )
3538 {
3539 return;
3540 }
3541 QList< QgsLayoutItemMap * > mapList;
3542 layout->layoutItems( mapList );
3543
3544 QList< QgsLayoutItemMap * >::const_iterator mapIt = mapList.constBegin();
3545 for ( ; mapIt != mapList.constEnd(); ++mapIt )
3546 {
3547 if ( !( *mapIt )->renderingErrors().isEmpty() )
3548 {
3549 const QgsMapRendererJob::Error e = ( *mapIt )->renderingErrors().at( 0 );
3550 throw QgsException( QStringLiteral( "Rendering error : '%1' in layer %2" ).arg( e.message, e.layerID ) );
3551 }
3552 }
3553 }
3554
3555 void QgsRenderer::configureLayers( QList<QgsMapLayer *> &layers, QgsMapSettings *settings )
3556 {
3557 const bool useSld = !mContext.parameters().sldBody().isEmpty();
3558
3559 for ( auto layer : layers )
3560 {
3561 const QgsWmsParametersLayer param = mContext.parameters( *layer );
3562
3563 if ( ! mContext.layersToRender().contains( layer ) )
3564 {
3565 continue;
3566 }
3567
3568 if ( mContext.isExternalLayer( param.mNickname ) )
3569 {
3570 if ( mContext.testFlag( QgsWmsRenderContext::UseOpacity ) )
3571 {
3572 setLayerOpacity( layer, param.mOpacity );
3573 }
3574 continue;
3575 }
3576
3577 if ( useSld )
3578 {
3579 setLayerSld( layer, mContext.sld( *layer ) );
3580 }
3581 else
3582 {
3583 setLayerStyle( layer, mContext.style( *layer ) );
3584 }
3585
3586 if ( mContext.testFlag( QgsWmsRenderContext::UseOpacity ) )
3587 {
3588 setLayerOpacity( layer, param.mOpacity );
3589 }
3590
3591 if ( mContext.testFlag( QgsWmsRenderContext::UseFilter ) )
3592 {
3593 setLayerFilter( layer, param.mFilter );
3594 }
3595
3597 {
3598 setLayerAccessControlFilter( layer );
3599 }
3600
3602 {
3603 setLayerSelection( layer, param.mSelection );
3604 }
3605
3606 if ( settings && mContext.updateExtent() )
3607 {
3608 updateExtent( layer, *settings );
3609 }
3610 }
3611
3613 {
3614 layers = highlightLayers( mWmsParameters.highlightLayersParameters() ) << layers;
3615 }
3616 }
3617
3618 void QgsRenderer::setLayerStyle( QgsMapLayer *layer, const QString &style ) const
3619 {
3620 if ( style.isEmpty() )
3621 {
3622 return;
3623 }
3624
3625 bool rc = layer->styleManager()->setCurrentStyle( style );
3626 if ( ! rc )
3627 {
3629 QStringLiteral( "Style '%1' does not exist for layer '%2'" ).arg( style, layer->name() ) );
3630 }
3631 }
3632
3633 void QgsRenderer::setLayerSld( QgsMapLayer *layer, const QDomElement &sld ) const
3634 {
3635 QString err;
3636 // Defined sld style name
3637 const QStringList styles = layer->styleManager()->styles();
3638 QString sldStyleName = "__sld_style";
3639 while ( styles.contains( sldStyleName ) )
3640 {
3641 sldStyleName.append( '@' );
3642 }
3643 layer->styleManager()->addStyleFromLayer( sldStyleName );
3644 layer->styleManager()->setCurrentStyle( sldStyleName );
3645 layer->readSld( sld, err );
3646 layer->setCustomProperty( "sldStyleName", sldStyleName );
3647 }
3648
3649 QgsLegendSettings QgsRenderer::legendSettings()
3650 {
3651 // getting scale from bbox or default size
3652 QgsLegendSettings settings = mWmsParameters.legendSettings();
3653
3654 if ( !mWmsParameters.bbox().isEmpty() )
3655 {
3656 QgsMapSettings mapSettings;
3658 std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
3659 configureMapSettings( tmp.get(), mapSettings );
3660 // QGIS 4.0 - require correct use of QgsRenderContext instead of these
3662 settings.setMapScale( mapSettings.scale() );
3663 settings.setMapUnitsPerPixel( mapSettings.mapUnitsPerPixel() );
3665 }
3666 else
3667 {
3668 // QGIS 4.0 - require correct use of QgsRenderContext instead of these
3670 const double defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mContext.project() ) / mContext.dotsPerMm();
3671 settings.setMapUnitsPerPixel( defaultMapUnitsPerPixel );
3673 }
3674
3675 return settings;
3676 }
3677} // namespace QgsWms
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition: qgis.h:561
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
@ Antialiasing
Use antialiasing while drawing.
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
@ AllowOverlapIfRequired
Avoids overlapping labels when possible, but permit overlaps if labels for features cannot otherwise ...
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Abstract base class for all geometries.
virtual QgsAbstractGeometry * segmentize(double tolerance=M_PI/180., SegmentationToleranceType toleranceType=MaximumAngle) const
Returns a version of the geometry without curves.
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns the WKB type of the geometry.
Manages storage of a set of QgsAnnotation annotation objects.
QList< QgsAnnotation * > annotations() const
Returns a list of all annotations contained in the manager.
Abstract base class for annotation items which are drawn over a map.
Definition: qgsannotation.h:54
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
This is an abstract base class for any elements of a drag and drop form.
QString name() const
Returns the name of this element.
This element will load a field's widget onto the form.
A vector of attributes.
Definition: qgsattributes.h:59
Exception thrown in case of malformed request.
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.
Q_GADGET QgsUnitTypes::DistanceUnit mapUnits
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.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
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
A server filter to apply a dimension filter to a request.
Contains configuration settings for an editor form.
QgsAttributeEditorContainer * invisibleRootContainer()
Gets the invisible root container for the drag and drop designer form (EditorLayout::TabLayout).
@ TabLayout
Use a layout with tabs and group boxes. Needs to be configured.
EditorLayout layout() const
Gets the active layout style for the attribute editor for this layer.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
Defines a QGIS exception class.
Definition: qgsexception.h:35
QString what() const
Definition: qgsexception.h:48
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
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...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
QString expression() const
Returns the original, unmodified expression string.
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required.
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value, QVariant::Type fieldType=QVariant::Type::Invalid)
Create an expression allowing to evaluate if a field is equal to a value.
static QString replaceExpressionText(const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea=nullptr)
This function replaces each expression between [% and %] in the string with the result of its evaluat...
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
A filter filter provider grouping several filter providers.
QgsFeatureFilterProviderGroup & addProvider(const QgsFeatureFilterProvider *provider)
Add another filter provider to the group.
void setFilter(const QgsVectorLayer *layer, const QgsExpression &expression)
Set a filter for the given layer.
void filterFeatures(const QgsVectorLayer *layer, QgsFeatureRequest &filterFeatures) const override
Filter the features of the layer.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
static QgsFeatureRenderer * loadSld(const QDomNode &node, QgsWkbTypes::GeometryType geomType, QString &errorMessage)
Create a new renderer according to the information contained in the UserStyle element of a SLD style ...
@ MoreSymbolsPerFeature
May use more than one symbol to render a feature: symbolsForFeature() will return them.
Definition: qgsrenderer.h:264
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
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.
QgsExpression * filterExpression() const
Returns the filter expression (if set).
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.
QgsCoordinateTransformContext transformContext() const
Returns the transform context, for use when a destinationCrs() has been set and reprojection is requi...
const QgsFeatureIds & filterFids() const
Returns the feature IDs that should be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
A container for features with the same fields and crs.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:265
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsFields fields
Definition: qgsfeature.h:66
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
Definition: qgsfeature.cpp:238
void setFields(const QgsFields &fields, bool initAttributes=false)
Assigns a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:198
QgsGeometry geometry
Definition: qgsfeature.h:67
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:338
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:170
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
A field formatter helps to handle and display values for a field.
virtual QString representValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const
Create a pretty String representation of the value.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition: qgsfield.cpp:412
ConfigurationFlags configurationFlags
Definition: qgsfield.h:64
@ HideFromWms
Field is not available if layer is served as WMS from QGIS server.
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 indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsGeometry pointOnSurface() const
Returns a point guaranteed to lie on the surface of a geometry.
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
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:167
void set(QgsAbstractGeometry *geometry)
Sets the underlying geometry store.
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
QString asWkt(int precision=17) const
Exports the geometry to WKT.
Handles exporting QgsFeature features to GeoJSON features.
Definition: qgsjsonutils.h:41
void setPlacementFlags(QgsLabeling::LinePlacementFlags flags)
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
void setOverlapHandling(Qgis::LabelOverlapHandling handling)
Sets the technique used to handle overlapping labels.
void setAllowDegradedPlacement(bool allow)
Sets whether labels can be placed in inferior fallback positions if they cannot otherwise be placed.
QStringList findLayerIds() const
Find layer IDs used in all layer nodes.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
void removeChildrenGroupWithoutLayers()
Remove all child group nodes without layers.
Layer tree node points to a map layer.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
virtual QSizeF drawSymbol(const QgsLegendSettings &settings, ItemContext *ctx, double itemHeight) const
Draws symbol on the left side of the item.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QgsLayerTree * rootGroup() const
Returns pointer to the root node of the layer tree. Always a non nullptr value.
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is nullptr, the node is a root node.
Namespace with helper functions for layer tree operations.
Definition: qgslayertree.h:33
Class used to render QgsLayout as an atlas, by iterating over the features from an associated vector ...
bool beginRender() override
Called when rendering begins, before iteration commences.
bool setFilterExpression(const QString &expression, QString &errorString)
Sets the expression used for filtering features in the coverage layer.
QgsLayout * layout() override
Returns the layout associated with the iterator.
bool enabled() const
Returns whether the atlas generation is enabled.
int count() const override
Returns the number of features to iterate over.
void setFilterFeatures(bool filtered)
Sets whether features should be filtered in the coverage layer.
QgsVectorLayer * coverageLayer() const
Returns the coverage layer used for the atlas features.
bool next() override
int updateFeatures()
Requeries the current atlas coverage layer and applies filtering and sorting.
Handles rendering and exports of layouts to various formats.
ExportResult exportToSvg(const QString &filePath, const QgsLayoutExporter::SvgExportSettings &settings)
Exports the layout as an SVG to the filePath, using the specified export settings.
ExportResult exportToImage(const QString &filePath, const QgsLayoutExporter::ImageExportSettings &settings)
Exports the layout to the filePath, using the specified export settings.
ExportResult exportToPdf(const QString &filePath, const QgsLayoutExporter::PdfExportSettings &settings)
Exports the layout as a PDF to the filePath, using the specified export settings.
Base class for frame items, which form a layout multiframe item.
A layout multiframe subclass for HTML content.
A layout item subclass for text labels.
A layout item subclass for map legends.
Layout graphical items for displaying a map.
double scale() const
Returns the map scale.
QList< QgsMapLayer * > layers() const
Returns the stored layer set.
QString id() const
Returns the item's ID name.
Manages storage of a set of layouts.
QgsMasterLayoutInterface * layoutByName(const QString &name) const
Returns the layout with a matching name, or nullptr if no matching layouts were found.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
double length() const
Returns the length of the measurement.
int pageCount() const
Returns the number of pages in the collection.
Stores information relating to the current rendering settings for a layout.
void setFeatureFilterProvider(QgsFeatureFilterProvider *featureFilterProvider)
Sets feature filter provider to featureFilterProvider.
@ FlagDrawSelection
Draw selection.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
static QVector< double > predefinedScales(const QgsLayout *layout)
Returns a list of predefined scales associated with a layout.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:122
Item model implementation based on layer tree model for layout legend.
The QgsLegendRenderer class handles automatic layout and rendering of legend.
QSizeF minimumSize(QgsRenderContext *renderContext=nullptr)
Runs the layout algorithm and returns the minimum size required for the legend.
QJsonObject exportLegendToJson(const QgsRenderContext &context)
Renders the legend in a json object.
Q_DECL_DEPRECATED void drawLegend(QPainter *painter)
Draws the legend with given painter.
The QgsLegendSettings class stores the appearance and layout settings for legend drawing with QgsLege...
Q_DECL_DEPRECATED void setMapScale(double scale)
Sets the legend map scale.
Q_DECL_DEPRECATED void setMapUnitsPerPixel(double mapUnitsPerPixel)
Sets the mmPerMapUnit calculated by mapUnitsPerPixel mostly taken from the map settings.
Q_DECL_DEPRECATED double mmPerMapUnit() const
Q_DECL_DEPRECATED double mapScale() const
Returns the legend map scale.
Manages QGIS Server properties for a map layer.
QStringList styles() const
Returns list of all defined style names.
bool setCurrentStyle(const QString &name)
Set a different style as the current style - will apply it to the layer.
bool addStyleFromLayer(const QString &name)
Add style by cloning the current one.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QString name
Definition: qgsmaplayer.h:76
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
virtual QgsRectangle extent() const
Returns the extent of the layer.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the map layer.
Definition: qgsmaplayer.h:427
virtual void setOpacity(double opacity)
Sets the opacity for the layer, where opacity is a value between 0 (totally transparent) and 1....
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
Definition: qgsmaplayer.h:146
virtual bool readSld(const QDomNode &node, QString &errorMessage)
Definition: qgsmaplayer.h:1162
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer's style manager.
virtual Q_INVOKABLE QgsDataProvider * dataProvider()
Returns the layer's data provider, it may be nullptr.
QString mapTipTemplate
Definition: qgsmaplayer.h:83
The QgsMapSettings class contains configuration for rendering of the map.
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer's CRS to output CRS
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in the map.
void setSelectionColor(const QColor &color)
Sets the color that is used for drawing of selected vector features.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
double scale() const
Returns the calculated map scale.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer's CRS to destination CRS.
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer's CRS to output CRS
QStringList layerIds(bool expandGroupLayers=false) const
Returns the list of layer IDs which will be rendered in the map.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
const QgsMapToPixel & mapToPixel() const
double mapUnitsPerPixel() const
Returns the distance in geographical coordinates that equals to one pixel in the map.
QSize outputSize() const
Returns the size of the resulting map image, in pixels.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QColor selectionColor() const
Returns the color that is used for drawing of selected vector features.
void setExtentBuffer(double buffer)
Sets the buffer in map units to use around the visible extent for rendering symbols whose correspondi...
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QList< QgsMapThemeCollection::MapThemeLayerRecord > layerRecords() const
Returns a list of records for all visible layer belonging to the theme.
QgsMapThemeCollection::MapThemeRecord mapThemeState(const QString &name) const
Returns the recorded state of a map theme.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
Definition: qgsmaptopixel.h:90
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).
static void applyAccessControlLayerFilters(const QgsAccessControl *accessControl, QgsMapLayer *mapLayer, QHash< QgsMapLayer *, QString > &originalLayerFilters)
Apply filter from AccessControl.
static QDomElement geometryToGML(const QgsGeometry &geometry, QDomDocument &doc, QgsOgcUtils::GMLVersion gmlVersion, const QString &srsName, bool invertAxisOrientation, const QString &gmlIdBase, int precision=17)
Exports the geometry to GML.
static QDomElement rectangleToGMLEnvelope(QgsRectangle *env, QDomDocument &doc, int precision=17)
Exports the rectangle to GML3 Envelope.
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
static QDomElement rectangleToGMLBox(QgsRectangle *box, QDomDocument &doc, int precision=17)
Exports the rectangle to GML2 Box.
Contains settings for how a map layer will be labeled.
const QgsLabelPlacementSettings & placementSettings() const
Returns the label placement settings.
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
Qgis::LabelPlacement placement
Label placement mode.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the label's property collection, used for data defined overrides.
int priority
Label priority.
Property
Data definable properties.
@ LabelRotation
Label rotation.
@ PositionY
Y-coordinate data defined label position.
@ PositionX
X-coordinate data defined label position.
@ Hali
Horizontal alignment for data defined label position (Left, Center, Right)
@ Vali
Vertical alignment for data defined label position (Bottom, Base, Half, Cap, Top)
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
double dist
Distance from feature to the label.
QString fieldName
Name of field (or an expression) to use for label text.
A class to represent a 2D point.
Definition: qgspointxy.h:59
void setX(double x) SIP_HOLDGIL
Sets the x value of the point.
Definition: qgspointxy.h:122
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
void setY(double y) SIP_HOLDGIL
Sets the y value of the point.
Definition: qgspointxy.h:132
Print layout, a QgsLayout subclass for static or atlas-based layouts.
QgsPrintLayout * clone() const override
Creates a clone of the layout.
A class to describe the version of a project.
QColor selectionColor
Definition: qgsproject.h:120
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:476
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:113
QgsAnnotationManager * annotationManager()
Returns pointer to the project's annotation manager.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:111
const QgsLayoutManager * layoutManager() const
Returns the project's layout manager, which manages print layouts, atlases and reports within the pro...
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns project's global labeling engine settings.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
virtual QgsRasterIdentifyResult identify(const QgsPointXY &point, QgsRaster::IdentifyFormat format, const QgsRectangle &boundingBox=QgsRectangle(), int width=0, int height=0, int dpi=96)
Identify raster value(s) found on the point position.
Raster identify results container.
bool isValid() const
Returns true if valid.
QMap< int, QVariant > results() const
Returns the identify results.
@ IdentifyValue
Numerical values.
@ IdentifyFeature
WMS GML -> feature.
virtual int capabilities() const
Returns a bitmask containing the supported capabilities.
Represents a raster layer.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
QString bandName(int bandNoInt) const
Returns the name of a band given its number.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
Raster renderer pipe that applies colors to a raster.
void setOpacity(double opacity)
Sets the opacity for the renderer, where opacity is a value between 0 (totally transparent) and 1....
IdentifyFormat
Definition: qgsraster.h:58
@ IdentifyFormatValue
Definition: qgsraster.h:60
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
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
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
void invert()
Swap x/y coordinates in the rectangle.
Definition: qgsrectangle.h:575
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
void setCoordinateTransform(const QgsCoordinateTransform &t)
Sets the current coordinate transform for the context.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setExtent(const QgsRectangle &extent)
When rendering a map layer, calling this method sets the "clipping" extent for the layer (in the laye...
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
void setRendererScale(double scale)
Sets the renderer map scale.
Calculates scale for a given combination of canvas size, map extent, and monitor dpi.
double calculate(const QgsRectangle &mapExtent, double canvasWidth) const
Calculate the scale denominator.
Scoped object for temporary scaling of a QgsRenderContext for millimeter based rendering.
Bad request error API exception.
static QgsDateTimeRange parseTemporalDateTimeInterval(const QString &interval) SIP_THROW(QgsServerApiBadRequestException)
Parses a datetime interval and returns a QgsDateTimeRange.
Exception base class for server exceptions.
int maxThreads() const
Returns the maximum number of threads to use.
QStringList allowedExtraSqlTokens() const
Returns the list of strings that represent the allowed extra SQL tokens accepted as components of a f...
bool parallelRendering() const
Returns parallel rendering setting.
const QList< QgsServerWmsDimensionProperties::WmsDimensionInfo > wmsDimensions() const
Returns the QGIS Server WMS Dimension list.
static QString symbolProperties(QgsSymbol *symbol)
Returns a string representing the symbol.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
bool isActive() const
Returns true if the temporal property is active.
void setIsActive(bool active)
Sets whether the temporal property is active.
void setIsTemporal(bool enabled)
Sets whether the temporal range is enabled (i.e.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
Container for settings relating to a text buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
void setSize(double size)
Sets the size of the buffer.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setSize(double size)
Sets the size for rendered text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
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.
Basic implementation of the labeling interface.
Represents a vector layer which manages a vector based data sets.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
QgsMapLayerTemporalProperties * temporalProperties() override
Returns the layer's temporal properties.
void updateFields()
Will regenerate the fields property of this layer by obtaining all fields from the dataProvider,...
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.
Q_INVOKABLE void selectByExpression(const QString &expression, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection, QgsExpressionContext *context=nullptr)
Selects matching features using an expression.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QString subsetString
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
QgsEditorWidgetSetup editorWidgetSetup(int index) const
The editor widget setup defines which QgsFieldFormatter and editor widget will be used for the field ...
QgsFeature getFeature(QgsFeatureId fid) const
Queries the layer for the feature with the given id.
Q_INVOKABLE void selectByIds(const QgsFeatureIds &ids, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection)
Selects matching features using a list of feature IDs.
virtual bool setSubsetString(const QString &subset)
Sets the string (typically sql) used to define a subset of the layer.
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
QgsEditFormConfig editFormConfig
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
Implements a map layer that is dedicated to rendering of vector tiles.
static QString displayString(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static bool isCurvedType(Type type) SIP_HOLDGIL
Returns true if the WKB type is a curved type or can contain curved geometries.
Definition: qgswkbtypes.h:911
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:732
Exception thrown in case of malformed request.
QgsRenderer(const QgsWmsRenderContext &context)
Constructor for QgsRenderer.
QHash< QgsVectorLayer *, SymbolSet > HitTest
QByteArray getPrint()
Returns printed page as binary.
HitTest symbols()
Returns the hit test according to the current context.
std::unique_ptr< QgsDxfExport > getDxf()
Returns the map as DXF data.
QSet< QString > SymbolSet
void configureLayers(QList< QgsMapLayer * > &layers, QgsMapSettings *settings=nullptr)
Configures layers for rendering optionally considering the map settings.
QJsonObject getLegendGraphicsAsJson(QgsLayerTreeModel &model)
Returns the map legend as a JSON object.
QByteArray getFeatureInfo(const QString &version="1.3.0")
Creates an xml document that describes the result of the getFeatureInfo request.
QImage * getLegendGraphics(QgsLayerTreeModel &model)
Returns the map legend as an image (or nullptr in case of error).
QImage * getMap()
Returns the map as an image (or nullptr in case of error).
~QgsRenderer()
Destructor for QgsRenderer.
Exception class for WMS service exceptions.
ExceptionCode
Exception codes as defined in OGC scpecifications for WMS 1.1.1 and WMS 1.3.0.
WMS parameter received from the client.
double dxfScale() const
Returns the DXF SCALE parameter.
bool transparentAsBool() const
Returns TRANSPARENT parameter as a bool or its default value if not defined.
QString x() const
Returns X parameter or an empty string if not defined.
QMap< DxfFormatOption, QString > dxfFormatOptions() const
Returns a map of DXF options defined within FORMAT_OPTIONS parameter.
QString formatAsString() const
Returns FORMAT parameter as a string.
QgsProjectVersion versionAsNumber() const
Returns VERSION parameter if defined or its default value.
QgsWmsParametersComposerMap composerMapParameters(int mapId) const
Returns the requested parameters for a composer map parameter.
QgsRectangle bboxAsRectangle() const
Returns BBOX as a rectangle if defined and valid.
bool withGeometry() const
Returns if the client wants the feature info response with geometry information.
QString pointTolerance() const
Returns FI_POINT_TOLERANCE parameter or an empty string if not defined.
QString filterGeom() const
Returns the filter geometry found in FILTER_GEOM parameter.
QString composerTemplate() const
Returns TEMPLATE parameter or an empty string if not defined.
Format infoFormat() const
Returns infoFormat.
QString y() const
Returns Y parameter or an empty string if not defined.
void dump() const
Dumps parameters.
int pointToleranceAsInt() const
Returns FI_POINT_TOLERANCE parameter as an integer.
bool withMapTip() const
withMapTip
QString polygonTolerance() const
Returns FI_POLYGON_TOLERANCE parameter or an empty string if not defined.
QString i() const
Returns I parameter or an empty string if not defined.
int lineToleranceAsInt() const
Returns FI_LINE_TOLERANCE parameter as an integer.
QString lineTolerance() const
Returns FI_LINE_TOLERANCE parameter or an empty string if not defined.
QString j() const
Returns J parameter or an empty string if not defined.
int xAsInt() const
Returns X parameter as an int or its default value if not defined.
QString bbox() const
Returns BBOX if defined or an empty string.
int heightAsInt() const
Returns HEIGHT parameter as an int or its default value if not defined.
QColor backgroundColorAsColor() const
Returns BGCOLOR parameter as a QColor or its default value if not defined.
Format format() const
Returns format.
QStringList atlasPk() const
Returns the ATLAS_PK parameter.
QList< QgsWmsParametersHighlightLayer > highlightLayersParameters() const
Returns parameters for each highlight layer.
int iAsInt() const
Returns I parameter as an int or its default value if not defined.
int polygonToleranceAsInt() const
Returns FI_POLYGON_TOLERANCE parameter as an integer.
QgsDxfExport::SymbologyExport dxfMode() const
Returns the DXF MODE parameter.
int widthAsInt() const
Returns WIDTH parameter as an int or its default value if not defined.
QString sldBody() const
Returns SLD_body if defined or an empty string.
QString layoutParameter(const QString &id, bool &ok) const
Returns a layout parameter thanks to its id.
bool dxfUseLayerTitleAsName() const
Returns the DXF USE_TITLE_AS_LAYERNAME parameter.
QMap< QString, QString > dimensionValues() const
Returns the dimensions parameter.
int infoFormatVersion() const
Returns the infoFormat version for GML.
QgsLegendSettings legendSettings() const
Returns legend settings.
QStringList dxfLayerAttributes() const
Returns the DXF LAYERATTRIBUTES parameter.
QString height() const
Returns HEIGHT parameter or an empty string if not defined.
QString crs() const
Returns CRS or an empty string if none is defined.
int featureCountAsInt() const
Returns FEATURE_COUNT as an integer.
int yAsInt() const
Returns Y parameter as an int or its default value if not defined.
Format
Output format for the response.
QString width() const
Returns WIDTH parameter or an empty string if not defined.
QStringList filters() const
Returns the list of filters found in FILTER parameter.
QString dpi() const
Returns DPI parameter or an empty string if not defined.
int jAsInt() const
Returns J parameter as an int or its default value if not defined.
QStringList queryLayersNickname() const
Returns nickname of layers found in QUERY_LAYERS parameter.
Rendering context for the WMS renderer.
QSize mapSize(bool aspectRatio=true) const
Returns the size (in pixels) of the map to render, according to width and height WMS parameters as we...
bool isExternalLayer(const QString &name) const
Returns true if the layer is an external layer, false otherwise.
bool isValidGroup(const QString &name) const
Returns true if name is a group.
QStringList flattenedQueryLayers(const QStringList &layerNames) const
Returns a list of query layer names where group names are replaced by the names of their layer compon...
QList< QgsMapLayer * > layersToRender() const
Returns a list of all layers to actually render according to the current configuration.
QgsMapLayer * layer(const QString &nickname) const
Returns the layer corresponding to the nickname, or a nullptr if not found or if the layer do not nee...
bool updateExtent() const
Returns true if the extent has to be updated before the rendering, false otherwise.
const QgsServerSettings & settings() const
Returns settings of the server.
bool isValidWidthHeight() const
Returns true if width and height are valid according to the maximum values defined within the project...
QList< QgsMapLayer * > layersFromGroup(const QString &nickname) const
Returns the group's layers list corresponding to the nickname, or an empty list if not found.
QgsWmsParameters parameters() const
Returns WMS parameters.
void setScaleDenominator(double scaleDenominator)
Sets a custom scale denominator.
QString style(const QgsMapLayer &layer) const
Returns a style's name for a specific layer.
QMap< QString, QList< QgsMapLayer * > > layerGroups() const
Returns a map having layer group names as keys and a list of layers as values.
double mapTileBuffer(int mapWidth) const
Returns the tile buffer in geographical units for the given map width in pixels.
QString layerNickname(const QgsMapLayer &layer) const
Returns the nickname (short name, id or name) of the layer according to the current configuration.
qreal dotsPerMm() const
Returns default dots per mm according to the current configuration.
bool testFlag(Flag flag) const
Returns the status of a rendering flag.
QDomElement sld(const QgsMapLayer &layer) const
Returns a SLD document for a specific layer.
bool isValidLayer(const QString &nickname) const
Returns true if the layer has to be rendered, false otherwise.
const QgsProject * project() const
Returns the project.
int precision() const
Returns the precision to use according to the current configuration.
bool renderMapTiles() const
Returns true if WMS requests should use the QgsMapSettings::RenderMapTile flag, so that no visible ar...
RAII class to restore the rendering context configuration on destruction.
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
SERVER_EXPORT QString getExpressionFromServerFid(const QString &serverFid, const QgsVectorDataProvider *provider)
Returns the expression feature id based on primary keys.
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 QString wmsFeatureInfoSchema(const QgsProject &project)
Returns the schema URL for XML GetFeatureInfo request.
SERVER_EXPORT bool wmsInfoFormatSia2045(const QgsProject &project)
Returns if the info format is SIA20145.
SERVER_EXPORT QString wmsFeatureInfoDocumentElementNs(const QgsProject &project)
Returns the document element namespace for XML GetFeatureInfo request.
SERVER_EXPORT QStringList wmsRestrictedComposers(const QgsProject &project)
Returns the restricted composer list.
SERVER_EXPORT bool wmsFeatureInfoSegmentizeWktGeometry(const QgsProject &project)
Returns if the geometry has to be segmentize in GetFeatureInfo request.
SERVER_EXPORT bool wmsFeatureInfoUseAttributeFormSettings(const QgsProject &project)
Returns if feature form settings should be considered for the format of the feature info response.
SERVER_EXPORT QHash< QString, QString > wmsFeatureInfoLayerAliasMap(const QgsProject &project)
Returns the mapping between layer name and wms layer name for GetFeatureInfo request.
SERVER_EXPORT bool wmsFeatureInfoAddWktGeometry(const QgsProject &project)
Returns if the geometry is displayed as Well Known Text in GetFeatureInfo request.
SERVER_EXPORT double wmsDefaultMapUnitsPerMm(const QgsProject &project)
Returns the default number of map units per millimeters in case of the scale is not given.
SERVER_EXPORT QString wmsFeatureInfoDocumentElement(const QgsProject &project)
Returns the document element name for XML GetFeatureInfo request.
SERVER_EXPORT int wmsMaxAtlasFeatures(const QgsProject &project)
Returns the maximum number of atlas features which can be printed in a request.
Median cut implementation.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define str(x)
Definition: qgis.cpp:37
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:3499
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:2854
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:3498
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2915
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:922
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
#define FID_TO_STRING(fid)
Definition: qgsfeatureid.h:33
QVector< QgsFeatureStore > QgsFeatureStoreList
QList< int > QgsAttributeList
Definition: qgsfield.h:26
const QgsField & field
Definition: qgsfield.h:463
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
const QgsCoordinateReferenceSystem & outputCrs
const QgsCoordinateReferenceSystem & crs
const QString & typeName
bool withGeom
Layers and optional attribute index to split into multiple layers using attribute value as layer name...
Definition: qgsdxfexport.h:74
Contains settings relating to exporting layouts to raster images.
QList< int > pages
List of specific pages to export, or an empty list to export all pages.
QSize imageSize
Manual size in pixels for output image.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
Contains settings relating to exporting layouts to PDF.
bool rasterizeWholeImage
Set to true to force whole layout to be rasterized while exporting.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
Contains settings relating to exporting layouts to SVG.
double dpi
Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.
QgsLayoutRenderContext::Flags flags
Layout context flags, which control how the export will be created.
QVector< qreal > predefinedMapScales
A list of predefined scales to use with the layout.
Setting to define QGIS Server WMS Dimension.
@ MaxValue
Modify current selection to include only select features which match.
Setting options for loading vector layers.
QList< QgsWmsParametersLayer > mLayers
QList< QgsWmsParametersHighlightLayer > mHighlightLayers
QList< QgsWmsParametersFilter > mFilter