QGIS API Documentation 3.39.0-Master (be2050b798e)
Loading...
Searching...
No Matches
qgsmaprendererjob.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaprendererjob.cpp
3 --------------------------------------
4 Date : December 2013
5 Copyright : (C) 2013 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsmaprendererjob.h"
17
18#include <QPainter>
19#include <QElapsedTimer>
20#include <QTimer>
21#include <QtConcurrentMap>
22
23#include <QPicture>
24
25#include "qgslogger.h"
26#include "qgsrendercontext.h"
27#include "qgsmaplayer.h"
28#include "qgsmaplayerrenderer.h"
29#include "qgsmaprenderercache.h"
30#include "qgsrasterlayer.h"
31#include "qgsmessagelog.h"
32#include "qgspallabeling.h"
33#include "qgsexception.h"
34#include "qgslabelingengine.h"
38#include "qgsvectorlayerutils.h"
41#include "qgsmaplayerstyle.h"
45#include "qgsrasterrenderer.h"
46#include "qgselevationmap.h"
48#include "qgssettingstree.h"
49#include "qgsruntimeprofiler.h"
50#include "qgsmeshlayer.h"
52#include "qgsgeos.h"
53
55const QgsSettingsEntryString *QgsMapRendererJob::settingsMaskBackend = new QgsSettingsEntryString( QStringLiteral( "mask-backend" ), QgsSettingsTree::sTreeMap, QString(), QStringLiteral( "Backend engine to use for selective masking" ) );
56
58
59const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );
60const QString QgsMapRendererJob::ELEVATION_MAP_CACHE_PREFIX = QStringLiteral( "_elevation_map_" );
61const QString QgsMapRendererJob::LABEL_PREVIEW_CACHE_ID = QStringLiteral( "_preview_labels_" );
62
63LayerRenderJob &LayerRenderJob::operator=( LayerRenderJob &&other )
64{
65 mContext = std::move( other.mContext );
66
67 img = other.img;
68 other.img = nullptr;
69
70 renderer = other.renderer;
71 other.renderer = nullptr;
72
73 previewRenderImage = other.previewRenderImage;
74 other.previewRenderImage = nullptr;
75
76 imageInitialized = other.imageInitialized;
77 previewRenderImageInitialized = other.previewRenderImageInitialized;
78
79 blendMode = other.blendMode;
80 opacity = other.opacity;
81 cached = other.cached;
82 layer = other.layer;
83 renderAboveLabels = other.renderAboveLabels;
84 completed = other.completed;
85 renderingTime = other.renderingTime;
86 estimatedRenderingTime = other.estimatedRenderingTime ;
87 errors = other.errors;
88 layerId = other.layerId;
89
90 maskPaintDevice = std::move( other.maskPaintDevice );
91
92 firstPassJob = other.firstPassJob;
93 other.firstPassJob = nullptr;
94
95 picture = std::move( other.picture );
96
97 maskJobs = other.maskJobs;
98
99 maskRequiresLayerRasterization = other.maskRequiresLayerRasterization;
100
101 elevationMap = other.elevationMap;
102 maskPainter = std::move( other.maskPainter );
103
104 return *this;
105}
106
107LayerRenderJob::LayerRenderJob( LayerRenderJob &&other )
108 : imageInitialized( other.imageInitialized )
109 , previewRenderImageInitialized( other.previewRenderImageInitialized )
110 , blendMode( other.blendMode )
111 , opacity( other.opacity )
112 , cached( other.cached )
113 , renderAboveLabels( other.renderAboveLabels )
114 , layer( other.layer )
115 , completed( other.completed )
116 , renderingTime( other.renderingTime )
117 , estimatedRenderingTime( other.estimatedRenderingTime )
118 , errors( other.errors )
119 , layerId( other.layerId )
120 , maskPainter( nullptr ) // should this be other.maskPainter??
121 , maskRequiresLayerRasterization( other.maskRequiresLayerRasterization )
122 , maskJobs( other.maskJobs )
123{
124 mContext = std::move( other.mContext );
125
126 img = other.img;
127 other.img = nullptr;
128
129 previewRenderImage = other.previewRenderImage;
130 other.previewRenderImage = nullptr;
131
132 renderer = other.renderer;
133 other.renderer = nullptr;
134
135 elevationMap = other.elevationMap;
136 other.elevationMap = nullptr;
137
138 maskPaintDevice = std::move( other.maskPaintDevice );
139
140 firstPassJob = other.firstPassJob;
141 other.firstPassJob = nullptr;
142
143 picture = std::move( other.picture );
144}
145
146bool LayerRenderJob::imageCanBeComposed() const
147{
148 if ( imageInitialized )
149 {
150 if ( renderer )
151 {
152 return renderer->isReadyToCompose();
153 }
154 else
155 {
156 return true;
157 }
158 }
159 else
160 {
161 return false;
162 }
163}
164
166 : mSettings( settings )
167 , mRenderedItemResults( std::make_unique< QgsRenderedItemResults >( settings.extent() ) )
168 , mLabelingEngineFeedback( new QgsLabelingEngineFeedback( this ) )
169{}
170
172
174{
176 startPrivate();
177 else
178 {
179 mErrors.append( QgsMapRendererJob::Error( QString(), tr( "Invalid map settings" ) ) );
180 emit finished();
181 }
182}
183
185{
187}
188
190{
191 return mRenderedItemResults.release();
192}
193
195 : QgsMapRendererJob( settings )
196{
197}
198
199
201{
202 return mErrors;
203}
204
206{
207 mCache = cache;
208}
209
211{
212 return mLabelingEngineFeedback;
213}
214
215QHash<QgsMapLayer *, int> QgsMapRendererJob::perLayerRenderingTime() const
216{
217 QHash<QgsMapLayer *, int> result;
218 for ( auto it = mPerLayerRenderingTime.constBegin(); it != mPerLayerRenderingTime.constEnd(); ++it )
219 {
220 if ( auto &&lKey = it.key() )
221 result.insert( lKey, it.value() );
222 }
223 return result;
224}
225
226void QgsMapRendererJob::setLayerRenderingTimeHints( const QHash<QString, int> &hints )
227{
229}
230
232{
233 return mSettings;
234}
235
237{
238 bool canCache = mCache;
239
240 // calculate which layers will be labeled
241 QSet< QgsMapLayer * > labeledLayers;
242 const QList<QgsMapLayer *> layers = mSettings.layers();
243 for ( QgsMapLayer *ml : layers )
244 {
246 labeledLayers << ml;
247
248 switch ( ml->type() )
249 {
251 {
252 QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( ml );
253 if ( vl->labelsEnabled() && vl->labeling()->requiresAdvancedEffects() )
254 {
255 canCache = false;
256 }
257 break;
258 }
259
261 {
262 QgsMeshLayer *l = qobject_cast< QgsMeshLayer *>( ml );
263 if ( l->labelsEnabled() && l->labeling()->requiresAdvancedEffects() )
264 {
265 canCache = false;
266 }
267 break;
268 }
269
271 {
272 // TODO -- add detection of advanced labeling effects for vector tile layers
273 break;
274 }
275
282 break;
283 }
284
285 if ( !canCache )
286 break;
287
288 }
289
291 {
292 // we may need to clear label cache and re-register labeled features - check for that here
293
294 // can we reuse the cached label solution?
295 const QList< QgsMapLayer * > labelDependentLayers = mCache->dependentLayers( LABEL_CACHE_ID );
296 bool canUseCache = canCache && QSet< QgsMapLayer * >( labelDependentLayers.begin(), labelDependentLayers.end() ) == labeledLayers;
297 if ( !canUseCache )
298 {
299 // no - participating layers have changed
301 }
302 }
303 return canCache;
304}
305
306
307bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 )
308{
309 bool res = true;
310 // we can safely use ballpark transforms without bothering the user here -- at the likely scale of layer extents there
311 // won't be an appreciable difference, and we aren't actually transforming any rendered points here anyway (just the layer extent)
312 QgsCoordinateTransform approxTransform = ct;
313 approxTransform.setBallparkTransformsAreAppropriate( true );
314
315 try
316 {
317#ifdef QGISDEBUG
318 // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
319#endif
320 // Split the extent into two if the source CRS is
321 // geographic and the extent crosses the split in
322 // geographic coordinates (usually +/- 180 degrees,
323 // and is assumed to be so here), and draw each
324 // extent separately.
325 static const double SPLIT_COORD = 180.0;
326
327 if ( ml->crs().isGeographic() )
328 {
329 if ( ml->type() == Qgis::LayerType::Vector && !approxTransform.destinationCrs().isGeographic() )
330 {
331 // if we transform from a projected coordinate system check
332 // check if transforming back roughly returns the input
333 // extend - otherwise render the world.
334 QgsRectangle extent1 = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
335 QgsRectangle extent2 = approxTransform.transformBoundingBox( extent1, Qgis::TransformDirection::Forward );
336
337 QgsDebugMsgLevel( QStringLiteral( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
338 .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
339 .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
340 .arg( std::fabs( 1.0 - extent2.width() / extent.width() ) )
341 .arg( std::fabs( 1.0 - extent2.height() / extent.height() ) )
342 , 3 );
343
344 // can differ by a maximum of up to 20% of height/width
345 if ( qgsDoubleNear( extent2.xMinimum(), extent.xMinimum(), extent.width() * 0.2 )
346 && qgsDoubleNear( extent2.xMaximum(), extent.xMaximum(), extent.width() * 0.2 )
347 && qgsDoubleNear( extent2.yMinimum(), extent.yMinimum(), extent.height() * 0.2 )
348 && qgsDoubleNear( extent2.yMaximum(), extent.yMaximum(), extent.height() * 0.2 )
349 )
350 {
351 extent = extent1;
352 }
353 else
354 {
355 extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
356 res = false;
357 }
358 }
359 else
360 {
361 // Note: ll = lower left point
362 QgsPointXY ll = approxTransform.transform( extent.xMinimum(), extent.yMinimum(),
364
365 // and ur = upper right point
366 QgsPointXY ur = approxTransform.transform( extent.xMaximum(), extent.yMaximum(),
368
369 QgsDebugMsgLevel( QStringLiteral( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ), 4 );
370
371 extent = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
372
373 QgsDebugMsgLevel( QStringLiteral( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ), 4 );
374
375 if ( ll.x() > ur.x() )
376 {
377 // the coordinates projected in reverse order than what one would expect.
378 // we are probably looking at an area that includes longitude of 180 degrees.
379 // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
380 // so let's use (-180,180). This hopefully does not add too much overhead. It is
381 // more straightforward than rendering with two separate extents and more consistent
382 // for rendering, labeling and caching as everything is rendered just in one go
383 extent.setXMinimum( -SPLIT_COORD );
384 extent.setXMaximum( SPLIT_COORD );
385 res = false;
386 }
387 }
388
389 // TODO: the above rule still does not help if using a projection that covers the whole
390 // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
391 // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
392 // but in fact the extent should cover the whole world.
393 }
394 else // can't cross 180
395 {
396 if ( approxTransform.destinationCrs().isGeographic() &&
397 ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
398 extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
399 // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
400 // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
401 // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
402 // but this seems like a safer choice.
403 {
404 extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
405 res = false;
406 }
407 else
408 extent = approxTransform.transformBoundingBox( extent, Qgis::TransformDirection::Reverse );
409 }
410 }
411 catch ( QgsCsException & )
412 {
413 QgsDebugError( QStringLiteral( "Transform error caught" ) );
414 extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
415 r2 = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
416 res = false;
417 }
418
419 return res;
420}
421
422QImage *QgsMapRendererJob::allocateImage( QString layerId )
423{
424 QImage *image = new QImage( mSettings.deviceOutputSize(),
426 image->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
427 image->setDotsPerMeterX( 1000 * mSettings.outputDpi() / 25.4 );
428 image->setDotsPerMeterY( 1000 * mSettings.outputDpi() / 25.4 );
429 if ( image->isNull() )
430 {
431 mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
432 delete image;
433 return nullptr;
434 }
435 return image;
436}
437
438QgsElevationMap *QgsMapRendererJob::allocateElevationMap( QString layerId )
439{
440 std::unique_ptr<QgsElevationMap> elevationMap = std::make_unique<QgsElevationMap>( mSettings.deviceOutputSize(), mSettings.devicePixelRatio() );
441 if ( !elevationMap->isValid() )
442 {
443 mErrors.append( Error( layerId, tr( "Insufficient memory for elevation map %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
444 return nullptr;
445 }
446 return elevationMap.release();
447}
448
449QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image, const QgsRenderContext *context )
450{
451 QPainter *painter = nullptr;
452 image = allocateImage( layerId );
453 if ( image )
454 {
455 painter = new QPainter( image );
456 context->setPainterFlagsUsingContext( painter );
457 }
458 return painter;
459}
460
461QgsMapRendererJob::PictureAndPainter QgsMapRendererJob::allocatePictureAndPainter( const QgsRenderContext *context )
462{
463 std::unique_ptr<QPicture> picture = std::make_unique<QPicture>();
464 QPainter *painter = new QPainter( picture.get() );
465 context->setPainterFlagsUsingContext( painter );
466 return { std::move( picture ), painter };
467}
468
469std::vector<LayerRenderJob> QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet )
470{
471 std::vector< LayerRenderJob > layerJobs;
472
473 // render all layers in the stack, starting at the base
474 QListIterator<QgsMapLayer *> li( mSettings.layers() );
475 li.toBack();
476
477 if ( mCache )
478 {
480 Q_UNUSED( cacheValid )
481 QgsDebugMsgLevel( QStringLiteral( "CACHE VALID: %1" ).arg( cacheValid ), 4 );
482 }
483
484 bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );
485
486 while ( li.hasPrevious() )
487 {
488 QgsMapLayer *ml = li.previous();
489
490 QgsDebugMsgLevel( QStringLiteral( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5 isValid:%6" )
491 .arg( ml->name() )
492 .arg( ml->minimumScale() )
493 .arg( ml->maximumScale() )
494 .arg( ml->hasScaleBasedVisibility() )
495 .arg( ml->blendMode() )
496 .arg( ml->isValid() )
497 , 3 );
498
499 if ( !ml->isValid() )
500 {
501 QgsDebugMsgLevel( QStringLiteral( "Invalid Layer skipped" ), 3 );
502 continue;
503 }
504
505 if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
506 {
507 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not within the defined visibility scale range" ), 3 );
508 continue;
509 }
510
512 {
513 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's time range" ), 3 );
514 continue;
515 }
516
518 {
519 QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's z range" ), 3 );
520 continue;
521 }
522
526
527 ct = mSettings.layerTransform( ml );
528 bool haveExtentInLayerCrs = true;
529 if ( ct.isValid() )
530 {
531 haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
532 }
533 QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
534 if ( !r1.isFinite() || !r2.isFinite() )
535 {
536 mErrors.append( Error( ml->id(), tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
537 continue;
538 }
539
540 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
541
542 // Force render of layers that are being edited
543 // or if there's a labeling engine that needs the layer to register features
544 if ( mCache )
545 {
546 const bool requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( ml ) ) && requiresLabelRedraw;
547 if ( ( vl && vl->isEditable() ) || requiresLabeling )
548 {
549 mCache->clearCacheImage( ml->id() );
550 }
551 }
552
553 layerJobs.emplace_back( LayerRenderJob() );
554 LayerRenderJob &job = layerJobs.back();
555 job.layer = ml;
556 job.layerId = ml->id();
557 job.renderAboveLabels = ml->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool();
558 job.estimatedRenderingTime = mLayerRenderingTimeHints.value( ml->id(), 0 );
559
560 job.setContext( std::make_unique< QgsRenderContext >( QgsRenderContext::fromMapSettings( mSettings ) ) );
561 if ( !ml->customProperty( QStringLiteral( "_noset_layer_expression_context" ) ).toBool() )
562 job.context()->expressionContext().appendScope( QgsExpressionContextUtils::layerScope( ml ) );
563 job.context()->setPainter( painter );
564 job.context()->setLabelingEngine( labelingEngine2 );
565 job.context()->setLabelSink( labelSink() );
566 job.context()->setCoordinateTransform( ct );
567 job.context()->setExtent( r1 );
568
569 // Also check geographic, see: https://github.com/qgis/QGIS/issues/45200
570 if ( !haveExtentInLayerCrs || ( ct.isValid() && ( ct.sourceCrs().isGeographic() != ct.destinationCrs().isGeographic() ) ) )
571 job.context()->setFlag( Qgis::RenderContextFlag::ApplyClipAfterReprojection, true );
572
573 if ( mFeatureFilterProvider )
574 job.context()->setFeatureFilterProvider( mFeatureFilterProvider );
575
576 QgsMapLayerStyleOverride styleOverride( ml );
577 if ( mSettings.layerStyleOverrides().contains( ml->id() ) )
578 styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
579
580 job.blendMode = ml->blendMode();
581
582 if ( ml->type() == Qgis::LayerType::Raster )
583 {
584 // raster layers are abnormal wrt opacity handling -- opacity is sometimes handled directly within the raster layer renderer
585 QgsRasterLayer *rl = qobject_cast< QgsRasterLayer * >( ml );
587 {
588 job.opacity = 1.0;
589 }
590 else
591 {
592 job.opacity = ml->opacity();
593 }
594 }
595 else
596 {
597 job.opacity = ml->opacity();
598 }
599
601
602 // if we can use the cache, let's do it and avoid rendering!
604 if ( canUseCache && mCache->hasCacheImage( ml->id() ) )
605 {
606 job.cached = true;
607 job.imageInitialized = true;
608 job.img = new QImage( mCache->cacheImage( ml->id() ) );
609 if ( shadingRenderer.isActive() &&
610 ml->elevationProperties() &&
613 job.elevationMap = new QgsElevationMap( mCache->cacheImage( ELEVATION_MAP_CACHE_PREFIX + ml->id() ) );
614 job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
615 job.renderer = nullptr;
616 job.context()->setPainter( nullptr );
617 mLayersRedrawnFromCache.append( ml->id() );
618 continue;
619 }
620
621 QElapsedTimer layerTime;
622 layerTime.start();
623 job.renderer = ml->createMapRenderer( *( job.context() ) );
624 if ( job.renderer )
625 {
626 job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
627 job.context()->setFeedback( job.renderer->feedback() );
628 }
629
630 // If we are drawing with an alternative blending mode then we need to render to a separate image
631 // before compositing this on the map. This effectively flattens the layer and prevents
632 // blending occurring between objects on the layer
633 if ( canUseCache || ( !painter && !deferredPainterSet ) || ( job.renderer && job.renderer->forceRasterRender() ) )
634 {
635 // Flattened image for drawing when a blending mode is set
636 job.context()->setPainter( allocateImageAndPainter( ml->id(), job.img, job.context() ) );
637 if ( ! job.img )
638 {
639 delete job.renderer;
640 job.renderer = nullptr;
641 layerJobs.pop_back();
642 continue;
643 }
644 }
645
646 if ( shadingRenderer.isActive()
647 && ml->elevationProperties()
649 {
650 job.elevationMap = allocateElevationMap( ml->id() );
651 job.context()->setElevationMap( job.elevationMap );
652 }
653
655 {
656 if ( canUseCache && ( job.renderer->flags() & Qgis::MapLayerRendererFlag::RenderPartialOutputOverPreviousCachedImage ) && mCache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
657 {
658 const QImage cachedImage = mCache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), mSettings.mapToPixel() );
659 if ( !cachedImage.isNull() )
660 {
661 job.previewRenderImage = new QImage( cachedImage );
662 job.previewRenderImageInitialized = true;
663 job.context()->setPreviewRenderPainter( new QPainter( job.previewRenderImage ) );
664 job.context()->setPainterFlagsUsingContext( painter );
665 }
666 }
667 if ( !job.previewRenderImage )
668 {
669 job.context()->setPreviewRenderPainter( allocateImageAndPainter( ml->id(), job.previewRenderImage, job.context() ) );
670 job.previewRenderImageInitialized = false;
671 }
672
673 if ( !job.previewRenderImage )
674 {
675 delete job.context()->previewRenderPainter();
676 job.context()->setPreviewRenderPainter( nullptr );
677 }
678 }
679
680 job.renderingTime = layerTime.elapsed(); // include job preparation time in layer rendering time
681 }
682
683 return layerJobs;
684}
685
686std::vector< LayerRenderJob > QgsMapRendererJob::prepareSecondPassJobs( std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob )
687{
688 std::vector< LayerRenderJob > secondPassJobs;
689
690 // We will need to quickly access the associated rendering job of a layer
691 QHash<QString, LayerRenderJob *> layerJobMapping;
692
693 // ... and layer that contains a mask (and whether there is effects implied or not)
694 QMap<QString, bool> maskLayerHasEffects;
695 QMap<int, bool> labelHasEffects;
696
697 struct MaskSource
698 {
699 QString layerId;
700 QString labelRuleId;
701 int labelMaskId;
702 bool hasEffects;
703 MaskSource( const QString &layerId_, const QString &labelRuleId_, int labelMaskId_, bool hasEffects_ ):
704 layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ), hasEffects( hasEffects_ ) {}
705 };
706
707 // We collect for each layer, the set of symbol layers that will be "masked"
708 // and the list of source layers that have a mask
709 QHash<QString, QPair<QSet<QString>, QList<MaskSource>>> maskedSymbolLayers;
710
713
714 // First up, create a mapping of layer id to jobs. We need this to filter out any masking
715 // which refers to layers which we aren't rendering as part of this map render
716 for ( LayerRenderJob &job : firstPassJobs )
717 {
718 layerJobMapping[job.layerId] = &job;
719 }
720
721 // next, collate a master list of masked layers, skipping over any which refer to layers
722 // which don't have a corresponding render job
723 for ( LayerRenderJob &job : firstPassJobs )
724 {
725 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
726 if ( ! vl )
727 continue;
728
729 // lambda function to factor code for both label masks and symbol layer masks
730 auto collectMasks = [&]( QgsMaskedLayers * masks, QString sourceLayerId, QString ruleId = QString(), int labelMaskId = -1 )
731 {
732 bool hasEffects = false;
733 for ( auto it = masks->begin(); it != masks->end(); ++it )
734 {
735 auto lit = maskedSymbolLayers.find( it.key() );
736 if ( lit == maskedSymbolLayers.end() )
737 {
738 maskedSymbolLayers[it.key()] = qMakePair( it.value().symbolLayerIds, QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId, it.value().hasEffects ) );
739 }
740 else
741 {
742 if ( lit->first != it.value().symbolLayerIds )
743 {
744 QgsLogger::warning( QStringLiteral( "Layer %1 : Different sets of symbol layers are masked by different sources ! Only one (arbitrary) set will be retained !" ).arg( it.key() ) );
745 continue;
746 }
747 lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId, hasEffects ) );
748 }
749 hasEffects |= it.value().hasEffects;
750 }
751 if ( ! masks->isEmpty() && labelMaskId == -1 )
752 maskLayerHasEffects[ sourceLayerId ] = hasEffects;
753 };
754
755 // collect label masks
756 QHash<QString, QgsMaskedLayers> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
757 for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
758 {
759 QString labelRule = it.key();
760 // this is a hash of layer id to masks
761 QgsMaskedLayers masks = it.value();
762
763 // filter out masks to those which we are actually rendering
764 QgsMaskedLayers usableMasks;
765 for ( auto mit = masks.begin(); mit != masks.end(); mit++ )
766 {
767 const QString sourceLayerId = mit.key();
768 // if we aren't rendering the source layer as part of this render, we can't process this mask
769 if ( !layerJobMapping.contains( sourceLayerId ) )
770 continue;
771 else
772 usableMasks.insert( sourceLayerId, mit.value() );
773 }
774
775 if ( usableMasks.empty() )
776 continue;
777
778 // group layers by QSet<QgsSymbolLayerReference>
779 QSet<QgsSymbolLayerReference> slRefs;
780 bool hasEffects = false;
781 for ( auto mit = usableMasks.begin(); mit != usableMasks.end(); mit++ )
782 {
783 const QString sourceLayerId = mit.key();
784 // if we aren't rendering the source layer as part of this render, we can't process this mask
785 if ( !layerJobMapping.contains( sourceLayerId ) )
786 continue;
787
788 for ( const QString &symbolLayerId : mit.value().symbolLayerIds )
789 slRefs.insert( QgsSymbolLayerReference( sourceLayerId, symbolLayerId ) );
790
791 hasEffects |= mit.value().hasEffects;
792 }
793 // generate a new mask id for this set
794 int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->id(), it.key(), slRefs );
795 labelHasEffects[ labelMaskId ] = hasEffects;
796
797 // now collect masks
798 collectMasks( &usableMasks, vl->id(), labelRule, labelMaskId );
799 }
800
801 // collect symbol layer masks
803 collectMasks( &symbolLayerMasks, vl->id() );
804 }
805
806 if ( maskedSymbolLayers.isEmpty() )
807 return secondPassJobs;
808
809 // Prepare label mask images
810 for ( int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
811 {
812 QPaintDevice *maskPaintDevice = nullptr;
813 QPainter *maskPainter = nullptr;
814 if ( forceVector && !labelHasEffects[ maskId ] )
815 {
816 // set a painter to get all masking instruction in order to later clip masked symbol layer
817 std::unique_ptr< QgsGeometryPaintDevice > geomPaintDevice = std::make_unique< QgsGeometryPaintDevice >( true );
818 geomPaintDevice->setStrokedPathSegments( 4 );
819 geomPaintDevice->setSimplificationTolerance( labelJob.context.maskSettings().simplifyTolerance() );
820 maskPaintDevice = geomPaintDevice.release();
821 maskPainter = new QPainter( maskPaintDevice );
822 }
823 else
824 {
825 // Note: we only need an alpha channel here, rather than a full RGBA image
826 QImage *maskImage = nullptr;
827 maskPainter = allocateImageAndPainter( QStringLiteral( "label mask" ), maskImage, &labelJob.context );
828 maskImage->fill( 0 );
829 maskPaintDevice = maskImage;
830 }
831
832 labelJob.context.setMaskPainter( maskPainter, maskId );
833 labelJob.maskPainters.push_back( std::unique_ptr<QPainter>( maskPainter ) );
834 labelJob.maskPaintDevices.push_back( std::unique_ptr<QPaintDevice>( maskPaintDevice ) );
835 }
836 labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
837
838 // Prepare second pass jobs
839 // - For raster rendering or vector rendering if effects are involved
840 // 1st pass, 2nd pass and mask are rendered in QImage and composed in composeSecondPass
841 // - For vector rendering if no effects are involved
842 // 1st pass is rendered in QImage, clip paths are generated according to mask and used during
843 // masked symbol layer rendering during second pass, which is rendered in QPicture, second
844 // pass job picture
845
846 // Allocate an image for labels
847 if ( !labelJob.img && !forceVector )
848 {
849 labelJob.img = allocateImage( QStringLiteral( "labels" ) );
850 }
851 else if ( !labelJob.picture && forceVector )
852 {
853 labelJob.picture.reset( new QPicture() );
854 }
855
856 // first we initialize painter and mask painter for all jobs
857 for ( LayerRenderJob &job : firstPassJobs )
858 {
859 job.maskRequiresLayerRasterization = false;
860
861 auto it = maskedSymbolLayers.find( job.layerId );
862 if ( it != maskedSymbolLayers.end() )
863 {
864 const QList<MaskSource> &sourceList = it->second;
865 for ( const MaskSource &source : sourceList )
866 {
867 job.maskRequiresLayerRasterization |= source.hasEffects;
868 }
869 }
870
871 // update first pass job painter and device if needed
872 const bool isRasterRendering = !forceVector || job.maskRequiresLayerRasterization || ( job.renderer && job.renderer->forceRasterRender() );
873 if ( isRasterRendering && !job.img )
874 {
875 job.context()->setPainter( allocateImageAndPainter( job.layerId, job.img, job.context() ) );
876 }
877 else if ( !isRasterRendering && !job.picture )
878 {
879 PictureAndPainter pictureAndPainter = allocatePictureAndPainter( job.context() );
880 job.picture = std::move( pictureAndPainter.first );
881 if ( job.context()->painter()->hasClipping() )
882 {
883 // need to copy clipping paths from original painter, so that e.g. the layout map bounds clipping path is respected
884 pictureAndPainter.second->setClipping( true );
885 pictureAndPainter.second->setClipPath( job.context()->painter()->clipPath() );
886 }
887 job.context()->setPainter( pictureAndPainter.second );
888 // force recreation of layer renderer so it initialize correctly the renderer
889 // especially the RasterLayerRender that need logicalDpiX from painting device
890 job.renderer = job.layer->createMapRenderer( *( job.context() ) );
891 }
892
893 // for layer that mask, generate mask in first pass job
894 if ( maskLayerHasEffects.contains( job.layerId ) )
895 {
896 QPaintDevice *maskPaintDevice = nullptr;
897 QPainter *maskPainter = nullptr;
898 if ( forceVector && !maskLayerHasEffects[ job.layerId ] )
899 {
900 // set a painter to get all masking instruction in order to later clip masked symbol layer
901 std::unique_ptr< QgsGeometryPaintDevice > geomPaintDevice = std::make_unique< QgsGeometryPaintDevice >( );
902 geomPaintDevice->setStrokedPathSegments( 4 );
903 geomPaintDevice->setSimplificationTolerance( job.context()->maskSettings().simplifyTolerance() );
904 maskPaintDevice = geomPaintDevice.release();
905 maskPainter = new QPainter( maskPaintDevice );
906 }
907 else
908 {
909 // Note: we only need an alpha channel here, rather than a full RGBA image
910 QImage *maskImage = nullptr;
911 maskPainter = allocateImageAndPainter( job.layerId, maskImage, job.context() );
912 maskImage->fill( 0 );
913 maskPaintDevice = maskImage;
914 }
915
916 job.context()->setMaskPainter( maskPainter );
917 job.maskPainter.reset( maskPainter );
918 job.maskPaintDevice.reset( maskPaintDevice );
919 }
920 }
921
922 for ( LayerRenderJob &job : firstPassJobs )
923 {
924 QgsMapLayer *ml = job.layer;
925
926 auto it = maskedSymbolLayers.find( job.layerId );
927 if ( it == maskedSymbolLayers.end() )
928 continue;
929
930 QList<MaskSource> &sourceList = it->second;
931 const QSet<QString> symbolList = it->first;
932
933 secondPassJobs.emplace_back( LayerRenderJob() );
934 LayerRenderJob &job2 = secondPassJobs.back();
935
936 job2.maskRequiresLayerRasterization = job.maskRequiresLayerRasterization;
937
938 // Points to the masking jobs. This will be needed during the second pass composition.
939 for ( MaskSource &source : sourceList )
940 {
941 if ( source.labelMaskId != -1 )
942 job2.maskJobs.push_back( qMakePair( nullptr, source.labelMaskId ) );
943 else
944 job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
945 }
946
947 // copy the context from the initial job
948 job2.setContext( std::make_unique< QgsRenderContext >( *job.context() ) );
949 // also assign layer to match initial job
950 job2.layer = job.layer;
951 job2.renderAboveLabels = job.renderAboveLabels;
952 job2.layerId = job.layerId;
953
954 // associate first pass job with second pass job
955 job2.firstPassJob = &job;
956
957 if ( !forceVector || job2.maskRequiresLayerRasterization )
958 {
959 job2.context()->setPainter( allocateImageAndPainter( job.layerId, job2.img, job2.context() ) );
960 }
961 else
962 {
963 PictureAndPainter pictureAndPainter = allocatePictureAndPainter( job2.context() );
964 if ( job.context()->painter()->hasClipping() )
965 {
966 // need to copy clipping paths from original painter, so that e.g. the layout map bounds clipping path is respected
967 pictureAndPainter.second->setClipping( true );
968 pictureAndPainter.second->setClipPath( job.context()->painter()->clipPath() );
969 }
970 job2.picture = std::move( pictureAndPainter.first );
971 job2.context()->setPainter( pictureAndPainter.second );
972 }
973
974 if ( ! job2.img && ! job2.picture )
975 {
976 secondPassJobs.pop_back();
977 continue;
978 }
979
980 // FIXME: another possibility here, to avoid allocating a new map renderer and reuse the one from
981 // the first pass job, would be to be able to call QgsMapLayerRenderer::render() with a QgsRenderContext.
982 QgsVectorLayerRenderer *mapRenderer = static_cast<QgsVectorLayerRenderer *>( ml->createMapRenderer( *job2.context() ) );
983 job2.renderer = mapRenderer;
984 if ( job2.renderer )
985 {
986 job2.context()->setFeedback( job2.renderer->feedback() );
987 }
988
989 // Render only the non masked symbol layer and we will compose 2nd pass with mask and first pass rendering in composeSecondPass
990 // If vector output is enabled, disabled symbol layers would be actually rendered and masked with clipping path set in QgsMapRendererJob::initSecondPassJobs
991 job2.context()->setDisabledSymbolLayersV2( symbolList );
992 }
993
994 return secondPassJobs;
995}
996
997void QgsMapRendererJob::initSecondPassJobs( std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob ) const
998{
1000 return;
1001
1002 for ( LayerRenderJob &job : secondPassJobs )
1003 {
1004 if ( job.maskRequiresLayerRasterization )
1005 continue;
1006
1007 // we draw disabled symbol layer but me mask them with clipping path produced during first pass job
1008 // Resulting 2nd pass job picture will be the final rendering
1009
1010 for ( const QPair<LayerRenderJob *, int> &p : std::as_const( job.maskJobs ) )
1011 {
1012 QPainter *maskPainter = p.first ? p.first->maskPainter.get() : labelJob.maskPainters[p.second].get();
1013
1014 const QSet<QString> layers = job.context()->disabledSymbolLayersV2();
1015 if ( QgsGeometryPaintDevice *geometryDevice = dynamic_cast<QgsGeometryPaintDevice *>( maskPainter->device() ) )
1016 {
1017 QgsGeometry geometry( geometryDevice->geometry().clone() );
1018
1019#if GEOS_VERSION_MAJOR==3 && GEOS_VERSION_MINOR<10
1020 // structure would be better, but too old GEOS
1021 geometry = geometry.makeValid( Qgis::MakeValidMethod::Linework );
1022#else
1023 geometry = geometry.makeValid( Qgis::MakeValidMethod::Structure );
1024#endif
1025
1026 for ( const QString &symbolLayerId : layers )
1027 {
1028 job.context()->addSymbolLayerClipGeometry( symbolLayerId, geometry );
1029 }
1030 }
1031 }
1032
1033 job.context()->setDisabledSymbolLayersV2( QSet<QString>() );
1034 }
1035}
1036
1037LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache )
1038{
1039 LabelRenderJob job;
1041 job.context.setPainter( painter );
1042 job.context.setLabelingEngine( labelingEngine2 );
1043 job.context.setFeedback( mLabelingEngineFeedback );
1044
1046 r1.grow( mSettings.extentBuffer() );
1047 job.context.setExtent( r1 );
1048
1049 job.context.setFeatureFilterProvider( mFeatureFilterProvider );
1052 job.context.setCoordinateTransform( ct );
1053
1054 // no cache, no image allocation
1056 return job;
1057
1058 // if we can use the cache, let's do it and avoid rendering!
1059 bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
1060 if ( hasCache )
1061 {
1062 job.cached = true;
1063 job.complete = true;
1064 job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
1065 Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
1066 job.context.setPainter( nullptr );
1067 }
1068 else
1069 {
1070 if ( canUseLabelCache && ( mCache || !painter ) )
1071 {
1072 job.img = allocateImage( QStringLiteral( "labels" ) );
1073 }
1074 }
1075
1076 return job;
1077}
1078
1079
1080void QgsMapRendererJob::cleanupJobs( std::vector<LayerRenderJob> &jobs )
1081{
1082 for ( LayerRenderJob &job : jobs )
1083 {
1084 if ( job.img )
1085 {
1086 delete job.context()->painter();
1087 job.context()->setPainter( nullptr );
1088
1089 if ( mCache && !job.cached && job.completed && job.layer )
1090 {
1091 QgsDebugMsgLevel( QStringLiteral( "caching image for %1" ).arg( job.layerId ), 2 );
1092 mCache->setCacheImageWithParameters( job.layerId, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
1093 mCache->setCacheImageWithParameters( job.layerId + QStringLiteral( "_preview" ), *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
1094 }
1095
1096 delete job.img;
1097 job.img = nullptr;
1098 }
1099
1100 if ( job.previewRenderImage )
1101 {
1102 delete job.context()->previewRenderPainter();
1103 job.context()->setPreviewRenderPainter( nullptr );
1104 delete job.previewRenderImage;
1105 job.previewRenderImage = nullptr;
1106 }
1107
1108 if ( job.elevationMap )
1109 {
1110 job.context()->setElevationMap( nullptr );
1111 if ( mCache && !job.cached && job.completed && job.layer )
1112 {
1113 QgsDebugMsgLevel( QStringLiteral( "caching elevation map for %1" ).arg( job.layerId ), 2 );
1115 ELEVATION_MAP_CACHE_PREFIX + job.layerId,
1116 job.elevationMap->rawElevationImage(),
1119 QList< QgsMapLayer * >() << job.layer );
1121 ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ),
1122 job.elevationMap->rawElevationImage(),
1125 QList< QgsMapLayer * >() << job.layer );
1126 }
1127
1128 delete job.elevationMap;
1129 job.elevationMap = nullptr;
1130 }
1131
1132 if ( job.picture )
1133 {
1134 delete job.context()->painter();
1135 job.context()->setPainter( nullptr );
1136 job.picture.reset( nullptr );
1137 }
1138
1139 if ( job.renderer )
1140 {
1141 const QStringList errors = job.renderer->errors();
1142 for ( const QString &message : errors )
1143 mErrors.append( Error( job.renderer->layerId(), message ) );
1144
1145 mRenderedItemResults->appendResults( job.renderer->takeRenderedItemDetails(), *job.context() );
1146
1147 delete job.renderer;
1148 job.renderer = nullptr;
1149 }
1150
1151 if ( job.layer )
1152 mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
1153
1154 job.maskPainter.reset( nullptr );
1155 job.maskPaintDevice.reset( nullptr );
1156 }
1157
1158 jobs.clear();
1159}
1160
1161void QgsMapRendererJob::cleanupSecondPassJobs( std::vector< LayerRenderJob > &jobs )
1162{
1163 for ( LayerRenderJob &job : jobs )
1164 {
1165 if ( job.img )
1166 {
1167 delete job.context()->painter();
1168 job.context()->setPainter( nullptr );
1169
1170 delete job.img;
1171 job.img = nullptr;
1172 }
1173
1174 if ( job.previewRenderImage )
1175 {
1176 delete job.context()->previewRenderPainter();
1177 job.context()->setPreviewRenderPainter( nullptr );
1178 delete job.previewRenderImage;
1179 job.previewRenderImage = nullptr;
1180 }
1181
1182 if ( job.picture )
1183 {
1184 delete job.context()->painter();
1185 job.context()->setPainter( nullptr );
1186 }
1187
1188 if ( job.renderer )
1189 {
1190 delete job.renderer;
1191 job.renderer = nullptr;
1192 }
1193
1194 if ( job.layer )
1195 mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
1196 }
1197
1198 jobs.clear();
1199}
1200
1201void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )
1202{
1203 if ( job.img )
1204 {
1205 if ( mCache && !job.cached && !job.context.renderingStopped() )
1206 {
1207 QgsDebugMsgLevel( QStringLiteral( "caching label result image" ), 2 );
1208 mCache->setCacheImageWithParameters( LABEL_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
1209 mCache->setCacheImageWithParameters( LABEL_PREVIEW_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
1210 }
1211
1212 delete job.img;
1213 job.img = nullptr;
1214 }
1215
1216 job.picture.reset( nullptr );
1217 job.maskPainters.clear();
1218 job.maskPaintDevices.clear();
1219}
1220
1221
1222#define DEBUG_RENDERING 0
1223
1224QImage QgsMapRendererJob::composeImage( const QgsMapSettings &settings,
1225 const std::vector<LayerRenderJob> &jobs,
1226 const LabelRenderJob &labelJob,
1227 const QgsMapRendererCache *cache
1228 )
1229{
1230 QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
1231 image.setDevicePixelRatio( settings.devicePixelRatio() );
1232 image.setDotsPerMeterX( static_cast<int>( settings.outputDpi() * 39.37 ) );
1233 image.setDotsPerMeterY( static_cast<int>( settings.outputDpi() * 39.37 ) );
1234 image.fill( settings.backgroundColor().rgba() );
1235
1236 const QgsElevationShadingRenderer mapShadingRenderer = settings.elevationShadingRenderer();
1237 std::unique_ptr<QgsElevationMap> mainElevationMap;
1238 if ( mapShadingRenderer.isActive() )
1239 mainElevationMap.reset( new QgsElevationMap( settings.deviceOutputSize(), settings.devicePixelRatio() ) );
1240
1241 QPainter painter( &image );
1242
1243#if DEBUG_RENDERING
1244 int i = 0;
1245#endif
1246 for ( const LayerRenderJob &job : jobs )
1247 {
1248 if ( job.renderAboveLabels )
1249 continue; // skip layer for now, it will be rendered after labels
1250
1251 QImage img = layerImageToBeComposed( settings, job, cache );
1252 if ( img.isNull() )
1253 continue; // image is not prepared and not even in cache
1254
1255 painter.setCompositionMode( job.blendMode );
1256 painter.setOpacity( job.opacity );
1257
1258 if ( mainElevationMap )
1259 {
1260 QgsElevationMap layerElevationMap = layerElevationToBeComposed( settings, job, cache );
1261 if ( layerElevationMap.isValid() )
1262 mainElevationMap->combine( layerElevationMap, mapShadingRenderer.combinedElevationMethod() );
1263 }
1264
1265
1266#if DEBUG_RENDERING
1267 img.save( QString( "/tmp/final_%1.png" ).arg( i ) );
1268 i++;
1269#endif
1270
1271 painter.drawImage( 0, 0, img );
1272 }
1273
1274 if ( mapShadingRenderer.isActive() && mainElevationMap )
1275 {
1276 mapShadingRenderer.renderShading( *mainElevationMap.get(), image, QgsRenderContext::fromMapSettings( settings ) );
1277 }
1278
1279 // IMPORTANT - don't draw labelJob img before the label job is complete,
1280 // as the image is uninitialized and full of garbage before the label job
1281 // commences
1282 if ( labelJob.img && labelJob.complete )
1283 {
1284 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1285 painter.setOpacity( 1.0 );
1286 painter.drawImage( 0, 0, *labelJob.img );
1287 }
1288 // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
1289 // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
1290 // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
1291 else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
1292 {
1293 const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
1294 painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
1295 painter.setOpacity( 1.0 );
1296 painter.drawImage( 0, 0, labelCacheImage );
1297 }
1298
1299 // render any layers with the renderAboveLabels flag now
1300 for ( const LayerRenderJob &job : jobs )
1301 {
1302 if ( !job.renderAboveLabels )
1303 continue;
1304
1305 QImage img = layerImageToBeComposed( settings, job, cache );
1306 if ( img.isNull() )
1307 continue; // image is not prepared and not even in cache
1308
1309 painter.setCompositionMode( job.blendMode );
1310 painter.setOpacity( job.opacity );
1311
1312 painter.drawImage( 0, 0, img );
1313 }
1314
1315 painter.end();
1316#if DEBUG_RENDERING
1317 image.save( "/tmp/final.png" );
1318#endif
1319 return image;
1320}
1321
1323 const QgsMapSettings &settings,
1324 const LayerRenderJob &job,
1325 const QgsMapRendererCache *cache
1326)
1327{
1328 if ( job.imageCanBeComposed() )
1329 {
1330 if ( job.previewRenderImage && !job.completed )
1331 return *job.previewRenderImage;
1332
1333 Q_ASSERT( job.img );
1334 return *job.img;
1335 }
1336 else
1337 {
1338 if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
1339 {
1340 return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
1341 }
1342 else
1343 return QImage();
1344 }
1345}
1346
1347QgsElevationMap QgsMapRendererJob::layerElevationToBeComposed( const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache )
1348{
1349 if ( job.imageCanBeComposed() && job.elevationMap )
1350 {
1351 return *job.elevationMap;
1352 }
1353 else
1354 {
1355 if ( cache && cache->hasAnyCacheImage( ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ) ) )
1356 return QgsElevationMap( cache->transformedCacheImage( ELEVATION_MAP_CACHE_PREFIX + job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() ) );
1357 else
1358 return QgsElevationMap();
1359 }
1360}
1361
1362void QgsMapRendererJob::composeSecondPass( std::vector<LayerRenderJob> &secondPassJobs, LabelRenderJob &labelJob, bool forceVector )
1363{
1364 // compose the second pass with the mask
1365 for ( LayerRenderJob &job : secondPassJobs )
1366 {
1367 const bool isRasterRendering = !forceVector || job.maskRequiresLayerRasterization;
1368
1369 // Merge all mask images into the first one if we have more than one mask image
1370 if ( isRasterRendering && job.maskJobs.size() > 1 )
1371 {
1372 QPainter *maskPainter = nullptr;
1373 for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
1374 {
1375 QImage *maskImage = static_cast<QImage *>( p.first ? p.first->maskPaintDevice.get() : labelJob.maskPaintDevices[p.second].get() );
1376 if ( !maskPainter )
1377 {
1378 maskPainter = p.first ? p.first->maskPainter.get() : labelJob.maskPainters[ p.second ].get();
1379 }
1380 else
1381 {
1382 maskPainter->drawImage( 0, 0, *maskImage );
1383 }
1384 }
1385 }
1386
1387 if ( ! job.maskJobs.isEmpty() )
1388 {
1389 // All have been merged into the first
1390 QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
1391 if ( isRasterRendering )
1392 {
1393 QImage *maskImage = static_cast<QImage *>( p.first ? p.first->maskPaintDevice.get() : labelJob.maskPaintDevices[p.second].get() );
1394
1395 // Only retain parts of the second rendering that are "inside" the mask image
1396 QPainter *painter = job.context()->painter();
1397
1398 painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
1399
1400 //Create an "alpha binarized" image of the maskImage to :
1401 //* Eliminate antialiasing artifact
1402 //* Avoid applying mask opacity to elements under the mask but not masked
1403 QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
1404 QVector<QRgb> mswTable;
1405 mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
1406 mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
1407 maskBinAlpha.setColorTable( mswTable );
1408 painter->drawImage( 0, 0, maskBinAlpha );
1409
1410 // Modify the first pass' image ...
1411 {
1412 QPainter tempPainter;
1413
1414 // reuse the first pass painter, if available
1415 QPainter *painter1 = job.firstPassJob->context()->painter();
1416 if ( ! painter1 )
1417 {
1418 tempPainter.begin( job.firstPassJob->img );
1419 painter1 = &tempPainter;
1420 }
1421
1422 // ... first retain parts that are "outside" the mask image
1423 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
1424 painter1->drawImage( 0, 0, *maskImage );
1425
1426 // ... and overpaint the second pass' image on it
1427 painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
1428 painter1->drawImage( 0, 0, *job.img );
1429 }
1430 }
1431 else
1432 {
1433 job.firstPassJob->picture = std::move( job.picture );
1434 job.picture = nullptr;
1435 }
1436 }
1437 }
1438}
1439
1440void QgsMapRendererJob::logRenderingTime( const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob )
1441{
1443 return;
1444
1445 QMultiMap<int, QString> elapsed;
1446 for ( const LayerRenderJob &job : jobs )
1447 elapsed.insert( job.renderingTime, job.layerId );
1448 for ( const LayerRenderJob &job : secondPassJobs )
1449 elapsed.insert( job.renderingTime, job.layerId + QString( " (second pass)" ) );
1450
1451 elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
1452
1453 QList<int> tt( elapsed.uniqueKeys() );
1454 std::sort( tt.begin(), tt.end(), std::greater<int>() );
1455 for ( int t : std::as_const( tt ) )
1456 {
1457 QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QLatin1String( ", " ) ) ), tr( "Rendering" ) );
1458 }
1459 QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
1460}
1461
1462void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1463{
1464 QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
1465
1466 std::unique_ptr< QgsScopedRuntimeProfile > labelingProfile;
1467 if ( renderContext.flags() & Qgis::RenderContextFlag::RecordProfile )
1468 {
1469 labelingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "(labeling)" ), QStringLiteral( "rendering" ) );
1470 }
1471
1472 QElapsedTimer t;
1473 t.start();
1474
1475 // Reset the composition mode before rendering the labels
1476 painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1477
1478 renderContext.setPainter( painter );
1479
1480 if ( labelingEngine2 )
1481 {
1482 labelingEngine2->run( renderContext );
1483 }
1484
1485 QgsDebugMsgLevel( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
1486}
1487
1488void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1489{
1490 Q_UNUSED( settings )
1491
1492 drawLabeling( renderContext, labelingEngine2, painter );
1493}
1494
@ InternalLayerOpacityHandling
The renderer internally handles the raster layer's opacity, so the default layer level opacity handli...
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ Vector
Vector layer.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ Raster
Raster layer.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
@ RenderPartialOutputOverPreviousCachedImage
When rendering temporary in-progress preview renders, these preview renders can be drawn over any pre...
@ RenderPartialOutputs
The renderer benefits from rendering temporary in-progress preview renders. These are temporary resul...
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
@ RecordProfile
Enable run-time profiling while rendering (since QGIS 3.34)
@ Linework
Combines all rings into a set of noded lines and then extracts valid polygons from that linework.
@ Structure
Structured method, first makes all rings valid and then merges shells and subtracts holes from shells...
@ Forward
Forward transform (from source to destination)
@ Reverse
Reverse/inverse transform (from destination to source)
@ ForceVectorOutput
Vector graphics should not be cached and drawn as raster images.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
@ ForceRasterMasks
Force symbol masking to be applied using a raster method. This is considerably faster when compared t...
virtual bool requiresAdvancedEffects() const =0
Returns true if drawing labels requires advanced effects like composition modes, which could prevent ...
virtual bool requiresAdvancedEffects() const =0
Returns true if drawing labels requires advanced effects like composition modes, which could prevent ...
Class for doing transforms between two map coordinate systems.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination coordinate reference system.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
Custom exception class for Coordinate Reference System related exceptions.
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition qgsrange.h:285
Stores digital elevation model in a raster image which may get updated as a part of map layer renderi...
bool isValid() const
Returns whether the elevation map is valid.
This class can render elevation shading on an image with different methods (eye dome lighting,...
Qgis::ElevationMapCombineMethod combinedElevationMethod() const
Returns the method used when conbining different elevation sources.
bool isActive() const
Returns whether this shading renderer is active.
void renderShading(const QgsElevationMap &elevation, QImage &image, const QgsRenderContext &context) const
Render shading on image condidering the elevation map elevation and the renderer context context If e...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
A paint device which converts everything renderer to a QgsGeometry representation of the rendered sha...
A geometry is the spatial representation of a feature.
QgsFeedback subclass for granular reporting of labeling engine progress.
The QgsLabelingEngine class provides map labeling functionality.
virtual void run(QgsRenderContext &context)=0
Runs the labeling job.
static void warning(const QString &msg)
Goes to qWarning.
virtual bool isVisibleInZRange(const QgsDoubleRange &range, QgsMapLayer *layer=nullptr) const
Returns true if the layer should be visible and rendered for the specified z range.
virtual bool hasElevation() const
Returns true if the layer has an elevation or z component.
virtual void setLayerRenderingTimeHint(int time)
Sets approximate render time (in ms) for the layer to render.
Restore overridden layer style on destruction.
virtual bool isVisibleInTemporalRange(const QgsDateTimeRange &range) const
Returns true if the layer should be visible and rendered for the specified time range.
Base class for all map layer types.
Definition qgsmaplayer.h:75
QString name
Definition qgsmaplayer.h:79
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:82
QString id
Definition qgsmaplayer.h:78
Qgis::LayerType type
Definition qgsmaplayer.h:85
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)=0
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context.
double minimumScale() const
Returns the minimum map scale (i.e.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
double opacity
Definition qgsmaplayer.h:87
double maximumScale() const
Returns the maximum map scale (i.e.
This class is responsible for keeping cache of rendered images resulting from a map rendering job.
bool updateParameters(const QgsRectangle &extent, const QgsMapToPixel &mtp)
Sets extent and scale parameters.
QList< QgsMapLayer * > dependentLayers(const QString &cacheKey) const
Returns a list of map layers on which an image in the cache depends.
bool hasCacheImage(const QString &cacheKey) const
Returns true if the cache contains an image with the specified cacheKey that has the same extent and ...
QImage cacheImage(const QString &cacheKey) const
Returns the cached image for the specified cacheKey.
bool hasAnyCacheImage(const QString &cacheKey, double minimumScaleThreshold=0, double maximumScaleThreshold=0) const
Returns true if the cache contains an image with the specified cacheKey with any cache's parameters (...
void setCacheImageWithParameters(const QString &cacheKey, const QImage &image, const QgsRectangle &extent, const QgsMapToPixel &mapToPixel, const QList< QgsMapLayer * > &dependentLayers=QList< QgsMapLayer * >())
Set the cached image for a particular cacheKey, using a specific extent and mapToPixel (which may dif...
void clearCacheImage(const QString &cacheKey)
Removes an image from the cache with matching cacheKey.
QImage transformedCacheImage(const QString &cacheKey, const QgsMapToPixel &mtp) const
Returns the cached image for the specified cacheKey transformed to the particular extent and scale.
Abstract base class for map rendering implementations.
void logRenderingTime(const std::vector< LayerRenderJob > &jobs, const std::vector< LayerRenderJob > &secondPassJobs, const LabelRenderJob &labelJob)
static QgsElevationMap layerElevationToBeComposed(const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache)
static QImage composeImage(const QgsMapSettings &settings, const std::vector< LayerRenderJob > &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
void cleanupSecondPassJobs(std::vector< LayerRenderJob > &jobs)
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
QHash< QgsMapLayer *, int > perLayerRenderingTime() const
Returns the render time (in ms) per layer.
void initSecondPassJobs(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob) const
Initialize secondPassJobs according to what have been rendered (mask clipping path e....
static QImage layerImageToBeComposed(const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache)
QHash< QString, int > mLayerRenderingTimeHints
Approximate expected layer rendering time per layer, by layer ID.
std::unique_ptr< QgsRenderedItemResults > mRenderedItemResults
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
static Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
static const QString LABEL_PREVIEW_CACHE_ID
QgsMapRendererCache ID string for cached label image during preview compositions only.
std::vector< LayerRenderJob > prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
void cleanupJobs(std::vector< LayerRenderJob > &jobs)
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
QgsMapRendererCache * mCache
void finished()
emitted when asynchronous rendering is finished (or canceled).
QgsMapSettings mSettings
static const QgsSettingsEntryBool * settingsLogCanvasRefreshEvent
Settings entry log canvas refresh event.
QgsMapRendererJob(const QgsMapSettings &settings)
~QgsMapRendererJob() override
void start()
Start the rendering job and immediately return.
int renderingTime() const
Returns the total time it took to finish the job (in milliseconds).
QStringList mLayersRedrawnFromCache
QStringList layersRedrawnFromCache() const
Returns a list of the layer IDs for all layers which were redrawn from cached images.
QList< QgsMapRendererJob::Error > Errors
static const QString LABEL_CACHE_ID
QgsMapRendererCache ID string for cached label image.
static const QString ELEVATION_MAP_CACHE_PREFIX
QgsMapRendererCache prefix string for cached elevation map image.
QHash< QgsWeakMapLayerPointer, int > mPerLayerRenderingTime
Render time (in ms) per layer, by layer ID.
QgsRenderedItemResults * takeRenderedItemResults()
Takes the rendered item results from the map render job and returns them.
QgsLabelingEngineFeedback * labelingEngineFeedback()
Returns the associated labeling engine feedback object.
static void composeSecondPass(std::vector< LayerRenderJob > &secondPassJobs, LabelRenderJob &labelJob, bool forceVector=false)
Compose second pass images into first pass images.
std::vector< LayerRenderJob > prepareSecondPassJobs(std::vector< LayerRenderJob > &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
static const QgsSettingsEntryString * settingsMaskBackend
Settings entry for mask painting backend engine.
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
void setLayerRenderingTimeHints(const QHash< QString, int > &hints)
Sets approximate render times (in ms) for map layers.
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
QgsLabelSink * labelSink() const
Returns the label sink associated to this rendering job.
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
QgsMapRendererQImageJob(const QgsMapSettings &settings)
The QgsMapSettings class contains configuration for rendering of the map.
QSize deviceOutputSize() const
Returns the device output size of the map render.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered 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.
QgsDoubleRange zRange() const
Returns the range of z-values which will be visible in the map.
QColor backgroundColor() const
Returns the background color of the map.
const QgsMapToPixel & mapToPixel() const
float devicePixelRatio() const
Returns the device pixel ratio.
QSize outputSize() const
Returns the size of the resulting map image, in pixels.
QImage::Format outputImageFormat() const
format of internal QImage, default QImage::Format_ARGB32_Premultiplied
double extentBuffer() const
Returns the buffer in map units to use around the visible extent for rendering symbols whose correspo...
Qgis::MapSettingsFlags flags() const
Returns combination of flags used for rendering.
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
const QgsElevationShadingRenderer & elevationShadingRenderer() const
Returns the shading renderer used to render shading on the entire map.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
bool testFlag(Qgis::MapSettingsFlag flag) const
Check whether a particular flag is enabled.
QMap< QString, QString > layerStyleOverrides() const
Returns the map of map layer style overrides (key: layer ID, value: style name) where a different sty...
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
const QgsAbstractMeshLayerLabeling * labeling() const
Access to const labeling configuration.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
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 bool staticWillUseLayer(const QgsMapLayer *layer)
Called to find out whether a specified layer is used for labeling.
A class to represent a 2D point.
Definition qgspointxy.h:60
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
double x
Definition qgspointxy.h:63
Represents a raster layer.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
virtual Qgis::RasterRendererFlags flags() const
Returns flags which dictate renderer behavior.
A rectangle specified with double values.
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
void setXMinimum(double x)
Set the minimum x value.
double width() const
Returns the width of the rectangle.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void setXMaximum(double x)
Set the maximum x value.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double height() const
Returns the height of the rectangle.
bool isFinite() const
Returns true if the rectangle has finite boundaries.
Contains information about the context of a rendering operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
Stores collated details of rendered items during a map rendering operation.
T value(const QString &dynamicKeyPart=QString()) const
Returns settings value.
A boolean settings entry.
A string settings entry.
static QgsSettingsTreeNode * sTreeMap
Type used to refer to a specific symbol layer in a symbol of a layer.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
bool isTemporal() const
Returns true if the object's temporal range is enabled, and the object will be filtered when renderin...
Implementation of threaded rendering for vector layers.
static QgsMaskedLayers symbolLayerMasks(const QgsVectorLayer *)
Returns all masks that may be defined on symbol layers for a given vector layer.
static QHash< QString, QgsMaskedLayers > labelMasks(const QgsVectorLayer *)
Returns masks defined in labeling options of a layer.
Represents a vector layer which manages a vector based data sets.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5652
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
QHash< QString, QgsMaskedLayer > QgsMaskedLayers
masked layers where key is the layer id