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