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