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