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