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