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