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