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