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