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