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