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