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