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