QGIS API Documentation 4.1.0-Master (009143bf4b4)
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 scaleCalc.setEllipsoid( mapSettings.ellipsoid() );
1336 const double scaleDenominator = scaleCalc.calculate( mWmsParameters.bboxAsRectangle(), outputImage->width() );
1337
1338 // configure layers
1339 QgsWmsRenderContext context = mContext;
1340 context.setScaleDenominator( scaleDenominator );
1341
1342 QList<QgsMapLayer *> layers = context.layersToRender();
1343 configureLayers( layers, &mapSettings );
1344
1345 // add layers to map settings
1346 mapSettings.setLayers( layers );
1347
1348#ifdef HAVE_SERVER_PYTHON_PLUGINS
1349 mContext.accessControl()->resolveFilterFeatures( mapSettings.layers() );
1350#endif
1351
1352 QDomDocument result = featureInfoDocument( layers, mapSettings, outputImage.get(), version );
1353
1354 QByteArray ba;
1355
1356 if ( infoFormat == QgsWmsParameters::Format::TEXT )
1357 ba = convertFeatureInfoToText( result );
1358 else if ( infoFormat == QgsWmsParameters::Format::HTML )
1359 ba = convertFeatureInfoToHtml( result );
1360 else if ( infoFormat == QgsWmsParameters::Format::JSON )
1361 ba = convertFeatureInfoToJson( layers, result, mapSettings.destinationCrs() );
1362 else
1363 ba = result.toByteArray();
1364
1365 return ba;
1366 }
1367
1368 QImage *QgsRenderer::createImage( const QSize &size ) const
1369 {
1370 std::unique_ptr<QImage> image;
1371
1372 // use alpha channel only if necessary because it slows down performance
1373 QgsWmsParameters::Format format = mWmsParameters.format();
1374 bool transparent = mWmsParameters.transparentAsBool();
1375
1376 if ( transparent && format != QgsWmsParameters::JPG )
1377 {
1378 image = std::make_unique<QImage>( size, QImage::Format_ARGB32_Premultiplied );
1379 image->fill( 0 );
1380 }
1381 else
1382 {
1383 image = std::make_unique<QImage>( size, QImage::Format_RGB32 );
1384 image->fill( mWmsParameters.backgroundColorAsColor() );
1385 }
1386
1387 // Check that image was correctly created
1388 if ( image->isNull() )
1389 {
1390 throw QgsException( u"createImage: image could not be created, check for out of memory conditions"_s );
1391 }
1392
1393 const int dpm = static_cast<int>( mContext.dotsPerMm() * 1000.0 );
1394 image->setDotsPerMeterX( dpm );
1395 image->setDotsPerMeterY( dpm );
1396
1397 return image.release();
1398 }
1399
1400 void QgsRenderer::configureMapSettings( const QPaintDevice *paintDevice, QgsMapSettings &mapSettings, bool mandatoryCrsParam )
1401 {
1402 if ( !paintDevice )
1403 {
1404 throw QgsException( u"configureMapSettings: no paint device"_s );
1405 }
1406
1407 mapSettings.setOutputSize( QSize( paintDevice->width(), paintDevice->height() ) );
1408 // Recalculate from input DPI: do not take the (integer) value from paint device
1409 // because it loose precision!
1410 mapSettings.setOutputDpi( mContext.dotsPerMm() * 25.4 );
1411
1412 //map extent
1413 QgsRectangle mapExtent = mWmsParameters.bboxAsRectangle();
1414 if ( !mWmsParameters.bbox().isEmpty() && mapExtent.isEmpty() )
1415 {
1416 throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue, mWmsParameters[QgsWmsParameter::BBOX] );
1417 }
1418
1419 QString crs = mWmsParameters.crs();
1420 if ( crs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
1421 {
1422 crs = QString( "EPSG:4326" );
1423 mapExtent.invert();
1424 }
1425 else if ( crs.isEmpty() && !mandatoryCrsParam )
1426 {
1427 crs = QString( "EPSG:4326" );
1428 }
1429
1430 QgsCoordinateReferenceSystem outputCRS;
1431
1432 //wms spec says that CRS parameter is mandatory.
1434 if ( !outputCRS.isValid() )
1435 {
1437 QgsWmsParameter parameter;
1438
1439 if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) )
1440 {
1442 parameter = mWmsParameters[QgsWmsParameter::CRS];
1443 }
1444 else
1445 {
1447 parameter = mWmsParameters[QgsWmsParameter::SRS];
1448 }
1449
1450 throw QgsBadRequestException( code, parameter );
1451 }
1452
1453 //then set destinationCrs
1454 mapSettings.setDestinationCrs( outputCRS );
1455
1456 mapSettings.setTransformContext( mProject->transformContext() );
1457
1458 // Change x- and y- of BBOX for WMS 1.3.0 if axis inverted
1459 if ( mWmsParameters.versionAsNumber() >= QgsProjectVersion( 1, 3, 0 ) && outputCRS.hasAxisInverted() )
1460 {
1461 mapExtent.invert();
1462 }
1463
1464 mapSettings.setExtent( mapExtent );
1465
1466 // set the extent buffer
1467 mapSettings.setExtentBuffer( mContext.mapTileBuffer( paintDevice->width() ) );
1468
1469 /* Define the background color
1470 * Transparent or colored
1471 */
1472 QgsWmsParameters::Format format = mWmsParameters.format();
1473 bool transparent = mWmsParameters.transparentAsBool();
1474 QColor backgroundColor = mWmsParameters.backgroundColorAsColor();
1475
1476 //set background color
1477 if ( transparent && format != QgsWmsParameters::JPG )
1478 {
1479 mapSettings.setBackgroundColor( QColor( 0, 0, 0, 0 ) );
1480 }
1481 else if ( backgroundColor.isValid() )
1482 {
1483 mapSettings.setBackgroundColor( backgroundColor );
1484 }
1485
1486 // add context from project (global variables, ...)
1487 QgsExpressionContext context = mProject->createExpressionContext();
1488 context << QgsExpressionContextUtils::mapSettingsScope( mapSettings );
1489 mapSettings.setExpressionContext( context );
1490
1491 // add labeling engine settings
1492 mapSettings.setLabelingEngineSettings( mProject->labelingEngineSettings() );
1493
1494 mapSettings.setSelectiveMaskingSourceSets( mProject->selectiveMaskingSourceSetManager()->sets() );
1495
1496 mapSettings.setScaleMethod( mProject->scaleMethod() );
1497
1498 // enable rendering optimization
1500
1501 mapSettings.setFlag( Qgis::MapSettingsFlag::RenderMapTile, mContext.renderMapTiles() );
1502
1503 // enable profiling
1504 if ( mContext.settings().logProfile() )
1505 {
1507 }
1508
1509 // set selection color
1510 mapSettings.setSelectionColor( mProject->selectionColor() );
1511
1512 // Set WMS temporal properties
1513 // Note that this cannot parse multiple time instants while the vector dimensions implementation can
1514 const QString timeString { mWmsParameters.dimensionValues().value( u"TIME"_s, QString() ) };
1515 if ( !timeString.isEmpty() )
1516 {
1517 bool isValidTemporalRange { true };
1518 QgsDateTimeRange range;
1519 // First try with a simple date/datetime instant
1520 const QDateTime dt { QDateTime::fromString( timeString, Qt::DateFormat::ISODateWithMs ) };
1521 if ( dt.isValid() )
1522 {
1523 range = QgsDateTimeRange( dt, dt );
1524 }
1525 else // parse as an interval
1526 {
1527 try
1528 {
1530 }
1531 catch ( const QgsServerApiBadRequestException &ex )
1532 {
1533 isValidTemporalRange = false;
1534 QgsMessageLog::logMessage( u"Could not parse TIME parameter into a temporal range"_s, "Server", Qgis::MessageLevel::Warning );
1535 }
1536 }
1537
1538 if ( isValidTemporalRange )
1539 {
1540 mIsTemporal = true;
1541 mapSettings.setIsTemporal( true );
1542 mapSettings.setTemporalRange( range );
1543 }
1544 }
1545 }
1546
1547 QgsRenderContext QgsRenderer::configureDefaultRenderContext( QPainter *painter )
1548 {
1549 QgsRenderContext context = QgsRenderContext::fromQPainter( painter );
1550 context.setScaleFactor( mContext.dotsPerMm() );
1551 const double mmPerMapUnit = 1 / QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mProject );
1552 context.setMapToPixel( QgsMapToPixel( 1 / ( mmPerMapUnit * context.scaleFactor() ) ) );
1553 QgsDistanceArea distanceArea = QgsDistanceArea();
1554 distanceArea.setSourceCrs( QgsCoordinateReferenceSystem( mWmsParameters.crs() ), mProject->transformContext() );
1555 distanceArea.setEllipsoid( Qgis::geoNone() );
1556 context.setDistanceArea( distanceArea );
1557 return context;
1558 }
1559
1560 QDomDocument QgsRenderer::featureInfoDocument( QList<QgsMapLayer *> &layers, const QgsMapSettings &mapSettings, const QImage *outputImage, const QString &version ) const
1561 {
1562 const QStringList queryLayers = mContext.flattenedQueryLayers( mContext.parameters().queryLayersNickname() );
1563
1564 bool ijDefined = ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() );
1565
1566 bool xyDefined = ( !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty() );
1567
1568 bool filtersDefined = !mWmsParameters.filters().isEmpty();
1569
1570 bool filterGeomDefined = !mWmsParameters.filterGeom().isEmpty();
1571
1572 int featureCount = mWmsParameters.featureCountAsInt();
1573 if ( featureCount < 1 )
1574 {
1575 featureCount = 1;
1576 }
1577
1578 int i = mWmsParameters.iAsInt();
1579 int j = mWmsParameters.jAsInt();
1580 if ( xyDefined && !ijDefined )
1581 {
1582 i = mWmsParameters.xAsInt();
1583 j = mWmsParameters.yAsInt();
1584 }
1585 int width = mWmsParameters.widthAsInt();
1586 int height = mWmsParameters.heightAsInt();
1587 if ( ( i != -1 && j != -1 && width != 0 && height != 0 ) && ( width != outputImage->width() || height != outputImage->height() ) )
1588 {
1589 i *= ( outputImage->width() / static_cast<double>( width ) );
1590 j *= ( outputImage->height() / static_cast<double>( height ) );
1591 }
1592
1593 // init search variables
1594 std::unique_ptr<QgsRectangle> featuresRect;
1595 std::unique_ptr<QgsGeometry> filterGeom;
1596 std::unique_ptr<QgsPointXY> infoPoint;
1597
1598 if ( i != -1 && j != -1 )
1599 {
1600 infoPoint = std::make_unique<QgsPointXY>();
1601 infoPointToMapCoordinates( i, j, infoPoint.get(), mapSettings );
1602 }
1603 else if ( filtersDefined )
1604 {
1605 featuresRect = std::make_unique<QgsRectangle>();
1606 }
1607
1608 if ( filterGeomDefined )
1609 {
1610 filterGeom = std::make_unique<QgsGeometry>( QgsGeometry::fromWkt( mWmsParameters.filterGeom() ) );
1611 }
1612
1613 QDomDocument result;
1614 const QDomNode header = result.createProcessingInstruction( u"xml"_s, u"version=\"1.0\" encoding=\"UTF-8\""_s );
1615 result.appendChild( header );
1616
1617 QDomElement getFeatureInfoElement;
1618 QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat();
1619 if ( infoFormat == QgsWmsParameters::Format::GML )
1620 {
1621 getFeatureInfoElement = result.createElement( u"wfs:FeatureCollection"_s );
1622 getFeatureInfoElement.setAttribute( u"xmlns:wfs"_s, u"http://www.opengis.net/wfs"_s );
1623 getFeatureInfoElement.setAttribute( u"xmlns:ogc"_s, u"http://www.opengis.net/ogc"_s );
1624 getFeatureInfoElement.setAttribute( u"xmlns:gml"_s, u"http://www.opengis.net/gml"_s );
1625 getFeatureInfoElement.setAttribute( u"xmlns:ows"_s, u"http://www.opengis.net/ows"_s );
1626 getFeatureInfoElement.setAttribute( u"xmlns:xlink"_s, u"http://www.w3.org/1999/xlink"_s );
1627 getFeatureInfoElement.setAttribute( u"xmlns:qgs"_s, u"http://qgis.org/gml"_s );
1628 getFeatureInfoElement.setAttribute( u"xmlns:xsi"_s, u"http://www.w3.org/2001/XMLSchema-instance"_s );
1629 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 );
1630 }
1631 else
1632 {
1633 QString featureInfoElemName = QgsServerProjectUtils::wmsFeatureInfoDocumentElement( *mProject );
1634 if ( featureInfoElemName.isEmpty() )
1635 {
1636 featureInfoElemName = u"GetFeatureInfoResponse"_s;
1637 }
1638 QString featureInfoElemNs = QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( *mProject );
1639 if ( featureInfoElemNs.isEmpty() )
1640 {
1641 getFeatureInfoElement = result.createElement( featureInfoElemName );
1642 }
1643 else
1644 {
1645 getFeatureInfoElement = result.createElementNS( featureInfoElemNs, featureInfoElemName );
1646 }
1647 //feature info schema
1648 QString featureInfoSchema = QgsServerProjectUtils::wmsFeatureInfoSchema( *mProject );
1649 if ( !featureInfoSchema.isEmpty() )
1650 {
1651 getFeatureInfoElement.setAttribute( u"xmlns:xsi"_s, u"http://www.w3.org/2001/XMLSchema-instance"_s );
1652 getFeatureInfoElement.setAttribute( u"xsi:schemaLocation"_s, featureInfoSchema );
1653 }
1654 }
1655 result.appendChild( getFeatureInfoElement );
1656
1657 //Render context is needed to determine feature visibility for vector layers
1658 QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings );
1659
1660 bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *mProject );
1661
1662 //layers can have assigned a different name for GetCapabilities
1663 QHash<QString, QString> layerAliasMap = QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( *mProject );
1664
1665 for ( const QString &queryLayer : queryLayers )
1666 {
1667 bool validLayer = false;
1668 bool queryableLayer = true;
1669 for ( QgsMapLayer *layer : std::as_const( layers ) )
1670 {
1671 if ( queryLayer == mContext.layerNickname( *layer ) )
1672 {
1673 validLayer = true;
1674 queryableLayer = layer->flags().testFlag( QgsMapLayer::Identifiable );
1675 if ( !queryableLayer )
1676 {
1677 break;
1678 }
1679
1680 QDomElement layerElement;
1681 if ( infoFormat == QgsWmsParameters::Format::GML )
1682 {
1683 layerElement = getFeatureInfoElement;
1684 }
1685 else
1686 {
1687 layerElement = result.createElement( u"Layer"_s );
1688 QString layerName = queryLayer;
1689
1690 //check if the layer is given a different name for GetFeatureInfo output
1691 QHash<QString, QString>::const_iterator layerAliasIt = layerAliasMap.constFind( layerName );
1692 if ( layerAliasIt != layerAliasMap.constEnd() )
1693 {
1694 layerName = layerAliasIt.value();
1695 }
1696
1697 layerElement.setAttribute( u"name"_s, layerName );
1698 const QString layerTitle = layer->serverProperties()->title();
1699 if ( !layerTitle.isEmpty() )
1700 {
1701 layerElement.setAttribute( u"title"_s, layerTitle );
1702 }
1703 else
1704 {
1705 layerElement.setAttribute( u"title"_s, layerName );
1706 }
1707 getFeatureInfoElement.appendChild( layerElement );
1708 if ( sia2045 ) //the name might not be unique after alias replacement
1709 {
1710 layerElement.setAttribute( u"id"_s, layer->id() );
1711 }
1712 }
1713
1714 if ( layer->type() == Qgis::LayerType::Vector )
1715 {
1716 QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
1717 if ( vectorLayer )
1718 {
1719 ( void ) featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, featuresRect.get(), filterGeom.get() );
1720 break;
1721 }
1722 }
1723 else if ( layer->type() == Qgis::LayerType::Raster )
1724 {
1725 QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer );
1726 if ( !rasterLayer )
1727 {
1728 break;
1729 }
1730 if ( !infoPoint )
1731 {
1732 break;
1733 }
1734 QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( layer, *( infoPoint.get() ) );
1735 if ( !rasterLayer->extent().contains( layerInfoPoint ) )
1736 {
1737 break;
1738 }
1739 if ( infoFormat == QgsWmsParameters::Format::GML )
1740 {
1741 layerElement = result.createElement( u"gml:featureMember"_s /*wfs:FeatureMember*/ );
1742 getFeatureInfoElement.appendChild( layerElement );
1743 }
1744 ( void ) featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, renderContext, result, layerElement, version );
1745 }
1746 else if ( layer->type() == Qgis::LayerType::Mesh )
1747 {
1748 QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer *>( layer );
1749 QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( layer, *( infoPoint.get() ) );
1750
1751 const QgsTriangularMesh *mesh = meshLayer->triangularMesh();
1752 if ( !mesh )
1753 {
1754 meshLayer->updateTriangularMesh( renderContext.coordinateTransform() );
1755 }
1756 ( void ) featureInfoFromMeshLayer( meshLayer, mapSettings, &layerInfoPoint, renderContext, result, layerElement, version );
1757 }
1758 }
1759 }
1760 if ( !validLayer && !mContext.isValidLayer( queryLayer ) && !mContext.isValidGroup( queryLayer ) )
1761 {
1762 QgsWmsParameter param( QgsWmsParameter::LAYER );
1763 param.mValue = queryLayer;
1764 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotDefined, param );
1765 }
1766 else if ( ( validLayer && !queryableLayer ) || ( !validLayer && mContext.isValidGroup( queryLayer ) ) )
1767 {
1768 QgsWmsParameter param( QgsWmsParameter::LAYER );
1769 param.mValue = queryLayer;
1770 // Check if this layer belongs to a group and the group has any queryable layers
1771 bool hasGroupAndQueryable { false };
1772 if ( !mContext.parameters().queryLayersNickname().contains( queryLayer ) )
1773 {
1774 // Find which group this layer belongs to
1775 const QStringList constNicks { mContext.parameters().queryLayersNickname() };
1776 for ( const QString &ql : constNicks )
1777 {
1778 if ( mContext.layerGroups().contains( ql ) )
1779 {
1780 const QList<QgsMapLayer *> constLayers { mContext.layerGroups()[ql] };
1781 for ( const QgsMapLayer *ml : constLayers )
1782 {
1783 if ( ( !ml->serverProperties()->shortName().isEmpty() && ml->serverProperties()->shortName() == queryLayer ) || ( ml->name() == queryLayer ) )
1784 {
1785 param.mValue = ql;
1786 }
1787 if ( ml->flags().testFlag( QgsMapLayer::Identifiable ) )
1788 {
1789 hasGroupAndQueryable = true;
1790 break;
1791 }
1792 }
1793 break;
1794 }
1795 }
1796 }
1797 // Only throw if it's not a group or the group has no queryable children
1798 if ( !hasGroupAndQueryable )
1799 {
1800 throw QgsBadRequestException( QgsServiceException::OGC_LayerNotQueryable, param );
1801 }
1802 }
1803 }
1804
1805 if ( featuresRect && !featuresRect->isNull() )
1806 {
1807 if ( infoFormat == QgsWmsParameters::Format::GML )
1808 {
1809 QDomElement bBoxElem = result.createElement( u"gml:boundedBy"_s );
1810 QDomElement boxElem;
1811 int gmlVersion = mWmsParameters.infoFormatVersion();
1812 if ( gmlVersion < 3 )
1813 {
1814 boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect.get(), result, 8 );
1815 }
1816 else
1817 {
1818 boxElem = QgsOgcUtils::rectangleToGMLEnvelope( featuresRect.get(), result, 8 );
1819 }
1820
1821 QgsCoordinateReferenceSystem crs = mapSettings.destinationCrs();
1822 if ( crs.isValid() )
1823 {
1824 boxElem.setAttribute( u"srsName"_s, crs.authid() );
1825 }
1826 bBoxElem.appendChild( boxElem );
1827 getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
1828 }
1829 else
1830 {
1831 QDomElement bBoxElem = result.createElement( u"BoundingBox"_s );
1832 bBoxElem.setAttribute( u"CRS"_s, mapSettings.destinationCrs().authid() );
1833 bBoxElem.setAttribute( u"minx"_s, qgsDoubleToString( featuresRect->xMinimum(), 8 ) );
1834 bBoxElem.setAttribute( u"maxx"_s, qgsDoubleToString( featuresRect->xMaximum(), 8 ) );
1835 bBoxElem.setAttribute( u"miny"_s, qgsDoubleToString( featuresRect->yMinimum(), 8 ) );
1836 bBoxElem.setAttribute( u"maxy"_s, qgsDoubleToString( featuresRect->yMaximum(), 8 ) );
1837 getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
1838 }
1839 }
1840
1841 if ( sia2045 && infoFormat == QgsWmsParameters::Format::XML )
1842 {
1843 convertFeatureInfoToSia2045( result );
1844 }
1845
1846 return result;
1847 }
1848
1849 bool QgsRenderer::featureInfoFromVectorLayer(
1850 QgsVectorLayer *layer,
1851 const QgsPointXY *infoPoint,
1852 int nFeatures,
1853 QDomDocument &infoDocument,
1854 QDomElement &layerElement,
1855 const QgsMapSettings &mapSettings,
1856 QgsRenderContext &renderContext,
1857 const QString &version,
1858 QgsRectangle *featureBBox,
1859 QgsGeometry *filterGeom
1860 ) const
1861 {
1862 if ( !layer )
1863 {
1864 return false;
1865 }
1866
1867 QgsFeatureRequest fReq;
1868
1869 // Transform filter geometry to layer CRS
1870 std::unique_ptr<QgsGeometry> layerFilterGeom;
1871 if ( filterGeom )
1872 {
1873 layerFilterGeom = std::make_unique<QgsGeometry>( *filterGeom );
1874 layerFilterGeom->transform( QgsCoordinateTransform( mapSettings.destinationCrs(), layer->crs(), fReq.transformContext() ) );
1875 }
1876
1877 //we need a selection rect (0.01 of map width)
1878 QgsRectangle mapRect = mapSettings.extent();
1879 QgsRectangle layerRect = mapSettings.mapToLayerCoordinates( layer, mapRect );
1880
1881
1882 QgsRectangle searchRect;
1883
1884 //info point could be 0 in case there is only an attribute filter
1885 if ( infoPoint )
1886 {
1887 searchRect = featureInfoSearchRect( layer, mapSettings, renderContext, *infoPoint );
1888 }
1889 else if ( layerFilterGeom )
1890 {
1891 searchRect = layerFilterGeom->boundingBox();
1892 }
1893 else if ( !mWmsParameters.bbox().isEmpty() )
1894 {
1895 searchRect = layerRect;
1896 }
1897
1898 //do a select with searchRect and go through all the features
1899
1900 QgsFeature feature;
1901 QgsAttributes featureAttributes;
1902 int featureCounter = 0;
1903 layer->updateFields();
1904 const QgsFields fields = layer->fields();
1905 bool addWktGeometry = ( QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) && mWmsParameters.withGeometry() );
1906 bool segmentizeWktGeometry = QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( *mProject );
1907
1908 bool hasGeometry = QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) || addWktGeometry || featureBBox || layerFilterGeom;
1910
1911 if ( !searchRect.isEmpty() )
1912 {
1913 fReq.setFilterRect( searchRect );
1914 }
1915 else
1916 {
1917 fReq.setFlags( fReq.flags() & ~static_cast<int>( Qgis::FeatureRequestFlag::ExactIntersect ) );
1918 }
1919
1920
1921 if ( layerFilterGeom )
1922 {
1923 fReq.setFilterExpression( QString( "intersects( $geometry, geom_from_wkt('%1') )" ).arg( layerFilterGeom->asWkt() ) );
1924 }
1925
1927 mFeatureFilter.filterFeatures( layer, fReq );
1929
1930#ifdef HAVE_SERVER_PYTHON_PLUGINS
1932 mContext.accessControl()->filterFeatures( layer, fReq );
1934
1935 QStringList attributes;
1936 for ( const QgsField &field : fields )
1937 {
1938 attributes.append( field.name() );
1939 }
1940 attributes = mContext.accessControl()->layerAttributes( layer, attributes );
1941 fReq.setSubsetOfAttributes( attributes, layer->fields() );
1942#endif
1943
1944 QgsFeatureIterator fit = layer->getFeatures( fReq );
1945 std::unique_ptr<QgsFeatureRenderer> r2( layer->renderer() ? layer->renderer()->clone() : nullptr );
1946 if ( r2 )
1947 {
1948 r2->startRender( renderContext, layer->fields() );
1949 }
1950
1951 bool featureBBoxInitialized = false;
1952 while ( fit.nextFeature( feature ) )
1953 {
1954 if ( layer->wkbType() == Qgis::WkbType::NoGeometry && !searchRect.isEmpty() )
1955 {
1956 break;
1957 }
1958
1959 ++featureCounter;
1960 if ( featureCounter > nFeatures )
1961 {
1962 break;
1963 }
1964
1965 renderContext.expressionContext().setFeature( feature );
1966
1967 if ( layer->wkbType() != Qgis::WkbType::NoGeometry && !searchRect.isEmpty() )
1968 {
1969 if ( !r2 )
1970 {
1971 continue;
1972 }
1973
1974 //check if feature is rendered at all
1975 bool render = r2->willRenderFeature( feature, renderContext );
1976 if ( !render )
1977 {
1978 continue;
1979 }
1980 }
1981
1982 QgsRectangle box;
1983 if ( layer->wkbType() != Qgis::WkbType::NoGeometry && hasGeometry )
1984 {
1985 box = mapSettings.layerExtentToOutputExtent( layer, feature.geometry().boundingBox() );
1986 if ( featureBBox ) //extend feature info bounding box if requested
1987 {
1988 if ( !featureBBoxInitialized && featureBBox->isEmpty() )
1989 {
1990 *featureBBox = box;
1991 featureBBoxInitialized = true;
1992 }
1993 else
1994 {
1995 featureBBox->combineExtentWith( box );
1996 }
1997 }
1998 }
1999
2000 QgsCoordinateReferenceSystem outputCrs = layer->crs();
2001 if ( layer->crs() != mapSettings.destinationCrs() )
2002 {
2003 outputCrs = mapSettings.destinationCrs();
2004 }
2005
2006 if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML )
2007 {
2008 bool withGeom = layer->wkbType() != Qgis::WkbType::NoGeometry && addWktGeometry;
2009 int gmlVersion = mWmsParameters.infoFormatVersion();
2010 QString typeName = mContext.layerNickname( *layer );
2011 QDomElement elem = createFeatureGML(
2012 &feature,
2013 layer,
2014 infoDocument,
2015 outputCrs,
2016 mapSettings,
2017 typeName,
2018 withGeom,
2019 gmlVersion
2020#ifdef HAVE_SERVER_PYTHON_PLUGINS
2021 ,
2022 &attributes
2023#endif
2024 );
2025 QDomElement featureMemberElem = infoDocument.createElement( u"gml:featureMember"_s /*wfs:FeatureMember*/ );
2026 featureMemberElem.appendChild( elem );
2027 layerElement.appendChild( featureMemberElem );
2028 continue;
2029 }
2030 else
2031 {
2032 QDomElement featureElement = infoDocument.createElement( u"Feature"_s );
2033 featureElement.setAttribute( u"id"_s, QgsServerFeatureId::getServerFid( feature, layer->dataProvider()->pkAttributeIndexes() ) );
2034 layerElement.appendChild( featureElement );
2035
2036 featureAttributes = feature.attributes();
2037 QgsEditFormConfig editConfig = layer->editFormConfig();
2039 {
2040 writeAttributesTabLayout(
2041 editConfig,
2042 layer,
2043 fields,
2044 featureAttributes,
2045 infoDocument,
2046 featureElement,
2047 renderContext
2048#ifdef HAVE_SERVER_PYTHON_PLUGINS
2049 ,
2050 &attributes
2051#endif
2052 );
2053 }
2054 else
2055 {
2056 for ( int i = 0; i < featureAttributes.count(); ++i )
2057 {
2058 writeVectorLayerAttribute(
2059 i,
2060 layer,
2061 fields,
2062 featureAttributes,
2063 infoDocument,
2064 featureElement,
2065 renderContext
2066#ifdef HAVE_SERVER_PYTHON_PLUGINS
2067 ,
2068 &attributes
2069#endif
2070 );
2071 }
2072 }
2073
2074 //add maptip attribute based on html/expression (in case there is no maptip attribute)
2075 QString mapTip = layer->mapTipTemplate();
2076 if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() || QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( *mProject ) ) )
2077 {
2078 QDomElement maptipElem = infoDocument.createElement( u"Attribute"_s );
2079 maptipElem.setAttribute( u"name"_s, u"maptip"_s );
2080 QgsExpressionContext context { renderContext.expressionContext() };
2082 maptipElem.setAttribute( u"value"_s, QgsExpression::replaceExpressionText( mapTip, &context ) );
2083 featureElement.appendChild( maptipElem );
2084 }
2085
2086 QgsExpression displayExpression = layer->displayExpression();
2087 if ( displayExpression.isValid() && mWmsParameters.withDisplayName() )
2088 {
2089 QDomElement displayElem = infoDocument.createElement( u"Attribute"_s );
2090 displayElem.setAttribute( u"name"_s, u"displayName"_s );
2091 QgsExpressionContext context { renderContext.expressionContext() };
2093 displayExpression.prepare( &context );
2094 displayElem.setAttribute( u"value"_s, displayExpression.evaluate( &context ).toString() );
2095 featureElement.appendChild( displayElem );
2096 }
2097
2098 //append feature bounding box to feature info xml
2099 if ( QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) && layer->wkbType() != Qgis::WkbType::NoGeometry && hasGeometry )
2100 {
2101 QDomElement bBoxElem = infoDocument.createElement( u"BoundingBox"_s );
2102 bBoxElem.setAttribute( version == "1.1.1"_L1 ? "SRS" : "CRS", outputCrs.authid() );
2103 bBoxElem.setAttribute( u"minx"_s, qgsDoubleToString( box.xMinimum(), mContext.precision() ) );
2104 bBoxElem.setAttribute( u"maxx"_s, qgsDoubleToString( box.xMaximum(), mContext.precision() ) );
2105 bBoxElem.setAttribute( u"miny"_s, qgsDoubleToString( box.yMinimum(), mContext.precision() ) );
2106 bBoxElem.setAttribute( u"maxy"_s, qgsDoubleToString( box.yMaximum(), mContext.precision() ) );
2107 featureElement.appendChild( bBoxElem );
2108 }
2109
2110 //also append the wkt geometry as an attribute
2111 if ( layer->wkbType() != Qgis::WkbType::NoGeometry && addWktGeometry && hasGeometry )
2112 {
2113 QgsGeometry geom = feature.geometry();
2114 if ( !geom.isNull() )
2115 {
2116 if ( layer->crs() != outputCrs )
2117 {
2118 QgsCoordinateTransform transform = mapSettings.layerTransform( layer );
2119 if ( transform.isValid() )
2120 geom.transform( transform );
2121 }
2122
2123 if ( segmentizeWktGeometry )
2124 {
2125 const QgsAbstractGeometry *abstractGeom = geom.constGet();
2126 if ( abstractGeom )
2127 {
2128 if ( QgsWkbTypes::isCurvedType( abstractGeom->wkbType() ) )
2129 {
2130 QgsAbstractGeometry *segmentizedGeom = abstractGeom->segmentize();
2131 geom.set( segmentizedGeom );
2132 }
2133 }
2134 }
2135 QDomElement geometryElement = infoDocument.createElement( u"Attribute"_s );
2136 geometryElement.setAttribute( u"name"_s, u"geometry"_s );
2137 geometryElement.setAttribute( u"value"_s, geom.asWkt( mContext.precision() ) );
2138 geometryElement.setAttribute( u"type"_s, u"derived"_s );
2139 featureElement.appendChild( geometryElement );
2140 }
2141 }
2142 }
2143 }
2144 if ( r2 )
2145 {
2146 r2->stopRender( renderContext );
2147 }
2148
2149 return true;
2150 }
2151
2152 void QgsRenderer::writeAttributesTabGroup(
2153 const QgsAttributeEditorElement *group,
2154 QgsVectorLayer *layer,
2155 const QgsFields &fields,
2156 QgsAttributes &featureAttributes,
2157 QDomDocument &doc,
2158 QDomElement &parentElem,
2159 QgsRenderContext &renderContext,
2160 QStringList *attributes
2161 ) const
2162 {
2163 const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( group );
2164 if ( container )
2165 {
2166 QString groupName = container->name();
2167 QDomElement nameElem;
2168
2169 if ( !groupName.isEmpty() )
2170 {
2171 nameElem = doc.createElement( groupName );
2172 parentElem.appendChild( nameElem );
2173 }
2174
2175 const QList<QgsAttributeEditorElement *> children = container->children();
2176 for ( const QgsAttributeEditorElement *child : children )
2177 {
2178 if ( child->type() == Qgis::AttributeEditorType::Container )
2179 {
2180 writeAttributesTabGroup( child, layer, fields, featureAttributes, doc, nameElem.isNull() ? parentElem : nameElem, renderContext );
2181 }
2182 else if ( child->type() == Qgis::AttributeEditorType::Field )
2183 {
2184 const QgsAttributeEditorField *editorField = dynamic_cast<const QgsAttributeEditorField *>( child );
2185 if ( editorField )
2186 {
2187 const int idx { fields.indexFromName( editorField->name() ) };
2188 if ( idx >= 0 )
2189 {
2190 writeVectorLayerAttribute( idx, layer, fields, featureAttributes, doc, nameElem.isNull() ? parentElem : nameElem, renderContext, attributes );
2191 }
2192 }
2193 }
2194 }
2195 }
2196 }
2197
2198 void QgsRenderer::writeAttributesTabLayout(
2199 QgsEditFormConfig &config, QgsVectorLayer *layer, const QgsFields &fields, QgsAttributes &featureAttributes, QDomDocument &doc, QDomElement &featureElem, QgsRenderContext &renderContext, QStringList *attributes
2200 ) const
2201 {
2202 QgsAttributeEditorContainer *editorContainer = config.invisibleRootContainer();
2203 if ( !editorContainer )
2204 {
2205 return;
2206 }
2207
2208 writeAttributesTabGroup( editorContainer, layer, fields, featureAttributes, doc, featureElem, renderContext, attributes );
2209 }
2210
2211 void QgsRenderer::writeVectorLayerAttribute(
2212 int attributeIndex, QgsVectorLayer *layer, const QgsFields &fields, QgsAttributes &featureAttributes, QDomDocument &doc, QDomElement &featureElem, QgsRenderContext &renderContext, QStringList *attributes
2213 ) const
2214 {
2215#ifndef HAVE_SERVER_PYTHON_PLUGINS
2216 Q_UNUSED( attributes );
2217#endif
2218
2219 if ( !layer )
2220 {
2221 return;
2222 }
2223
2224 //skip attribute if it is explicitly excluded from WMS publication
2225 if ( fields.at( attributeIndex ).configurationFlags().testFlag( Qgis::FieldConfigurationFlag::HideFromWms ) )
2226 {
2227 return;
2228 }
2229#ifdef HAVE_SERVER_PYTHON_PLUGINS
2230 //skip attribute if it is excluded by access control
2231 if ( attributes && !attributes->contains( fields.at( attributeIndex ).name() ) )
2232 {
2233 return;
2234 }
2235#endif
2236
2237 QString attributeName = layer->attributeDisplayName( attributeIndex );
2238 QDomElement attributeElement = doc.createElement( u"Attribute"_s );
2239 attributeElement.setAttribute( u"name"_s, attributeName );
2240 const QgsEditorWidgetSetup setup = layer->editorWidgetSetup( attributeIndex );
2241 attributeElement.setAttribute( u"value"_s, QgsExpression::replaceExpressionText( replaceValueMapAndRelation( layer, attributeIndex, featureAttributes[attributeIndex] ), &renderContext.expressionContext() ) );
2242 featureElem.appendChild( attributeElement );
2243 }
2244
2245 bool QgsRenderer::featureInfoFromMeshLayer(
2246 QgsMeshLayer *layer, const QgsMapSettings &mapSettings, const QgsPointXY *infoPoint, const QgsRenderContext &renderContext, QDomDocument &infoDocument, QDomElement &layerElement, const QString &version
2247 ) const
2248 {
2249 Q_UNUSED( version )
2250 Q_UNUSED( mapSettings )
2251
2252 if ( !infoPoint || !layer || !layer->dataProvider() )
2253 {
2254 return false;
2255 }
2256
2257 const bool isTemporal = layer->temporalProperties()->isActive();
2258 QgsDateTimeRange range, layerRange;
2259 const QString dateFormat = u"yyyy-MM-ddTHH:mm:ss"_s;
2260
2261 QList<QgsMeshDatasetIndex> datasetIndexList;
2262 const int activeScalarGroup = layer->rendererSettings().activeScalarDatasetGroup();
2263 const int activeVectorGroup = layer->rendererSettings().activeVectorDatasetGroup();
2264
2265 const QList<int> allGroup = layer->enabledDatasetGroupsIndexes();
2266
2267 if ( isTemporal )
2268 {
2269 range = renderContext.temporalRange();
2270 layerRange = static_cast<QgsMeshLayerTemporalProperties *>( layer->temporalProperties() )->timeExtent();
2271
2272 if ( activeScalarGroup >= 0 )
2273 {
2274 QgsMeshDatasetIndex indice;
2275 indice = layer->activeScalarDatasetAtTime( range );
2276 datasetIndexList.append( indice );
2277 }
2278
2279 if ( activeVectorGroup >= 0 && activeVectorGroup != activeScalarGroup )
2280 datasetIndexList.append( layer->activeVectorDatasetAtTime( range ) );
2281
2282 for ( int groupIndex : allGroup )
2283 {
2284 if ( groupIndex != activeScalarGroup && groupIndex != activeVectorGroup )
2285 datasetIndexList.append( layer->datasetIndexAtTime( range, groupIndex ) );
2286 }
2287 }
2288 else
2289 {
2290 if ( activeScalarGroup >= 0 )
2291 datasetIndexList.append( layer->staticScalarDatasetIndex() );
2292 if ( activeVectorGroup >= 0 && activeVectorGroup != activeScalarGroup )
2293 datasetIndexList.append( layer->staticVectorDatasetIndex() );
2294
2295 for ( int groupIndex : allGroup )
2296 {
2297 if ( groupIndex != activeScalarGroup && groupIndex != activeVectorGroup )
2298 {
2299 if ( !layer->datasetGroupMetadata( groupIndex ).isTemporal() )
2300 datasetIndexList.append( groupIndex );
2301 }
2302 }
2303 }
2304
2305 const double searchRadius = Qgis::DEFAULT_SEARCH_RADIUS_MM * renderContext.scaleFactor() * renderContext.mapToPixel().mapUnitsPerPixel();
2306
2307 double scalarDoubleValue = 0.0;
2308
2309 for ( const QgsMeshDatasetIndex &index : datasetIndexList )
2310 {
2311 if ( !index.isValid() )
2312 continue;
2313
2314 const QgsMeshDatasetGroupMetadata &groupMeta = layer->datasetGroupMetadata( index );
2315 QMap<QString, QString> derivedAttributes;
2316
2317 QMap<QString, QString> attribute;
2318
2319 if ( groupMeta.isScalar() )
2320 {
2321 const QgsMeshDatasetValue scalarValue = layer->datasetValue( index, *infoPoint, searchRadius );
2322 scalarDoubleValue = scalarValue.scalar();
2323 attribute.insert( u"Scalar Value"_s, std::isnan( scalarDoubleValue ) ? u"no data"_s : QLocale().toString( scalarDoubleValue ) );
2324 }
2325
2326 if ( groupMeta.isVector() )
2327 {
2328 const QgsMeshDatasetValue vectorValue = layer->datasetValue( index, *infoPoint, searchRadius );
2329 const double vectorX = vectorValue.x();
2330 const double vectorY = vectorValue.y();
2331 if ( std::isnan( vectorX ) || std::isnan( vectorY ) )
2332 {
2333 attribute.insert( u"Vector Value"_s, u"no data"_s );
2334 }
2335 else
2336 {
2337 attribute.insert( u"Vector Magnitude"_s, QLocale().toString( vectorValue.scalar() ) );
2338 derivedAttributes.insert( u"Vector x-component"_s, QLocale().toString( vectorY ) );
2339 derivedAttributes.insert( u"Vector y-component"_s, QLocale().toString( vectorX ) );
2340 }
2341 }
2342
2343 const QgsMeshDatasetMetadata &meta = layer->datasetMetadata( index );
2344
2345 if ( groupMeta.isTemporal() )
2346 derivedAttributes.insert( u"Time Step"_s, layer->formatTime( meta.time() ) );
2347 derivedAttributes.insert( u"Source"_s, groupMeta.uri() );
2348
2349 const QString resultName = groupMeta.name();
2350
2351 QDomElement attributeElement = infoDocument.createElement( u"Attribute"_s );
2352 attributeElement.setAttribute( u"name"_s, resultName );
2353
2354 QString value;
2355 if ( !QgsVariantUtils::isNull( scalarDoubleValue ) )
2356 {
2357 value = QString::number( scalarDoubleValue );
2358 }
2359
2360 attributeElement.setAttribute( u"value"_s, value );
2361 layerElement.appendChild( attributeElement );
2362
2363 if ( isTemporal )
2364 {
2365 QDomElement attributeElementTime = infoDocument.createElement( u"Attribute"_s );
2366 attributeElementTime.setAttribute( u"name"_s, u"Time"_s );
2367 if ( range.isInstant() )
2368 {
2369 value = range.begin().toString( dateFormat );
2370 }
2371 else
2372 {
2373 value = range.begin().toString( dateFormat ) + '/' + range.end().toString( dateFormat );
2374 }
2375 attributeElementTime.setAttribute( u"value"_s, value );
2376 layerElement.appendChild( attributeElementTime );
2377 }
2378 }
2379 return true;
2380 }
2381
2382 bool QgsRenderer::featureInfoFromRasterLayer(
2383 QgsRasterLayer *layer, const QgsMapSettings &mapSettings, const QgsPointXY *infoPoint, const QgsRenderContext &renderContext, QDomDocument &infoDocument, QDomElement &layerElement, const QString &version
2384 ) const
2385 {
2386 Q_UNUSED( version )
2387
2388 if ( !infoPoint || !layer || !layer->dataProvider() )
2389 {
2390 return false;
2391 }
2392
2393 QgsMessageLog::logMessage( u"infoPoint: %1 %2"_s.arg( infoPoint->x() ).arg( infoPoint->y() ), u"Server"_s, Qgis::MessageLevel::Info );
2394
2396 {
2397 return false;
2398 }
2399
2400 const Qgis::RasterIdentifyFormat identifyFormat(
2402 );
2403
2404 QgsRasterIdentifyResult identifyResult;
2405 if ( layer->crs() != mapSettings.destinationCrs() )
2406 {
2407 const QgsRectangle extent { mapSettings.extent() };
2408 const QgsCoordinateTransform transform { mapSettings.destinationCrs(), layer->crs(), mapSettings.transformContext() };
2409 if ( !transform.isValid() )
2410 {
2411 throw QgsBadRequestException(
2412 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() )
2413 );
2414 }
2415 identifyResult = layer->dataProvider()->identify( *infoPoint, identifyFormat, transform.transform( extent ), mapSettings.outputSize().width(), mapSettings.outputSize().height() );
2416 }
2417 else
2418 {
2419 identifyResult = layer->dataProvider()->identify( *infoPoint, identifyFormat, mapSettings.extent(), mapSettings.outputSize().width(), mapSettings.outputSize().height() );
2420 }
2421
2422 if ( !identifyResult.isValid() )
2423 return false;
2424
2425 QMap<int, QVariant> attributes = identifyResult.results();
2426
2427 if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML )
2428 {
2429 QgsFeature feature;
2430 QgsFields fields;
2431 QgsCoordinateReferenceSystem layerCrs = layer->crs();
2432 int gmlVersion = mWmsParameters.infoFormatVersion();
2433 QString typeName = mContext.layerNickname( *layer );
2434
2435 if ( identifyFormat == Qgis::RasterIdentifyFormat::Value )
2436 {
2437 feature.initAttributes( attributes.count() );
2438 int index = 0;
2439 for ( auto it = attributes.constBegin(); it != attributes.constEnd(); ++it )
2440 {
2441 fields.append( QgsField( layer->bandName( it.key() ), QMetaType::Type::Double ) );
2442 feature.setAttribute( index++, QString::number( it.value().toDouble() ) );
2443 }
2444 feature.setFields( fields );
2445 QDomElement elem = createFeatureGML( &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr );
2446 layerElement.appendChild( elem );
2447 }
2448 else
2449 {
2450 const auto values = identifyResult.results();
2451 for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
2452 {
2453 QVariant value = it.value();
2454 if ( value.userType() == QMetaType::Type::Bool && !value.toBool() )
2455 {
2456 // sublayer not visible or not queryable
2457 continue;
2458 }
2459
2460 if ( value.userType() == QMetaType::Type::QString )
2461 {
2462 continue;
2463 }
2464
2465 // list of feature stores for a single sublayer
2466 const QgsFeatureStoreList featureStoreList = it.value().value<QgsFeatureStoreList>();
2467
2468 for ( const QgsFeatureStore &featureStore : featureStoreList )
2469 {
2470 const QgsFeatureList storeFeatures = featureStore.features();
2471 for ( const QgsFeature &feature : storeFeatures )
2472 {
2473 QDomElement elem = createFeatureGML( &feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr );
2474 layerElement.appendChild( elem );
2475 }
2476 }
2477 }
2478 }
2479 }
2480 else
2481 {
2482 if ( identifyFormat == Qgis::RasterIdentifyFormat::Value )
2483 {
2484 for ( auto it = attributes.constBegin(); it != attributes.constEnd(); ++it )
2485 {
2486 QDomElement attributeElement = infoDocument.createElement( u"Attribute"_s );
2487 attributeElement.setAttribute( u"name"_s, layer->bandName( it.key() ) );
2488
2489 QString value;
2490 if ( !QgsVariantUtils::isNull( it.value() ) )
2491 {
2492 value = QString::number( it.value().toDouble() );
2493 }
2494
2495 attributeElement.setAttribute( u"value"_s, value );
2496 layerElement.appendChild( attributeElement );
2497 }
2498 }
2499 else // feature
2500 {
2501 const auto values = identifyResult.results();
2502 for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
2503 {
2504 QVariant value = it.value();
2505 if ( value.userType() == QMetaType::Type::Bool && !value.toBool() )
2506 {
2507 // sublayer not visible or not queryable
2508 continue;
2509 }
2510
2511 if ( value.userType() == QMetaType::Type::QString )
2512 {
2513 continue;
2514 }
2515
2516 // list of feature stores for a single sublayer
2517 const QgsFeatureStoreList featureStoreList = it.value().value<QgsFeatureStoreList>();
2518 for ( const QgsFeatureStore &featureStore : featureStoreList )
2519 {
2520 const QgsFeatureList storeFeatures = featureStore.features();
2521 for ( const QgsFeature &feature : storeFeatures )
2522 {
2523 for ( const auto &fld : feature.fields() )
2524 {
2525 const auto val { feature.attribute( fld.name() ) };
2526 if ( val.isValid() )
2527 {
2528 QDomElement attributeElement = infoDocument.createElement( u"Attribute"_s );
2529 attributeElement.setAttribute( u"name"_s, fld.name() );
2530 attributeElement.setAttribute( u"value"_s, val.toString() );
2531 layerElement.appendChild( attributeElement );
2532 }
2533 }
2534 }
2535 }
2536 }
2537 }
2538 //add maptip attribute based on html/expression
2539 QString mapTip = layer->mapTipTemplate();
2540 if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() || QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( *mProject ) ) )
2541 {
2542 QDomElement maptipElem = infoDocument.createElement( u"Attribute"_s );
2543 maptipElem.setAttribute( u"name"_s, u"maptip"_s );
2544 QgsExpressionContext context { renderContext.expressionContext() };
2545 QgsExpressionContextScope *scope = QgsExpressionContextUtils::layerScope( layer );
2546 scope->addVariable( QgsExpressionContextScope::StaticVariable( u"layer_cursor_point"_s, QVariant::fromValue( QgsGeometry::fromPointXY( QgsPointXY( infoPoint->x(), infoPoint->y() ) ) ) ) );
2547 context.appendScope( scope );
2548 maptipElem.setAttribute( u"value"_s, QgsExpression::replaceExpressionText( mapTip, &context ) );
2549 layerElement.appendChild( maptipElem );
2550 }
2551 }
2552 return true;
2553 }
2554
2555 bool QgsRenderer::testFilterStringSafety( const QString &filter ) const
2556 {
2557 //; too dangerous for sql injections
2558 if ( filter.contains( ";"_L1 ) )
2559 {
2560 return false;
2561 }
2562
2563 QStringList tokens = filter.split( ' ', Qt::SkipEmptyParts );
2564 groupStringList( tokens, u"'"_s );
2565 groupStringList( tokens, u"\""_s );
2566
2567 for ( auto tokenIt = tokens.constBegin(); tokenIt != tokens.constEnd(); ++tokenIt )
2568 {
2569 //allowlist of allowed characters and keywords
2570 if ( 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( '%'_L1 ) == 0
2580 || tokenIt->compare( "IS"_L1, Qt::CaseInsensitive ) == 0
2581 || tokenIt->compare( "NOT"_L1, Qt::CaseInsensitive ) == 0
2582 || tokenIt->compare( "NULL"_L1, Qt::CaseInsensitive ) == 0
2583 || tokenIt->compare( "AND"_L1, Qt::CaseInsensitive ) == 0
2584 || tokenIt->compare( "OR"_L1, Qt::CaseInsensitive ) == 0
2585 || tokenIt->compare( "IN"_L1, Qt::CaseInsensitive ) == 0
2586 || tokenIt->compare( "LIKE"_L1, Qt::CaseInsensitive ) == 0
2587 || tokenIt->compare( "ILIKE"_L1, Qt::CaseInsensitive ) == 0
2588 || tokenIt->compare( "DMETAPHONE"_L1, Qt::CaseInsensitive ) == 0
2589 || tokenIt->compare( "SOUNDEX"_L1, Qt::CaseInsensitive ) == 0
2590 || mContext.settings().allowedExtraSqlTokens().contains( *tokenIt, Qt::CaseSensitivity::CaseInsensitive ) )
2591 {
2592 continue;
2593 }
2594
2595 //numbers are OK
2596 bool isNumeric;
2597 ( void ) tokenIt->toDouble( &isNumeric );
2598 if ( isNumeric )
2599 {
2600 continue;
2601 }
2602
2603 //numeric strings need to be quoted once either with single or with double quotes
2604
2605 //empty strings are OK
2606 if ( *tokenIt == "''"_L1 )
2607 {
2608 continue;
2609 }
2610
2611 //single quote
2612 if ( tokenIt->size() > 2
2613 && ( *tokenIt )[0] == QChar( '\'' )
2614 && ( *tokenIt )[tokenIt->size() - 1] == QChar( '\'' )
2615 && ( *tokenIt )[1] != QChar( '\'' )
2616 && ( *tokenIt )[tokenIt->size() - 2] != QChar( '\'' ) )
2617 {
2618 continue;
2619 }
2620
2621 //double quote
2622 if ( tokenIt->size() > 2 && ( *tokenIt )[0] == QChar( '"' ) && ( *tokenIt )[tokenIt->size() - 1] == QChar( '"' ) && ( *tokenIt )[1] != QChar( '"' ) && ( *tokenIt )[tokenIt->size() - 2] != QChar( '"' ) )
2623 {
2624 continue;
2625 }
2626
2627 return false;
2628 }
2629
2630 return true;
2631 }
2632
2633 void QgsRenderer::groupStringList( QStringList &list, const QString &groupString )
2634 {
2635 //group contents within single quotes together
2636 bool groupActive = false;
2637 int startGroup = -1;
2638 QString concatString;
2639
2640 for ( int i = 0; i < list.size(); ++i )
2641 {
2642 QString &str = list[i];
2643 if ( str.startsWith( groupString ) )
2644 {
2645 startGroup = i;
2646 groupActive = true;
2647 concatString.clear();
2648 }
2649
2650 if ( groupActive )
2651 {
2652 if ( i != startGroup )
2653 {
2654 concatString.append( " " );
2655 }
2656 concatString.append( str );
2657 }
2658
2659 if ( str.endsWith( groupString ) )
2660 {
2661 int endGroup = i;
2662 groupActive = false;
2663
2664 if ( startGroup != -1 )
2665 {
2666 list[startGroup] = concatString;
2667 for ( int j = startGroup + 1; j <= endGroup; ++j )
2668 {
2669 list.removeAt( startGroup + 1 );
2670 --i;
2671 }
2672 }
2673
2674 concatString.clear();
2675 startGroup = -1;
2676 }
2677 }
2678 }
2679
2680 void QgsRenderer::convertFeatureInfoToSia2045( QDomDocument &doc ) const
2681 {
2682 QDomDocument SIAInfoDoc;
2683 QDomElement infoDocElement = doc.documentElement();
2684 QDomElement SIAInfoDocElement = SIAInfoDoc.importNode( infoDocElement, false ).toElement();
2685 SIAInfoDoc.appendChild( SIAInfoDocElement );
2686
2687 QString currentAttributeName;
2688 QString currentAttributeValue;
2689 QDomElement currentAttributeElem;
2690 QString currentLayerName;
2691 QDomElement currentLayerElem;
2692 QDomNodeList layerNodeList = infoDocElement.elementsByTagName( u"Layer"_s );
2693 for ( int i = 0; i < layerNodeList.size(); ++i )
2694 {
2695 currentLayerElem = layerNodeList.at( i ).toElement();
2696 currentLayerName = currentLayerElem.attribute( u"name"_s );
2697
2698 QDomElement currentFeatureElem;
2699
2700 QDomNodeList featureList = currentLayerElem.elementsByTagName( u"Feature"_s );
2701 if ( featureList.isEmpty() )
2702 {
2703 //raster?
2704 QDomNodeList attributeList = currentLayerElem.elementsByTagName( u"Attribute"_s );
2705 QDomElement rasterLayerElem;
2706 if ( !attributeList.isEmpty() )
2707 {
2708 rasterLayerElem = SIAInfoDoc.createElement( currentLayerName );
2709 }
2710 for ( int j = 0; j < attributeList.size(); ++j )
2711 {
2712 currentAttributeElem = attributeList.at( j ).toElement();
2713 currentAttributeName = currentAttributeElem.attribute( u"name"_s );
2714 currentAttributeValue = currentAttributeElem.attribute( u"value"_s );
2715 QDomElement outAttributeElem = SIAInfoDoc.createElement( currentAttributeName );
2716 QDomText outAttributeText = SIAInfoDoc.createTextNode( currentAttributeValue );
2717 outAttributeElem.appendChild( outAttributeText );
2718 rasterLayerElem.appendChild( outAttributeElem );
2719 }
2720 if ( !attributeList.isEmpty() )
2721 {
2722 SIAInfoDocElement.appendChild( rasterLayerElem );
2723 }
2724 }
2725 else //vector
2726 {
2727 //property attributes
2728 QSet<QString> layerPropertyAttributes;
2729 QString currentLayerId = currentLayerElem.attribute( u"id"_s );
2730 if ( !currentLayerId.isEmpty() )
2731 {
2732 QgsMapLayer *currentLayer = mProject->mapLayer( currentLayerId );
2733 if ( currentLayer )
2734 {
2735 QString WMSPropertyAttributesString = currentLayer->customProperty( u"WMSPropertyAttributes"_s ).toString();
2736 if ( !WMSPropertyAttributesString.isEmpty() )
2737 {
2738 QStringList propertyList = WMSPropertyAttributesString.split( u"//"_s );
2739 for ( auto propertyIt = propertyList.constBegin(); propertyIt != propertyList.constEnd(); ++propertyIt )
2740 {
2741 layerPropertyAttributes.insert( *propertyIt );
2742 }
2743 }
2744 }
2745 }
2746
2747 QDomElement propertyRefChild; //child to insert the next property after (or
2748 for ( int j = 0; j < featureList.size(); ++j )
2749 {
2750 QDomElement SIAFeatureElem = SIAInfoDoc.createElement( currentLayerName );
2751 currentFeatureElem = featureList.at( j ).toElement();
2752 QDomNodeList attributeList = currentFeatureElem.elementsByTagName( u"Attribute"_s );
2753
2754 for ( int k = 0; k < attributeList.size(); ++k )
2755 {
2756 currentAttributeElem = attributeList.at( k ).toElement();
2757 currentAttributeName = currentAttributeElem.attribute( u"name"_s );
2758 currentAttributeValue = currentAttributeElem.attribute( u"value"_s );
2759 if ( layerPropertyAttributes.contains( currentAttributeName ) )
2760 {
2761 QDomElement propertyElem = SIAInfoDoc.createElement( u"property"_s );
2762 QDomElement identifierElem = SIAInfoDoc.createElement( u"identifier"_s );
2763 QDomText identifierText = SIAInfoDoc.createTextNode( currentAttributeName );
2764 identifierElem.appendChild( identifierText );
2765 QDomElement valueElem = SIAInfoDoc.createElement( u"value"_s );
2766 QDomText valueText = SIAInfoDoc.createTextNode( currentAttributeValue );
2767 valueElem.appendChild( valueText );
2768 propertyElem.appendChild( identifierElem );
2769 propertyElem.appendChild( valueElem );
2770 if ( propertyRefChild.isNull() )
2771 {
2772 SIAFeatureElem.insertBefore( propertyElem, QDomNode() );
2773 propertyRefChild = propertyElem;
2774 }
2775 else
2776 {
2777 SIAFeatureElem.insertAfter( propertyElem, propertyRefChild );
2778 }
2779 }
2780 else
2781 {
2782 QDomElement SIAAttributeElem = SIAInfoDoc.createElement( currentAttributeName );
2783 QDomText SIAAttributeText = SIAInfoDoc.createTextNode( currentAttributeValue );
2784 SIAAttributeElem.appendChild( SIAAttributeText );
2785 SIAFeatureElem.appendChild( SIAAttributeElem );
2786 }
2787 }
2788 SIAInfoDocElement.appendChild( SIAFeatureElem );
2789 }
2790 }
2791 }
2792 doc = SIAInfoDoc;
2793 }
2794
2795 QByteArray QgsRenderer::convertFeatureInfoToHtml( const QDomDocument &doc ) const
2796 {
2797 const bool onlyMapTip = mWmsParameters.htmlInfoOnlyMapTip() || QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( *mProject );
2798 QString featureInfoString = u" <!DOCTYPE html>"_s;
2799 if ( !onlyMapTip )
2800 {
2801 featureInfoString.append( QStringLiteral( R"HTML(
2802
2803 <head>
2804 <title>Information</title>
2805 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
2806 <style>
2807 body {
2808 font-family: "Open Sans", "Calluna Sans", "Gill Sans MT", "Calibri", "Trebuchet MS", sans-serif;
2809 }
2810
2811 table,
2812 th,
2813 td {
2814 width: 100%;
2815 border: 1px solid black;
2816 border-collapse: collapse;
2817 text-align: left;
2818 padding: 2px;
2819 }
2820
2821 th {
2822 width: 25%;
2823 font-weight: bold;
2824 }
2825
2826 .layer-title {
2827 font-weight: bold;
2828 padding: 2px;
2829 }
2830 </style>
2831 </head>
2832
2833 <body>
2834 )HTML" ) );
2835 }
2836
2837 const QDomNodeList layerList = doc.elementsByTagName( u"Layer"_s );
2838
2839 //layer loop
2840 for ( int i = 0; i < layerList.size(); ++i )
2841 {
2842 const QDomElement layerElem = layerList.at( i ).toElement();
2843
2844 //feature loop (for vector layers)
2845 const QDomNodeList featureNodeList = layerElem.elementsByTagName( u"Feature"_s );
2846 const QDomElement currentFeatureElement;
2847
2848 if ( !featureNodeList.isEmpty() ) //vector layer
2849 {
2850 if ( !onlyMapTip )
2851 {
2852 const QString featureInfoLayerTitleString = u" <div class='layer-title'>%1</div>"_s.arg( layerElem.attribute( u"title"_s ).toHtmlEscaped() );
2853 featureInfoString.append( featureInfoLayerTitleString );
2854 }
2855
2856 for ( int j = 0; j < featureNodeList.size(); ++j )
2857 {
2858 const QDomElement featureElement = featureNodeList.at( j ).toElement();
2859 if ( !onlyMapTip )
2860 {
2861 featureInfoString.append( QStringLiteral( R"HTML(
2862 <table>)HTML" ) );
2863 }
2864
2865 //attribute loop
2866 const QDomNodeList attributeNodeList = featureElement.elementsByTagName( u"Attribute"_s );
2867 for ( int k = 0; k < attributeNodeList.size(); ++k )
2868 {
2869 const QDomElement attributeElement = attributeNodeList.at( k ).toElement();
2870 const QString name = attributeElement.attribute( u"name"_s ).toHtmlEscaped();
2871 QString value = attributeElement.attribute( u"value"_s );
2872 if ( name != "maptip"_L1 )
2873 {
2874 value = value.toHtmlEscaped();
2875 }
2876
2877 if ( !onlyMapTip )
2878 {
2879 const QString featureInfoAttributeString = QStringLiteral( R"HTML(
2880 <tr>
2881 <th>%1</th>
2882 <td>%2</td>
2883 </tr>)HTML" )
2884 .arg( name, value );
2885
2886 featureInfoString.append( featureInfoAttributeString );
2887 }
2888 else if ( name == "maptip"_L1 )
2889 {
2890 featureInfoString.append( QStringLiteral( R"HTML(
2891 %1)HTML" )
2892 .arg( value ) );
2893 break;
2894 }
2895 }
2896 if ( !onlyMapTip )
2897 {
2898 featureInfoString.append( QStringLiteral( R"HTML(
2899 </table>)HTML" ) );
2900 }
2901 }
2902 }
2903 else //no result or raster layer
2904 {
2905 const QDomNodeList attributeNodeList = layerElem.elementsByTagName( u"Attribute"_s );
2906
2907 // raster layer
2908 if ( !attributeNodeList.isEmpty() )
2909 {
2910 if ( !onlyMapTip )
2911 {
2912 const QString featureInfoLayerTitleString = u" <div class='layer-title'>%1</div>"_s.arg( layerElem.attribute( u"title"_s ).toHtmlEscaped() );
2913 featureInfoString.append( featureInfoLayerTitleString );
2914
2915 featureInfoString.append( QStringLiteral( R"HTML(
2916 <table>)HTML" ) );
2917 }
2918
2919 for ( int j = 0; j < attributeNodeList.size(); ++j )
2920 {
2921 const QDomElement attributeElement = attributeNodeList.at( j ).toElement();
2922 const QString name = attributeElement.attribute( u"name"_s ).toHtmlEscaped();
2923 QString value = attributeElement.attribute( u"value"_s );
2924 if ( value.isEmpty() )
2925 {
2926 value = u"no data"_s;
2927 }
2928 if ( name != "maptip"_L1 )
2929 {
2930 value = value.toHtmlEscaped();
2931 }
2932
2933 if ( !onlyMapTip )
2934 {
2935 const QString featureInfoAttributeString = QStringLiteral( R"HTML(
2936 <tr>
2937 <th>%1</th>
2938 <td>%2</td>
2939 </tr>)HTML" )
2940 .arg( name, value );
2941
2942
2943 featureInfoString.append( featureInfoAttributeString );
2944 }
2945 else if ( name == "maptip"_L1 )
2946 {
2947 featureInfoString.append( QStringLiteral( R"HTML(
2948 %1)HTML" )
2949 .arg( value ) );
2950 break;
2951 }
2952 }
2953 if ( !onlyMapTip )
2954 {
2955 featureInfoString.append( QStringLiteral( R"HTML(
2956 </table>)HTML" ) );
2957 }
2958 }
2959 }
2960 }
2961
2962 //end the html body
2963 if ( !onlyMapTip )
2964 {
2965 featureInfoString.append( QStringLiteral( R"HTML(
2966 </body>)HTML" ) );
2967 }
2968
2969 return featureInfoString.toUtf8();
2970 }
2971
2972 QByteArray QgsRenderer::convertFeatureInfoToText( const QDomDocument &doc ) const
2973 {
2974 QString featureInfoString;
2975
2976 //the Text head
2977 featureInfoString.append( "GetFeatureInfo results\n" );
2978 featureInfoString.append( "\n" );
2979
2980 QDomNodeList layerList = doc.elementsByTagName( u"Layer"_s );
2981
2982 //layer loop
2983 for ( int i = 0; i < layerList.size(); ++i )
2984 {
2985 QDomElement layerElem = layerList.at( i ).toElement();
2986
2987 featureInfoString.append( "Layer '" + layerElem.attribute( u"name"_s ) + "'\n" );
2988
2989 //feature loop (for vector layers)
2990 QDomNodeList featureNodeList = layerElem.elementsByTagName( u"Feature"_s );
2991 QDomElement currentFeatureElement;
2992
2993 if ( !featureNodeList.isEmpty() ) //vector layer
2994 {
2995 for ( int j = 0; j < featureNodeList.size(); ++j )
2996 {
2997 QDomElement featureElement = featureNodeList.at( j ).toElement();
2998 featureInfoString.append( "Feature " + featureElement.attribute( u"id"_s ) + "\n" );
2999
3000 //attribute loop
3001 QDomNodeList attributeNodeList = featureElement.elementsByTagName( u"Attribute"_s );
3002 for ( int k = 0; k < attributeNodeList.size(); ++k )
3003 {
3004 QDomElement attributeElement = attributeNodeList.at( k ).toElement();
3005 featureInfoString.append( attributeElement.attribute( u"name"_s ) + " = '" + attributeElement.attribute( u"value"_s ) + "'\n" );
3006 }
3007 }
3008 }
3009 else //raster layer
3010 {
3011 QDomNodeList attributeNodeList = layerElem.elementsByTagName( u"Attribute"_s );
3012 for ( int j = 0; j < attributeNodeList.size(); ++j )
3013 {
3014 QDomElement attributeElement = attributeNodeList.at( j ).toElement();
3015 QString value = attributeElement.attribute( u"value"_s );
3016 if ( value.isEmpty() )
3017 {
3018 value = u"no data"_s;
3019 }
3020 featureInfoString.append( attributeElement.attribute( u"name"_s ) + " = '" + value + "'\n" );
3021 }
3022 }
3023
3024 featureInfoString.append( "\n" );
3025 }
3026
3027 return featureInfoString.toUtf8();
3028 }
3029
3030 QByteArray QgsRenderer::convertFeatureInfoToJson( const QList<QgsMapLayer *> &layers, const QDomDocument &doc, const QgsCoordinateReferenceSystem &destCRS ) const
3031 {
3032 json json {
3033 { "type", "FeatureCollection" },
3034 { "features", json::array() },
3035 };
3036 const bool withGeometry = ( QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) && mWmsParameters.withGeometry() );
3037 const bool withDisplayName = mWmsParameters.withDisplayName();
3038
3039 const QDomNodeList layerList = doc.elementsByTagName( u"Layer"_s );
3040 for ( int i = 0; i < layerList.size(); ++i )
3041 {
3042 const QDomElement layerElem = layerList.at( i ).toElement();
3043 const QString layerName = layerElem.attribute( u"name"_s );
3044
3045 QgsMapLayer *layer = nullptr;
3046 for ( QgsMapLayer *l : layers )
3047 {
3048 if ( mContext.layerNickname( *l ).compare( layerName ) == 0 )
3049 {
3050 layer = l;
3051 }
3052 }
3053
3054 if ( !layer )
3055 continue;
3056
3057 if ( layer->type() == Qgis::LayerType::Vector )
3058 {
3059 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
3060
3061 // search features to export
3062 QgsFeatureList features;
3063 QgsAttributeList attributes;
3064 const QDomNodeList featuresNode = layerElem.elementsByTagName( u"Feature"_s );
3065 if ( featuresNode.isEmpty() )
3066 continue;
3067
3068 QMap<QgsFeatureId, QString> fidMap;
3069 QMap<QgsFeatureId, QString> fidDisplayNameMap;
3070
3071 for ( int j = 0; j < featuresNode.size(); ++j )
3072 {
3073 const QDomElement featureNode = featuresNode.at( j ).toElement();
3074 const QString fid = featureNode.attribute( u"id"_s );
3075 QgsFeature feature;
3076 const QString expression { QgsServerFeatureId::getExpressionFromServerFid( fid, static_cast<QgsVectorDataProvider *>( layer->dataProvider() ) ) };
3077 if ( expression.isEmpty() )
3078 {
3079 feature = vl->getFeature( fid.toLongLong() );
3080 }
3081 else
3082 {
3083 QgsFeatureRequest request { QgsExpression( expression ) };
3085 vl->getFeatures( request ).nextFeature( feature );
3086 }
3087
3088 fidMap.insert( feature.id(), fid );
3089
3090 QString wkt;
3091 if ( withGeometry )
3092 {
3093 const QDomNodeList attrs = featureNode.elementsByTagName( "Attribute" );
3094 for ( int k = 0; k < attrs.count(); k++ )
3095 {
3096 const QDomElement elm = attrs.at( k ).toElement();
3097 if ( elm.attribute( u"name"_s ).compare( "geometry" ) == 0 )
3098 {
3099 wkt = elm.attribute( "value" );
3100 break;
3101 }
3102 }
3103
3104 if ( !wkt.isEmpty() )
3105 {
3106 // CRS in WMS parameters may be different from the layer
3107 feature.setGeometry( QgsGeometry::fromWkt( wkt ) );
3108 }
3109 }
3110
3111 // Note: this is the feature expression display name, not the field alias
3112 if ( withDisplayName )
3113 {
3114 QString displayName;
3115 const QDomNodeList attrs = featureNode.elementsByTagName( "Attribute" );
3116 for ( int k = 0; k < attrs.count(); k++ )
3117 {
3118 const QDomElement elm = attrs.at( k ).toElement();
3119 if ( elm.attribute( u"name"_s ).compare( "displayName" ) == 0 )
3120 {
3121 displayName = elm.attribute( "value" );
3122 break;
3123 }
3124 }
3125 fidDisplayNameMap.insert( feature.id(), displayName );
3126 }
3127
3128 features << feature;
3129
3130 // search attributes to export (one time only)
3131 if ( !attributes.isEmpty() )
3132 continue;
3133
3134 const QDomNodeList attributesNode = featureNode.elementsByTagName( u"Attribute"_s );
3135 for ( int k = 0; k < attributesNode.size(); ++k )
3136 {
3137 const QDomElement attributeElement = attributesNode.at( k ).toElement();
3138 const QString fieldName = attributeElement.attribute( u"name"_s );
3139 attributes << feature.fieldNameIndex( fieldName );
3140 }
3141 }
3142
3143 // export
3144 QgsJsonExporter exporter( vl );
3145 exporter.setAttributeDisplayName( true );
3146 exporter.setAttributes( attributes );
3147 exporter.setIncludeGeometry( withGeometry );
3148 exporter.setTransformGeometries( false );
3149
3150 QgsJsonUtils::addCrsInfo( json, destCRS );
3151
3152 for ( const auto &feature : std::as_const( features ) )
3153 {
3154 const QString id = u"%1.%2"_s.arg( layerName ).arg( fidMap.value( feature.id() ) );
3155 QVariantMap extraProperties;
3156 if ( withDisplayName )
3157 {
3158 extraProperties.insert( u"display_name"_s, fidDisplayNameMap.value( feature.id() ) );
3159 }
3160 json["features"].push_back( exporter.exportFeatureToJsonObject( feature, extraProperties, id ) );
3161 }
3162 }
3163 else // raster layer
3164 {
3165 auto properties = json::object();
3166 const QDomNodeList attributesNode = layerElem.elementsByTagName( u"Attribute"_s );
3167 for ( int j = 0; j < attributesNode.size(); ++j )
3168 {
3169 const QDomElement attrElmt = attributesNode.at( j ).toElement();
3170 const QString name = attrElmt.attribute( u"name"_s );
3171
3172 QString value = attrElmt.attribute( u"value"_s );
3173 if ( value.isEmpty() )
3174 {
3175 value = u"null"_s;
3176 }
3177
3178 properties[name.toStdString()] = value.toStdString();
3179 }
3180
3181 json["features"].push_back( { { "type", "Feature" }, { "id", layerName.toStdString() }, { "properties", properties } } );
3182 }
3183 }
3184#ifdef QGISDEBUG
3185 // This is only useful to generate human readable reference files for tests
3186 return QByteArray::fromStdString( json.dump( 2 ) );
3187#else
3188 return QByteArray::fromStdString( json.dump() );
3189#endif
3190 }
3191
3192 QDomElement QgsRenderer::createFeatureGML(
3193 const QgsFeature *feat, QgsVectorLayer *layer, QDomDocument &doc, QgsCoordinateReferenceSystem &crs, const QgsMapSettings &mapSettings, const QString &typeName, bool withGeom, int version, QStringList *attributes
3194 ) const
3195 {
3196 //qgs:%TYPENAME%
3197 QDomElement typeNameElement = doc.createElement( "qgs:" + typeName /*qgs:%TYPENAME%*/ );
3198 QString fid;
3199 if ( layer && layer->dataProvider() )
3201 else
3202 fid = FID_TO_STRING( feat->id() );
3203
3204 typeNameElement.setAttribute( u"fid"_s, u"%1.%2"_s.arg( typeName, fid ) );
3205
3206 QgsCoordinateTransform transform;
3207 if ( layer && layer->crs() != crs )
3208 {
3209 transform = mapSettings.layerTransform( layer );
3210 }
3211
3212 QgsGeometry geom = feat->geometry();
3213
3214 QgsExpressionContext expressionContext;
3216 if ( layer )
3217 expressionContext << QgsExpressionContextUtils::layerScope( layer );
3218 expressionContext.setFeature( *feat );
3219
3220 QgsEditFormConfig editConfig { layer ? layer->editFormConfig() : QgsEditFormConfig() };
3221 const bool honorFormConfig { layer && QgsServerProjectUtils::wmsFeatureInfoUseAttributeFormSettings( *mProject ) && editConfig.layout() == Qgis::AttributeFormLayout::DragAndDrop };
3222
3223 // always add bounding box info if feature contains geometry and has been
3224 // explicitly configured in the project
3226 {
3227 QgsRectangle box = feat->geometry().boundingBox();
3228 if ( transform.isValid() )
3229 {
3230 try
3231 {
3232 box = transform.transformBoundingBox( box );
3233 }
3234 catch ( QgsCsException &e )
3235 {
3236 QgsMessageLog::logMessage( u"Transform error caught: %1"_s.arg( e.what() ) );
3237 }
3238 }
3239
3240 QDomElement bbElem = doc.createElement( u"gml:boundedBy"_s );
3241 QDomElement boxElem;
3242 if ( version < 3 )
3243 {
3244 boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, mContext.precision() );
3245 }
3246 else
3247 {
3248 boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, mContext.precision() );
3249 }
3250
3251 if ( crs.isValid() )
3252 {
3253 boxElem.setAttribute( u"srsName"_s, crs.authid() );
3254 }
3255 bbElem.appendChild( boxElem );
3256 typeNameElement.appendChild( bbElem );
3257 }
3258
3259 // find if an attribute is in any form tab
3260 std::function<bool( const QString &, const QgsAttributeEditorElement * )> findAttributeInTree;
3261 findAttributeInTree = [&findAttributeInTree, &layer]( const QString &attributeName, const QgsAttributeEditorElement *group ) -> bool {
3262 const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( group );
3263 if ( container )
3264 {
3265 const QList<QgsAttributeEditorElement *> children = container->children();
3266 for ( const QgsAttributeEditorElement *child : children )
3267 {
3268 switch ( child->type() )
3269 {
3271 {
3272 if ( findAttributeInTree( attributeName, child ) )
3273 {
3274 return true;
3275 }
3276 break;
3277 }
3279 {
3280 if ( child->name() == attributeName )
3281 {
3282 return true;
3283 }
3284 break;
3285 }
3287 {
3288 const QgsAttributeEditorRelation *relationEditor = static_cast<const QgsAttributeEditorRelation *>( child );
3289 if ( relationEditor )
3290 {
3291 const QgsRelation &relation { relationEditor->relation() };
3292 if ( relation.referencedLayer() == layer )
3293 {
3294 const QgsAttributeList &referencedFields { relation.referencedFields() };
3295 for ( const auto &idx : std::as_const( referencedFields ) )
3296 {
3297 const QgsField f { layer->fields().at( idx ) };
3298 if ( f.name() == attributeName )
3299 {
3300 return true;
3301 }
3302 }
3303 }
3304 else if ( relation.referencingLayer() == layer )
3305 {
3306 const QgsAttributeList &referencingFields { relation.referencingFields() };
3307 for ( const auto &idx : std::as_const( referencingFields ) )
3308 {
3309 const QgsField f { layer->fields().at( idx ) };
3310 if ( f.name() == attributeName )
3311 {
3312 return true;
3313 }
3314 }
3315 }
3316 }
3317 break;
3318 }
3319 default:
3320 break;
3321 }
3322 }
3323 }
3324 return false;
3325 };
3326
3327 if ( withGeom && !geom.isNull() )
3328 {
3329 //add geometry column (as gml)
3330
3331 if ( transform.isValid() )
3332 {
3333 geom.transform( transform );
3334 }
3335
3336 QDomElement geomElem = doc.createElement( u"qgs:geometry"_s );
3337 QDomElement gmlElem;
3338 if ( version < 3 )
3339 {
3340 gmlElem = QgsOgcUtils::geometryToGML( geom, doc, mContext.precision() );
3341 }
3342 else
3343 {
3344 gmlElem = QgsOgcUtils::geometryToGML( geom, doc, u"GML3"_s, mContext.precision() );
3345 }
3346
3347 if ( !gmlElem.isNull() )
3348 {
3349 if ( crs.isValid() )
3350 {
3351 gmlElem.setAttribute( u"srsName"_s, crs.authid() );
3352 }
3353 geomElem.appendChild( gmlElem );
3354 typeNameElement.appendChild( geomElem );
3355 }
3356 }
3357
3358 //read all allowed attribute values from the feature
3359 QgsAttributes featureAttributes = feat->attributes();
3360 QgsFields fields = feat->fields();
3361 for ( int i = 0; i < fields.count(); ++i )
3362 {
3363 QString attributeName = fields.at( i ).name();
3364 //skip attribute if it is explicitly excluded from WMS publication
3365 if ( fields.at( i ).configurationFlags().testFlag( Qgis::FieldConfigurationFlag::HideFromWms ) )
3366 {
3367 continue;
3368 }
3369 //skip attribute if it is excluded by access control
3370 if ( attributes && !attributes->contains( attributeName ) )
3371 {
3372 continue;
3373 }
3374
3375 if ( honorFormConfig )
3376 {
3377 const QgsAttributeEditorContainer *editorContainer = editConfig.invisibleRootContainer();
3378 if ( !editorContainer || !findAttributeInTree( attributeName, editorContainer ) )
3379 {
3380 continue;
3381 }
3382 }
3383
3384 QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ) );
3385 QString fieldTextString = featureAttributes.at( i ).toString();
3386 if ( layer )
3387 {
3388 fieldTextString = QgsExpression::replaceExpressionText( replaceValueMapAndRelation( layer, i, fieldTextString ), &expressionContext );
3389 }
3390 QDomText fieldText = doc.createTextNode( fieldTextString );
3391 fieldElem.appendChild( fieldText );
3392 typeNameElement.appendChild( fieldElem );
3393 }
3394
3395 //add maptip attribute based on html/expression (in case there is no maptip attribute)
3396 if ( layer )
3397 {
3398 QString mapTip = layer->mapTipTemplate();
3399
3400 if ( !mapTip.isEmpty() && ( mWmsParameters.withMapTip() || mWmsParameters.htmlInfoOnlyMapTip() || QgsServerProjectUtils::wmsHTMLFeatureInfoUseOnlyMaptip( *mProject ) ) )
3401 {
3402 QString fieldTextString = QgsExpression::replaceExpressionText( mapTip, &expressionContext );
3403 QDomElement fieldElem = doc.createElement( u"qgs:maptip"_s );
3404 QDomText maptipText = doc.createTextNode( fieldTextString );
3405 fieldElem.appendChild( maptipText );
3406 typeNameElement.appendChild( fieldElem );
3407 }
3408 }
3409
3410 return typeNameElement;
3411 }
3412
3413 QString QgsRenderer::replaceValueMapAndRelation( QgsVectorLayer *vl, int idx, const QVariant &attributeVal )
3414 {
3415 const QgsEditorWidgetSetup setup = vl->editorWidgetSetup( idx );
3416 QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
3417 QString value( fieldFormatter->representValue( vl, idx, setup.config(), QVariant(), attributeVal ) );
3418
3419 if ( setup.config().value( u"AllowMulti"_s ).toBool() && value.startsWith( '{'_L1 ) && value.endsWith( '}'_L1 ) )
3420 {
3421 value = value.mid( 1, value.size() - 2 );
3422 }
3423 return value;
3424 }
3425
3426 QgsRectangle QgsRenderer::featureInfoSearchRect( QgsVectorLayer *ml, const QgsMapSettings &mapSettings, const QgsRenderContext &rct, const QgsPointXY &infoPoint ) const
3427 {
3428 if ( !ml )
3429 {
3430 return QgsRectangle();
3431 }
3432
3433 double mapUnitTolerance = 0.0;
3435 {
3436 if ( !mWmsParameters.polygonTolerance().isEmpty() && mWmsParameters.polygonToleranceAsInt() > 0 )
3437 {
3438 mapUnitTolerance = mWmsParameters.polygonToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
3439 }
3440 else
3441 {
3442 mapUnitTolerance = mapSettings.extent().width() / 400.0;
3443 }
3444 }
3445 else if ( ml->geometryType() == Qgis::GeometryType::Line )
3446 {
3447 if ( !mWmsParameters.lineTolerance().isEmpty() && mWmsParameters.lineToleranceAsInt() > 0 )
3448 {
3449 mapUnitTolerance = mWmsParameters.lineToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
3450 }
3451 else
3452 {
3453 mapUnitTolerance = mapSettings.extent().width() / 200.0;
3454 }
3455 }
3456 else //points
3457 {
3458 if ( !mWmsParameters.pointTolerance().isEmpty() && mWmsParameters.pointToleranceAsInt() > 0 )
3459 {
3460 mapUnitTolerance = mWmsParameters.pointToleranceAsInt() * rct.mapToPixel().mapUnitsPerPixel();
3461 }
3462 else
3463 {
3464 mapUnitTolerance = mapSettings.extent().width() / 100.0;
3465 }
3466 }
3467
3468 // Make sure the map unit tolerance is at least 1 pixel
3469 mapUnitTolerance = std::max( mapUnitTolerance, 1.0 * rct.mapToPixel().mapUnitsPerPixel() );
3470
3471 QgsRectangle mapRectangle( infoPoint.x() - mapUnitTolerance, infoPoint.y() - mapUnitTolerance, infoPoint.x() + mapUnitTolerance, infoPoint.y() + mapUnitTolerance );
3472 return ( mapSettings.mapToLayerCoordinates( ml, mapRectangle ) );
3473 }
3474
3475 QList<QgsMapLayer *> QgsRenderer::highlightLayers( QList<QgsWmsParametersHighlightLayer> params )
3476 {
3477 QList<QgsMapLayer *> highlightLayers;
3478
3479 // try to create highlight layer for each geometry
3480 QString crs = mWmsParameters.crs();
3481 for ( const QgsWmsParametersHighlightLayer &param : params )
3482 {
3483 // create sld document from symbology
3484 QDomDocument sldDoc;
3485 QString errorMsg;
3486 int errorLine;
3487 int errorColumn;
3488 if ( !sldDoc.setContent( param.mSld, true, &errorMsg, &errorLine, &errorColumn ) )
3489 {
3490 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 );
3491 continue;
3492 }
3493
3494 // create renderer from sld document
3495 std::unique_ptr<QgsFeatureRenderer> renderer;
3496 QDomElement el = sldDoc.documentElement();
3497 renderer.reset( QgsFeatureRenderer::loadSld( el, param.mGeom.type(), errorMsg ) );
3498 if ( !renderer )
3499 {
3501 continue;
3502 }
3503
3504 // build url for vector layer
3505 const QString typeName = QgsWkbTypes::displayString( param.mGeom.wkbType() );
3506 QString url = typeName + "?crs=" + crs;
3507 if ( !param.mLabel.isEmpty() )
3508 {
3509 url += "&field=label:string";
3510 }
3511
3512 // create vector layer
3513 const QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
3514 auto layer = std::make_unique<QgsVectorLayer>( url, param.mName, "memory"_L1, options );
3515 if ( !layer->isValid() )
3516 {
3517 continue;
3518 }
3519
3520 // create feature with label if necessary
3521 QgsFeature fet( layer->fields() );
3522 if ( !param.mLabel.isEmpty() )
3523 {
3524 fet.setAttribute( 0, param.mLabel );
3525
3526 // init labeling engine
3527 QgsPalLayerSettings palSettings;
3528 palSettings.fieldName = "label"; // defined in url
3529 palSettings.priority = 10; // always drawn
3531 palSettings.placementSettings().setAllowDegradedPlacement( true );
3532 palSettings.dist = param.mLabelDistance;
3533
3534 if ( !qgsDoubleNear( param.mLabelRotation, 0 ) )
3535 {
3537 palSettings.dataDefinedProperties().setProperty( pR, param.mLabelRotation );
3538 }
3539
3541 switch ( param.mGeom.type() )
3542 {
3544 {
3545 if ( param.mHali.isEmpty() || param.mVali.isEmpty() || QgsWkbTypes::flatType( param.mGeom.wkbType() ) != Qgis::WkbType::Point )
3546 {
3549 }
3550 else //set label directly on point if there is hali/vali
3551 {
3552 QgsPointXY pt = param.mGeom.asPoint();
3554 QVariant x( pt.x() );
3555 palSettings.dataDefinedProperties().setProperty( pX, x );
3557 QVariant y( pt.y() );
3558 palSettings.dataDefinedProperties().setProperty( pY, y );
3560 palSettings.dataDefinedProperties().setProperty( pHali, param.mHali );
3562 palSettings.dataDefinedProperties().setProperty( pVali, param.mVali );
3563 }
3564
3565 break;
3566 }
3568 {
3569 QgsGeometry point = param.mGeom.pointOnSurface();
3570 QgsPointXY pt = point.asPoint();
3572
3574 QVariant x( pt.x() );
3575 palSettings.dataDefinedProperties().setProperty( pX, x );
3576
3578 QVariant y( pt.y() );
3579 palSettings.dataDefinedProperties().setProperty( pY, y );
3580
3582 QVariant hali( "Center" );
3583 palSettings.dataDefinedProperties().setProperty( pHali, hali );
3584
3586 QVariant vali( "Half" );
3587 palSettings.dataDefinedProperties().setProperty( pVali, vali );
3588 break;
3589 }
3590 default:
3591 {
3592 placement = Qgis::LabelPlacement::Line;
3594 break;
3595 }
3596 }
3597 palSettings.placement = placement;
3598 QgsTextFormat textFormat;
3599 QgsTextBufferSettings bufferSettings;
3600
3601 if ( param.mColor.isValid() )
3602 {
3603 textFormat.setColor( param.mColor );
3604 }
3605
3606 if ( param.mSize > 0 )
3607 {
3608 textFormat.setSize( param.mSize );
3609 }
3610
3611 // no weight property in PAL settings or QgsTextFormat
3612 /* if ( param.fontWeight > 0 )
3613 {
3614 } */
3615
3616 if ( !param.mFont.isEmpty() )
3617 {
3618 textFormat.setFont( param.mFont );
3619 }
3620
3621 if ( param.mBufferColor.isValid() )
3622 {
3623 bufferSettings.setColor( param.mBufferColor );
3624 }
3625
3626 if ( param.mBufferSize > 0 )
3627 {
3628 bufferSettings.setEnabled( true );
3629 bufferSettings.setSize( static_cast<double>( param.mBufferSize ) );
3630 }
3631
3632 textFormat.setBuffer( bufferSettings );
3633 palSettings.setFormat( textFormat );
3634
3635 QgsVectorLayerSimpleLabeling *simpleLabeling = new QgsVectorLayerSimpleLabeling( palSettings );
3636 layer->setLabeling( simpleLabeling );
3637 layer->setLabelsEnabled( true );
3638 }
3639 fet.setGeometry( param.mGeom );
3640
3641 // add feature to layer and set the SLD renderer
3642 layer->dataProvider()->addFeatures( QgsFeatureList() << fet );
3643 layer->setRenderer( renderer.release() );
3644
3645 // keep the vector as an highlight layer
3646 if ( layer->isValid() )
3647 {
3648 highlightLayers.append( layer.release() );
3649 }
3650 }
3651
3652 mTemporaryLayers.append( highlightLayers );
3653 return highlightLayers;
3654 }
3655
3656 void QgsRenderer::removeTemporaryLayers()
3657 {
3658 qDeleteAll( mTemporaryLayers );
3659 mTemporaryLayers.clear();
3660 }
3661
3662 QPainter *QgsRenderer::layersRendering( const QgsMapSettings &mapSettings, QImage *image ) const
3663 {
3664 QPainter *painter = nullptr;
3665
3666 QgsFeatureFilterProviderGroup filters;
3667 filters.addProvider( &mFeatureFilter );
3668#ifdef HAVE_SERVER_PYTHON_PLUGINS
3669 mContext.accessControl()->resolveFilterFeatures( mapSettings.layers() );
3670 filters.addProvider( mContext.accessControl() );
3671#endif
3672 QgsMapRendererJobProxy renderJob( mContext.settings().parallelRendering(), mContext.settings().maxThreads(), &filters );
3673
3674 renderJob.render( mapSettings, image, mContext.socketFeedback() );
3675 painter = renderJob.takePainter();
3676
3677 logRenderingErrors( renderJob.errors() );
3678
3679 if ( !renderJob.errors().isEmpty() && !mContext.settings().ignoreRenderingErrors() )
3680 {
3681 const QgsMapRendererJob::Error e = renderJob.errors().at( 0 );
3682
3683 QString layerWMSName;
3684 QgsMapLayer *errorLayer = mProject->mapLayer( e.layerID );
3685 if ( errorLayer )
3686 {
3687 layerWMSName = mContext.layerNickname( *errorLayer );
3688 }
3689
3690 QString errorMessage = u"Rendering error : '%1'"_s.arg( e.message );
3691 if ( !layerWMSName.isEmpty() )
3692 {
3693 errorMessage = u"Rendering error : '%1' in layer '%2'"_s.arg( e.message, layerWMSName );
3694 }
3695 throw QgsException( errorMessage );
3696 }
3697
3698 return painter;
3699 }
3700
3701 void QgsRenderer::setLayerOpacity( QgsMapLayer *layer, int opacity ) const
3702 {
3703 if ( opacity >= 0 && opacity <= 255 )
3704 {
3705 switch ( layer->type() )
3706 {
3708 {
3709 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
3710 vl->setOpacity( opacity / 255. );
3711 // Labeling
3712 if ( vl->labelsEnabled() && vl->labeling() )
3713 {
3714 QgsAbstractVectorLayerLabeling *labeling { vl->labeling() };
3715 labeling->multiplyOpacity( opacity / 255. );
3716 }
3717 break;
3718 }
3719
3721 {
3722 QgsRasterLayer *rl = qobject_cast<QgsRasterLayer *>( layer );
3723 QgsRasterRenderer *rasterRenderer = rl->renderer();
3724 rasterRenderer->setOpacity( opacity / 255. );
3725 break;
3726 }
3727
3729 {
3730 QgsVectorTileLayer *vl = qobject_cast<QgsVectorTileLayer *>( layer );
3731 vl->setOpacity( opacity / 255. );
3732 break;
3733 }
3734
3741 break;
3742 }
3743 }
3744 }
3745
3746 void QgsRenderer::setLayerFilter( QgsMapLayer *layer, const QList<QgsWmsParametersFilter> &filters )
3747 {
3748 if ( layer->type() == Qgis::LayerType::Vector )
3749 {
3750 QgsVectorLayer *filteredLayer = qobject_cast<QgsVectorLayer *>( layer );
3751 QStringList expList;
3752 for ( const QgsWmsParametersFilter &filter : filters )
3753 {
3754 if ( filter.mType == QgsWmsParametersFilter::OGC_FE )
3755 {
3756 // OGC filter
3757 QDomDocument filterXml;
3758
3759 QXmlStreamReader xmlReader( filter.mFilter );
3760 xmlReader.addExtraNamespaceDeclaration( QXmlStreamNamespaceDeclaration( u"fes"_s, u"http://www.opengis.net/fes/2.0"_s ) );
3761 xmlReader.addExtraNamespaceDeclaration( QXmlStreamNamespaceDeclaration( u"ogc"_s, u"http://www.opengis.net/ogc"_s ) );
3762 if ( QDomDocument::ParseResult result = filterXml.setContent( &xmlReader, QDomDocument::ParseOption::UseNamespaceProcessing ); !result )
3763 {
3764 throw QgsBadRequestException(
3766 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 )
3767 );
3768 }
3769
3770 QDomElement filterElem = filterXml.firstChildElement();
3771 std::unique_ptr<QgsExpression> filterExp( QgsOgcUtils::expressionFromOgcFilter( filterElem, filter.mVersion, filteredLayer ) );
3772
3773 if ( filterExp )
3774 {
3775 expList << filterExp->dump();
3776 }
3777 }
3778 else if ( filter.mType == QgsWmsParametersFilter::SQL )
3779 {
3780 // QGIS (SQL) filter
3781 if ( !testFilterStringSafety( filter.mFilter ) )
3782 {
3783 throw QgsSecurityException(
3784 QStringLiteral(
3785 "The filter string %1"
3786 " has been rejected because of security reasons."
3787 " Note: Text strings have to be enclosed in single or double quotes."
3788 " A space between each word / special character is mandatory."
3789 " Allowed Keywords and special characters are"
3790 " IS,NOT,NULL,AND,OR,IN,=,<,>=,>,>=,!=,',',(,),DMETAPHONE,SOUNDEX%2."
3791 " Not allowed are semicolons in the filter expression."
3792 )
3793 .arg( filter.mFilter, mContext.settings().allowedExtraSqlTokens().isEmpty() ? QString() : mContext.settings().allowedExtraSqlTokens().join( ',' ).prepend( ',' ) )
3794 );
3795 }
3796
3797 QString newSubsetString = filter.mFilter;
3798 if ( !filteredLayer->subsetString().isEmpty() )
3799 {
3800 newSubsetString.prepend( ") AND (" );
3801 newSubsetString.append( ")" );
3802 newSubsetString.prepend( filteredLayer->subsetString() );
3803 newSubsetString.prepend( "(" );
3804 }
3805 if ( !filteredLayer->setSubsetString( newSubsetString ) )
3806 {
3807 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 );
3808 throw QgsBadRequestException( QgsServiceException::QGIS_InvalidParameterValue, u"Filter not valid for layer %1: check the filter syntax and the field names."_s.arg( layer->name() ) );
3809 }
3810 }
3811 }
3812
3813 expList.append( dimensionFilter( filteredLayer ) );
3814
3815 // Join and apply expressions provided by OGC filter and Dimensions
3816 QString exp;
3817 if ( expList.size() == 1 )
3818 {
3819 exp = expList[0];
3820 }
3821 else if ( expList.size() > 1 )
3822 {
3823 exp = u"( %1 )"_s.arg( expList.join( " ) AND ( "_L1 ) );
3824 }
3825 if ( !exp.isEmpty() )
3826 {
3827 auto expression = std::make_unique<QgsExpression>( exp );
3828 if ( expression )
3829 {
3831 mFeatureFilter.setFilter( filteredLayer, *expression );
3833 }
3834 }
3835 }
3836 }
3837
3838 QStringList QgsRenderer::dimensionFilter( QgsVectorLayer *layer ) const
3839 {
3840 QStringList expList;
3841 // WMS Dimension filters
3842 QgsMapLayerServerProperties *serverProperties = static_cast<QgsMapLayerServerProperties *>( layer->serverProperties() );
3843 const QList<QgsMapLayerServerProperties::WmsDimensionInfo> wmsDims = serverProperties->wmsDimensions();
3844 if ( wmsDims.isEmpty() )
3845 {
3846 return expList;
3847 }
3848
3849 QMap<QString, QString> dimParamValues = mContext.parameters().dimensionValues();
3850 for ( const QgsMapLayerServerProperties::WmsDimensionInfo &dim : wmsDims )
3851 {
3852 // Skip temporal properties for this layer, give precedence to the dimensions implementation
3853 if ( mIsTemporal && dim.name.toUpper() == "TIME"_L1 && layer->temporalProperties()->isActive() )
3854 {
3855 layer->temporalProperties()->setIsActive( false );
3856 }
3857 // Check field index
3858 int fieldIndex = layer->fields().indexOf( dim.fieldName );
3859 if ( fieldIndex == -1 )
3860 {
3861 continue;
3862 }
3863 // Check end field index
3864 int endFieldIndex = -1;
3865 if ( !dim.endFieldName.isEmpty() )
3866 {
3867 endFieldIndex = layer->fields().indexOf( dim.endFieldName );
3868 if ( endFieldIndex == -1 )
3869 {
3870 continue;
3871 }
3872 }
3873 // Apply dimension filtering
3874 if ( !dimParamValues.contains( dim.name.toUpper() ) )
3875 {
3876 // Default value based on type configured by user
3877 QVariant defValue;
3878 if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::AllValues )
3879 {
3880 continue; // no filter by default for this dimension
3881 }
3882 else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::ReferenceValue )
3883 {
3884 defValue = dim.referenceValue;
3885 }
3886 else
3887 {
3888 // get unique values
3889 QSet<QVariant> uniqueValues = layer->uniqueValues( fieldIndex );
3890 if ( endFieldIndex != -1 )
3891 {
3892 uniqueValues.unite( layer->uniqueValues( endFieldIndex ) );
3893 }
3894 // sort unique values
3895 QList<QVariant> values = qgis::setToList( uniqueValues );
3896 std::sort( values.begin(), values.end() );
3897 if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MinValue )
3898 {
3899 defValue = values.first();
3900 }
3901 else if ( dim.defaultDisplayType == QgsMapLayerServerProperties::WmsDimensionInfo::MaxValue )
3902 {
3903 defValue = values.last();
3904 }
3905 }
3906 // build expression
3907 if ( endFieldIndex == -1 )
3908 {
3909 expList << QgsExpression::createFieldEqualityExpression( dim.fieldName, defValue );
3910 }
3911 else
3912 {
3913 QStringList expElems;
3914 expElems
3915 << QgsExpression::quotedColumnRef( dim.fieldName )
3916 << u"<="_s
3917 << QgsExpression::quotedValue( defValue )
3918 << u"AND"_s
3919 << QgsExpression::quotedColumnRef( dim.endFieldName )
3920 << u">="_s
3921 << QgsExpression::quotedValue( defValue );
3922 expList << expElems.join( ' ' );
3923 }
3924 }
3925 else
3926 {
3927 // Get field to convert value provided in parameters
3928 QgsField dimField = layer->fields().at( fieldIndex );
3929 // Value provided in parameters
3930 QString dimParamValue = dimParamValues[dim.name.toUpper()];
3931 // The expression list for this dimension
3932 QStringList dimExplist;
3933 // Multiple values are separated by ,
3934 QStringList dimValues = dimParamValue.split( ',' );
3935 for ( int i = 0; i < dimValues.size(); ++i )
3936 {
3937 QString dimValue = dimValues[i];
3938 // Trim value if necessary
3939 if ( dimValue.size() > 1 )
3940 {
3941 dimValue = dimValue.trimmed();
3942 }
3943 // Range value is separated by / for example 0/1
3944 if ( dimValue.contains( '/' ) )
3945 {
3946 QStringList rangeValues = dimValue.split( '/' );
3947 // Check range value size
3948 if ( rangeValues.size() != 2 )
3949 {
3950 continue; // throw an error
3951 }
3952 // Get range values
3953 QVariant rangeMin = QVariant( rangeValues[0] );
3954 QVariant rangeMax = QVariant( rangeValues[1] );
3955 // Convert and check range values
3956 if ( !dimField.convertCompatible( rangeMin ) )
3957 {
3958 continue; // throw an error
3959 }
3960 if ( !dimField.convertCompatible( rangeMax ) )
3961 {
3962 continue; // throw an error
3963 }
3964 // Build expression for this range
3965 QStringList expElems;
3966 if ( endFieldIndex == -1 )
3967 {
3968 // The field values are between min and max range
3969 expElems
3970 << QgsExpression::quotedColumnRef( dim.fieldName )
3971 << u">="_s
3972 << QgsExpression::quotedValue( rangeMin )
3973 << u"AND"_s
3974 << QgsExpression::quotedColumnRef( dim.fieldName )
3975 << u"<="_s
3976 << QgsExpression::quotedValue( rangeMax );
3977 }
3978 else
3979 {
3980 // The start field or the end field are lesser than min range
3981 // or the start field or the end field are greater than min range
3982 expElems
3983 << u"("_s
3984 << QgsExpression::quotedColumnRef( dim.fieldName )
3985 << u">="_s
3986 << QgsExpression::quotedValue( rangeMin )
3987 << u"OR"_s
3988 << QgsExpression::quotedColumnRef( dim.endFieldName )
3989 << u">="_s
3990 << QgsExpression::quotedValue( rangeMin )
3991 << u") AND ("_s
3992 << QgsExpression::quotedColumnRef( dim.fieldName )
3993 << u"<="_s
3994 << QgsExpression::quotedValue( rangeMax )
3995 << u"OR"_s
3996 << QgsExpression::quotedColumnRef( dim.endFieldName )
3997 << u"<="_s
3998 << QgsExpression::quotedValue( rangeMax )
3999 << u")"_s;
4000 }
4001 dimExplist << expElems.join( ' ' );
4002 }
4003 else
4004 {
4005 QVariant dimVariant = QVariant( dimValue );
4006 if ( !dimField.convertCompatible( dimVariant ) )
4007 {
4008 continue; // throw an error
4009 }
4010 // Build expression for this value
4011 if ( endFieldIndex == -1 )
4012 {
4013 // Field is equal to
4014 dimExplist << QgsExpression::createFieldEqualityExpression( dim.fieldName, dimVariant );
4015 }
4016 else
4017 {
4018 // The start field is lesser or equal to
4019 // and the end field is greater or equal to
4020 QStringList expElems;
4021 expElems
4022 << QgsExpression::quotedColumnRef( dim.fieldName )
4023 << u"<="_s
4024 << QgsExpression::quotedValue( dimVariant )
4025 << u"AND"_s
4026 << QgsExpression::quotedColumnRef( dim.endFieldName )
4027 << u">="_s
4028 << QgsExpression::quotedValue( dimVariant );
4029 dimExplist << expElems.join( ' ' );
4030 }
4031 }
4032 }
4033 // Build the expression for this dimension
4034 if ( dimExplist.size() == 1 )
4035 {
4036 expList << dimExplist;
4037 }
4038 else if ( dimExplist.size() > 1 )
4039 {
4040 expList << u"( %1 )"_s.arg( dimExplist.join( " ) OR ( "_L1 ) );
4041 }
4042 }
4043 }
4044 return expList;
4045 }
4046
4047 void QgsRenderer::setLayerSelection( QgsMapLayer *layer, const QStringList &fids ) const
4048 {
4049 if ( !fids.empty() && layer->type() == Qgis::LayerType::Vector )
4050 {
4051 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
4052
4053 QgsFeatureRequest request;
4055 const QgsFeatureIds selectedIds = request.filterFids();
4056
4057 if ( selectedIds.empty() )
4058 {
4060 }
4061 else
4062 {
4063 vl->selectByIds( selectedIds );
4064 }
4065 }
4066 }
4067
4068 void QgsRenderer::setLayerAccessControlFilter( QgsMapLayer *layer ) const
4069 {
4070#ifdef HAVE_SERVER_PYTHON_PLUGINS
4071 QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( mContext.accessControl(), layer );
4072#else
4073 Q_UNUSED( layer )
4074#endif
4075 }
4076
4077 void QgsRenderer::updateExtent( const QgsMapLayer *layer, QgsMapSettings &mapSettings ) const
4078 {
4079 QgsRectangle layerExtent = mapSettings.layerToMapCoordinates( layer, layer->extent() );
4080 QgsRectangle mapExtent = mapSettings.extent();
4081 if ( !layerExtent.isEmpty() )
4082 {
4083 mapExtent.combineExtentWith( layerExtent );
4084 mapSettings.setExtent( mapExtent );
4085 }
4086 }
4087
4088 void QgsRenderer::annotationsRendering( QPainter *painter, const QgsMapSettings &mapSettings ) const
4089 {
4090 const QgsAnnotationManager *annotationManager = mProject->annotationManager();
4091 const QList<QgsAnnotation *> annotations = annotationManager->annotations();
4092
4093 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( painter );
4095 renderContext.setFeedback( mContext.socketFeedback() );
4096
4097 for ( QgsAnnotation *annotation : annotations )
4098 {
4099 if ( mContext.socketFeedback() && mContext.socketFeedback()->isCanceled() )
4100 break;
4101 if ( !annotation || !annotation->isVisible() )
4102 continue;
4103
4104 //consider item position
4105 double offsetX = 0;
4106 double offsetY = 0;
4107 if ( annotation->hasFixedMapPosition() )
4108 {
4109 QgsPointXY mapPos = annotation->mapPosition();
4110 if ( mapSettings.destinationCrs() != annotation->mapPositionCrs() )
4111 {
4112 QgsCoordinateTransform coordTransform( annotation->mapPositionCrs(), mapSettings.destinationCrs(), mapSettings.transformContext() );
4113 try
4114 {
4115 mapPos = coordTransform.transform( mapPos );
4116 }
4117 catch ( const QgsCsException &e )
4118 {
4119 QgsMessageLog::logMessage( u"Error transforming coordinates of annotation item: %1"_s.arg( e.what() ) );
4120 }
4121 }
4122 const QgsPointXY devicePos = mapSettings.mapToPixel().transform( mapPos );
4123 offsetX = devicePos.x();
4124 offsetY = devicePos.y();
4125 }
4126 else
4127 {
4128 const QPointF relativePos = annotation->relativePosition();
4129 offsetX = mapSettings.outputSize().width() * relativePos.x();
4130 offsetY = mapSettings.outputSize().height() * relativePos.y();
4131 }
4132
4133 painter->save();
4134 painter->translate( offsetX, offsetY );
4135 annotation->render( renderContext );
4136 painter->restore();
4137 }
4138 }
4139
4140 QImage *QgsRenderer::scaleImage( const QImage *image ) const
4141 {
4142 // Test if width / height ratio of image is the same as the ratio of
4143 // WIDTH / HEIGHT parameters. If not, the image has to be scaled (required
4144 // by WMS spec)
4145 QImage *scaledImage = nullptr;
4146 const int width = mWmsParameters.widthAsInt();
4147 const int height = mWmsParameters.heightAsInt();
4148 if ( width != image->width() || height != image->height() )
4149 {
4150 scaledImage = new QImage( image->scaled( width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
4151 }
4152
4153 return scaledImage;
4154 }
4156 void QgsRenderer::logRenderingErrors( const QgsMapRendererJob::Errors &errors ) const
4157 {
4158 QgsMapRendererJob::Errors::const_iterator it = errors.constBegin();
4159 for ( ; it != errors.constEnd(); ++it )
4160 {
4161 QString msg = QString( "Rendering error: %1" ).arg( it->message );
4162 if ( !it->layerID.isEmpty() )
4163 {
4164 msg += QString( " in layer %1" ).arg( it->layerID );
4165 }
4167 }
4168 }
4169
4170 void QgsRenderer::handlePrintErrors( const QgsLayout *layout ) const
4171 {
4172 if ( !layout )
4173 {
4174 return;
4175 }
4176
4177 QList<QgsLayoutItemMap *> mapList;
4178 layout->layoutItems( mapList );
4179
4180 //log rendering errors even if they are ignored
4181 QList<QgsLayoutItemMap *>::const_iterator mapIt = mapList.constBegin();
4182 for ( ; mapIt != mapList.constEnd(); ++mapIt )
4183 {
4184 logRenderingErrors( ( *mapIt )->renderingErrors() );
4185 }
4186
4187 if ( mContext.settings().ignoreRenderingErrors() )
4188 {
4189 return;
4190 }
4191
4192 mapIt = mapList.constBegin();
4193 for ( ; mapIt != mapList.constEnd(); ++mapIt )
4194 {
4195 if ( !( *mapIt )->renderingErrors().isEmpty() )
4196 {
4197 const QgsMapRendererJob::Error e = ( *mapIt )->renderingErrors().at( 0 );
4198 throw QgsException( u"Rendering error : '%1' in layer %2"_s.arg( e.message, e.layerID ) );
4199 }
4200 }
4201 }
4202
4203 void QgsRenderer::configureLayers( QList<QgsMapLayer *> &layers, QgsMapSettings *settings )
4204 {
4205 const bool useSld = !mContext.parameters().sldBody().isEmpty();
4206
4207 for ( auto layer : layers )
4208 {
4209 const QgsWmsParametersLayer param = mContext.parameters( *layer );
4210
4211 if ( !mContext.layersToRender().contains( layer ) )
4212 {
4213 continue;
4214 }
4215
4216 if ( mContext.isExternalLayer( param.mNickname ) )
4217 {
4218 if ( mContext.testFlag( QgsWmsRenderContext::UseOpacity ) )
4219 {
4220 setLayerOpacity( layer, param.mOpacity );
4221 }
4222 continue;
4223 }
4224
4225 if ( useSld )
4226 {
4227 setLayerSld( layer, mContext.sld( *layer ) );
4228 }
4229 else
4230 {
4231 setLayerStyle( layer, mContext.style( *layer ) );
4232 }
4233
4234 if ( mContext.testFlag( QgsWmsRenderContext::UseOpacity ) )
4235 {
4236 setLayerOpacity( layer, param.mOpacity );
4237 }
4238
4239 if ( mContext.testFlag( QgsWmsRenderContext::UseFilter ) )
4240 {
4241 setLayerFilter( layer, param.mFilter );
4242 }
4243
4244 if ( mContext.testFlag( QgsWmsRenderContext::SetAccessControl ) )
4245 {
4246 setLayerAccessControlFilter( layer );
4247 }
4248
4249 if ( mContext.testFlag( QgsWmsRenderContext::UseSelection ) )
4250 {
4251 setLayerSelection( layer, param.mSelection );
4252 }
4253
4254 if ( settings && mContext.updateExtent() )
4255 {
4256 updateExtent( layer, *settings );
4257 }
4258 }
4259
4260 if ( mContext.testFlag( QgsWmsRenderContext::AddHighlightLayers ) )
4261 {
4262 layers = highlightLayers( mWmsParameters.highlightLayersParameters() ) << layers;
4263 }
4264 }
4265
4266 void QgsRenderer::setLayerStyle( QgsMapLayer *layer, const QString &style ) const
4267 {
4268 if ( style.isEmpty() )
4269 {
4270 return;
4271 }
4272
4273 bool rc = layer->styleManager()->setCurrentStyle( style );
4274 if ( !rc )
4275 {
4276 throw QgsBadRequestException( QgsServiceException::OGC_StyleNotDefined, u"Style '%1' does not exist for layer '%2'"_s.arg( style, layer->name() ) );
4277 }
4278 }
4279
4280 void QgsRenderer::setLayerSld( QgsMapLayer *layer, const QDomElement &sld ) const
4281 {
4282 QString err;
4283 // Defined sld style name
4284 const QStringList styles = layer->styleManager()->styles();
4285 QString sldStyleName = "__sld_style";
4286 while ( styles.contains( sldStyleName ) )
4287 {
4288 sldStyleName.append( '@' );
4289 }
4290 layer->styleManager()->addStyleFromLayer( sldStyleName );
4291 layer->styleManager()->setCurrentStyle( sldStyleName );
4292 layer->readSld( sld, err );
4293 layer->setCustomProperty( "sldStyleName", sldStyleName );
4294 }
4295
4296 QgsLegendSettings QgsRenderer::legendSettings()
4297 {
4298 // getting scale from bbox or default size
4299 QgsLegendSettings settings = mWmsParameters.legendSettings();
4300
4301 if ( !mWmsParameters.bbox().isEmpty() )
4302 {
4303 QgsMapSettings mapSettings;
4305 std::unique_ptr<QImage> tmp( createImage( mContext.mapSize( false ) ) );
4306 configureMapSettings( tmp.get(), mapSettings );
4307 // QGIS 5.0 - require correct use of QgsRenderContext instead of these
4309 settings.setMapScale( mapSettings.scale() );
4310 settings.setMapUnitsPerPixel( mapSettings.mapUnitsPerPixel() );
4312 }
4313 else
4314 {
4315 // QGIS 5.0 - require correct use of QgsRenderContext instead of these
4317 const double defaultMapUnitsPerPixel = QgsServerProjectUtils::wmsDefaultMapUnitsPerMm( *mContext.project() ) / mContext.dotsPerMm();
4318 settings.setMapUnitsPerPixel( defaultMapUnitsPerPixel );
4320 }
4321
4322 return settings;
4323 }
4324} // 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:1385
@ AboveLine
Labels can be placed above a line feature. Unless MapOrientation is also specified this mode respects...
Definition qgis.h:1383
@ Millimeters
Millimeters.
Definition qgis.h:5491
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition qgis.h:1273
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
Definition qgis.h:1274
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
Definition qgis.h:1276
@ VisibleLayers
Synchronize to map layers. The legend will include layers which are included in the linked map only.
Definition qgis.h:4869
@ AllProjectLayers
Synchronize to all project layers.
Definition qgis.h:4868
@ Manual
No automatic synchronization of legend layers. The legend will be manually populated.
Definition qgis.h:4870
@ DragAndDrop
"Drag and drop" layout. Needs to be configured.
Definition qgis.h:5973
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2331
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2329
@ NoFlags
No flags are set.
Definition qgis.h:2328
@ 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:1396
@ ShowRuleDetails
If set, the rule expression of a rule based renderer legend item will be added to the JSON.
Definition qgis.h:4883
@ 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:5147
@ IdentifyFeature
WMS GML -> feature.
Definition qgis.h:5150
@ 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:2911
@ Antialiasing
Use antialiasing while drawing.
Definition qgis.h:2906
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Definition qgis.h:2916
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Definition qgis.h:2909
static const double DEFAULT_SEARCH_RADIUS_MM
Identify search radius in mm.
Definition qgis.h:6673
@ Container
A container.
Definition qgis.h:5938
@ Relation
A relation.
Definition qgis.h:5940
QFlags< LegendJsonRenderFlag > LegendJsonRenderFlags
Definition qgis.h:4886
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:6837
RasterIdentifyFormat
Raster identify formats.
Definition qgis.h:5119
@ Feature
WMS GML/JSON -> feature.
Definition qgis.h:5124
@ Value
Numerical pixel value.
Definition qgis.h:5121
@ 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:1844
@ AllowOverlapIfRequired
Avoids overlapping labels when possible, but permit overlaps if labels for features cannot otherwise ...
Definition qgis.h:1236
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2819
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
Definition qgis.h:2873
@ RecordProfile
Enable run-time profiling while rendering.
Definition qgis.h:2883
@ UseRenderingOptimization
Enable vector simplification and other rendering optimizations.
Definition qgis.h:2870
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Definition qgis.h:2876
@ DisableTiledRasterLayerRenders
If set, then raster layers will not be drawn as separate tiles. This may improve the appearance in ex...
Definition qgis.h:5533
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
Definition qgis.h:5537
@ DrawSelection
Draw selection.
Definition qgis.h:5532
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:480
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:51
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition qgslayout.h:121
Handles automatic layout and rendering of legends.
QSizeF minimumSize(QgsRenderContext *renderContext=nullptr)
Runs the layout algorithm and returns the minimum size required for the legend.
QJsonObject exportLegendToJson(const QgsRenderContext &context)
Renders the legend in a json object.
Q_DECL_DEPRECATED void drawLegend(QPainter *painter)
Draws the legend with given painter.
Stores the appearance and layout settings for legend drawing with QgsLegendRenderer.
Q_DECL_DEPRECATED void setMapScale(double scale)
Sets the legend map scale.
Q_DECL_DEPRECATED void setMapUnitsPerPixel(double mapUnitsPerPixel)
Sets the mmPerMapUnit calculated by mapUnitsPerPixel mostly taken from the map settings.
void setJsonRenderFlags(const Qgis::LegendJsonRenderFlags &jsonRenderFlags)
Sets the JSON export flags to jsonRenderFlags.
void setWmsLegendSize(QSizeF s)
Sets the desired size (in millimeters) of WMS legend graphics shown in the legend.
QStringList styles() const
Returns list of all defined style names.
bool setCurrentStyle(const QString &name)
Set a different style as the current style - will apply it to the layer.
bool addStyleFromLayer(const QString &name)
Add style by cloning the current one.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
virtual Q_INVOKABLE QgsRectangle extent() const
Returns the extent of the layer.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
QgsMapLayerServerProperties * serverProperties()
Returns QGIS Server Properties for the map layer.
Qgis::LayerType type
Definition qgsmaplayer.h:93
virtual void setOpacity(double opacity)
Sets the opacity for the layer, where opacity is a value between 0 (totally transparent) and 1....
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
virtual bool readSld(const QDomNode &node, QString &errorMessage)
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer's style manager.
virtual Q_INVOKABLE QgsDataProvider * dataProvider()
Returns the layer's data provider, it may be nullptr.
QString mapTipTemplate
Definition qgsmaplayer.h:96
QList< QgsMapRendererJob::Error > Errors
Contains configuration for rendering maps.
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer's CRS to output CRS
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in the map.
void setSelectionColor(const QColor &color)
Sets the color that is used for drawing of selected vector features.
void setLayers(const QList< QgsMapLayer * > &layers)
Sets the list of layers to render in the map.
double scale() const
Returns the calculated map scale.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer's CRS to destination CRS.
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer's CRS to output CRS
void setScaleMethod(Qgis::ScaleCalculationMethod method)
Sets the method to use for scale calculations for the map.
void setDpiTarget(double dpi)
Sets the target dpi (dots per inch) to be taken into consideration when rendering.
QStringList layerIds(bool expandGroupLayers=false) const
Returns the list of layer IDs which will be rendered in the map.
void setOutputDpi(double dpi)
Sets the dpi (dots per inch) used for conversion between real world units (e.g.
const QgsMapToPixel & mapToPixel() const
double mapUnitsPerPixel() const
Returns the distance in geographical coordinates that equals to one pixel in the map.
QSize outputSize() const
Returns the size of the resulting map image, in pixels.
QgsRectangle extent() const
Returns geographical coordinates of the rectangle that should be rendered.
void setExtent(const QgsRectangle &rect, bool magnified=true)
Sets the coordinates of the rectangle which should be rendered.
void setSelectiveMaskingSourceSets(const QVector< QgsSelectiveMaskingSourceSet > &sets)
Sets a list of all selective masking source sets defined for the map.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QColor selectionColor() const
Returns the color that is used for drawing of selected vector features.
void setExtentBuffer(double buffer)
Sets the buffer in map units to use around the visible extent for rendering symbols whose correspondi...
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets the global configuration of the labeling engine.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context, which stores various information regarding which datum transfo...
QString ellipsoid() const
Returns ellipsoid's acronym.
void setOutputSize(QSize size)
Sets the size of the resulting map image, in pixels.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
void setBackgroundColor(const QColor &color)
Sets the background color of the map.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
void setFlag(Qgis::MapSettingsFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs (coordinate reference system) for the map render.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
QList< QgsMapThemeCollection::MapThemeLayerRecord > layerRecords() const
Returns a list of records for all visible layer belonging to the theme.
QgsMapThemeCollection::MapThemeRecord mapThemeState(const QString &name) const
Returns the recorded state of a map theme.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
bool isTemporal() const
Returns whether the dataset group is temporal (contains time-related dataset).
bool isVector() const
Returns whether dataset group has vector data.
QString name() const
Returns name of the dataset group.
bool isScalar() const
Returns whether dataset group has scalar data.
QString uri() const
Returns the uri of the source.
double time() const
Returns the time value for this dataset.
double y() const
Returns y value.
double scalar() const
Returns magnitude of vector for vector data or scalar value for scalar data.
double x() const
Returns x value.
QgsMeshRendererSettings rendererSettings() const
Returns renderer settings.
QgsMeshDatasetIndex activeVectorDatasetAtTime(const QgsDateTimeRange &timeRange, int group=-1) const
Returns dataset index from active vector group depending on the time range If the temporal properties...
void updateTriangularMesh(const QgsCoordinateTransform &transform=QgsCoordinateTransform())
Gets native mesh and updates (creates if it doesn't exist) the base triangular mesh.
QgsMeshDatasetIndex staticVectorDatasetIndex(int group=-1) const
Returns the static vector dataset index that is rendered if the temporal properties is not active.
QList< int > enabledDatasetGroupsIndexes() const
Returns the list of indexes of enables dataset groups handled by the layer.
QgsMeshDatasetMetadata datasetMetadata(const QgsMeshDatasetIndex &index) const
Returns the dataset metadata.
QgsMeshDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
QgsMeshDatasetIndex datasetIndexAtTime(const QgsDateTimeRange &timeRange, int datasetGroupIndex) const
Returns dataset index from datasets group depending on the time range.
QgsMeshDatasetValue datasetValue(const QgsMeshDatasetIndex &index, int valueIndex) const
Returns vector/scalar value associated with the index from the dataset To read multiple continuous va...
QgsMapLayerTemporalProperties * temporalProperties() override
Returns the layer's temporal properties.
QgsMeshDatasetIndex activeScalarDatasetAtTime(const QgsDateTimeRange &timeRange, int group=-1) const
Returns dataset index from active scalar group depending on the time range.
QgsTriangularMesh * triangularMesh(double minimumTriangleSize=0) const
Returns triangular mesh (nullptr before rendering or calling to updateMesh).
QgsMeshDatasetIndex staticScalarDatasetIndex(int group=-1) const
Returns the static scalar dataset index that is rendered if the temporal properties is not active.
QString formatTime(double hours)
Returns (date) time in hours formatted to human readable form.
QgsMeshDatasetGroupMetadata datasetGroupMetadata(const QgsMeshDatasetIndex &index) const
Returns the dataset groups metadata.
int activeVectorDatasetGroup() const
Returns the active vector dataset group.
int activeScalarDatasetGroup() const
Returns the active scalar dataset group.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
static void applyAccessControlLayerFilters(const QgsAccessControl *accessControl, QgsMapLayer *mapLayer, QHash< QgsMapLayer *, QString > &originalLayerFilters)
Apply filter from AccessControl.
static QDomElement rectangleToGMLEnvelope(const QgsRectangle *env, QDomDocument &doc, int precision=17)
Exports the rectangle to GML3 Envelope.
static QDomElement geometryToGML(const QgsGeometry &geometry, QDomDocument &doc, QgsOgcUtils::GMLVersion gmlVersion, const QString &srsName, bool invertAxisOrientation, const QString &gmlIdBase, int precision=17)
Exports the geometry to GML.
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
static QDomElement rectangleToGMLBox(const QgsRectangle *box, QDomDocument &doc, int precision=17)
Exports the rectangle to GML2 Box.
const QgsLabelPlacementSettings & placementSettings() const
Returns the label placement settings.
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
Qgis::LabelPlacement placement
Label placement mode.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the label's property collection, used for data defined overrides.
int priority
Label priority.
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
double dist
Distance from feature to the label.
Property
Data definable properties.
@ PositionX
X-coordinate data defined label position.
@ PositionY
Y-coordinate data defined label position.
@ Vali
Vertical alignment for data defined label position (Bottom, Base, Half, Cap, Top).
@ Hali
Horizontal alignment for data defined label position (Left, Center, Right).
QString fieldName
Name of field (or an expression) to use for label text.
Represents a 2D point.
Definition qgspointxy.h:62
void setY(double y)
Sets the y value of the point.
Definition qgspointxy.h:132
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:122
Print layout, a QgsLayout subclass for static or atlas-based layouts.
QgsPrintLayout * clone() const override
Creates a clone of the layout.
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.
void setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
Scoped object for temporary scaling of a QgsRenderContext for millimeter based rendering.
static QgsDateTimeRange parseTemporalDateTimeInterval(const QString &interval)
Parses a datetime interval and returns a QgsDateTimeRange.
Exception base class for server exceptions.
static QString getExpressionFromServerFid(const QString &serverFid, const QgsVectorDataProvider *provider)
Returns the expression feature id based on primary keys.
static QgsFeatureRequest updateFeatureRequestFromServerFids(QgsFeatureRequest &featureRequest, const QStringList &serverFids, const QgsVectorDataProvider *provider)
Returns the feature request based on feature ids build with primary keys.
static QString getServerFid(const QgsFeature &feature, const QgsAttributeList &pkAttributes)
Returns the feature id based on primary keys.
static QString wmsFeatureInfoSchema(const QgsProject &project)
Returns the schema URL for XML GetFeatureInfo request.
static bool wmsInfoFormatSia2045(const QgsProject &project)
Returns if the info format is SIA20145.
static bool wmsHTMLFeatureInfoUseOnlyMaptip(const QgsProject &project)
Returns if only the maptip should be used for HTML feature info response so that the HTML response to...
static QString wmsFeatureInfoDocumentElementNs(const QgsProject &project)
Returns the document element namespace for XML GetFeatureInfo request.
static QStringList wmsRestrictedComposers(const QgsProject &project)
Returns the restricted composer list.
static bool wmsFeatureInfoSegmentizeWktGeometry(const QgsProject &project)
Returns if the geometry has to be segmentize in GetFeatureInfo request.
static bool wmsFeatureInfoUseAttributeFormSettings(const QgsProject &project)
Returns if feature form settings should be considered for the format of the feature info response.
static QHash< QString, QString > wmsFeatureInfoLayerAliasMap(const QgsProject &project)
Returns the mapping between layer name and wms layer name for GetFeatureInfo request.
static bool wmsFeatureInfoAddWktGeometry(const QgsProject &project)
Returns if the geometry is displayed as Well Known Text in GetFeatureInfo request.
static double wmsDefaultMapUnitsPerMm(const QgsProject &project)
Returns the default number of map units per millimeters in case of the scale is not given.
static QString wmsFeatureInfoDocumentElement(const QgsProject &project)
Returns the document element name for XML GetFeatureInfo request.
static int wmsMaxAtlasFeatures(const QgsProject &project)
Returns the maximum number of atlas features which can be printed in a request.
const QList< QgsServerWmsDimensionProperties::WmsDimensionInfo > wmsDimensions() const
Returns the QGIS Server WMS Dimension list.
static QString symbolProperties(QgsSymbol *symbol)
Returns a string representing the symbol.
@ Hidden
Hide task from GUI.
bool isActive() const
Returns true if the temporal property is active.
void setIsActive(bool active)
Sets whether the temporal property is active.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
void setIsTemporal(bool enabled)
Sets whether the temporal range is enabled (i.e.
void setTemporalRange(const QgsDateTimeRange &range)
Sets the temporal range for the object.
T begin() const
Returns the beginning of the range.
Definition qgsrange.h:408
T end() const
Returns the upper bound of the range.
Definition qgsrange.h:415
bool isInstant() const
Returns true if the range consists only of a single instant.
Definition qgsrange.h:437
void 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
Q_INVOKABLE QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const final
Calculates a list of unique values contained within an attribute in the layer.
Q_INVOKABLE QgsFeature getFeature(QgsFeatureId fid) const
Queries the layer for the feature with the given id.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
static Q_INVOKABLE QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static Q_INVOKABLE bool isCurvedType(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Implementation of legend node interface for displaying WMS legend entries.
Exception thrown in case of malformed request.
QgsRenderer(const QgsWmsRenderContext &context)
Constructor for QgsRenderer.
QHash< QgsVectorLayer *, SymbolSet > HitTest
QByteArray getPrint()
Returns printed page as binary.
HitTest symbols()
Returns the hit test according to the current context.
std::unique_ptr< QgsDxfExport > getDxf()
Returns the map as DXF data.
QSet< QString > SymbolSet
void configureLayers(QList< QgsMapLayer * > &layers, QgsMapSettings *settings=nullptr)
Configures layers for rendering optionally considering the map settings.
std::unique_ptr< QgsMapRendererTask > getPdf(const QString &tmpFileName)
Returns a configured pdf export task.
QByteArray getFeatureInfo(const QString &version="1.3.0")
Creates an xml document that describes the result of the getFeatureInfo request.
QImage * getLegendGraphics(QgsLayerTreeModel &model)
Returns the map legend as an image (or nullptr in case of error).
std::unique_ptr< QImage > getMap()
Returns the map as an image (or nullptr in case of error).
QJsonObject getLegendGraphicsAsJson(QgsLayerTreeModel &model, const Qgis::LegendJsonRenderFlags &jsonRenderFlags=Qgis::LegendJsonRenderFlags())
Returns the map legend as a JSON object.
Exception class for WMS service exceptions.
ExceptionCode
Exception codes as defined in OGC scpecifications for WMS 1.1.1 and WMS 1.3.0.
WMS parameter received from the client.
bool transparentAsBool() const
Returns TRANSPARENT parameter as a bool or its default value if not defined.
QString formatAsString() const
Returns FORMAT parameter as a string.
QgsWmsParametersComposerMap composerMapParameters(int mapId) const
Returns the requested parameters for a composer map parameter.
DxfFormatOption
Options for DXF format.
QColor backgroundColorAsColor() const
Returns BGCOLOR parameter as a QColor or its default value if not defined.
Format format() const
Returns format.
Format
Output format for the response.
Rendering context for the WMS renderer.
QList< QgsMapLayer * > layersToRender() const
Returns a list of all layers to actually render according to the current configuration.
void setScaleDenominator(double scaleDenominator)
Sets a custom scale denominator.
Median cut implementation.
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:7652
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:7026
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7651
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7108
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