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