QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 "qgslogger.h"
24 #include "qgsrendercontext.h"
25 #include "qgsmaplayer.h"
26 #include "qgsproject.h"
27 #include "qgsmaplayerrenderer.h"
29 #include "qgsmaprenderercache.h"
30 #include "qgsmessagelog.h"
31 #include "qgspallabeling.h"
32 #include "qgsexception.h"
33 #include "qgslabelingengine.h"
34 #include "qgsmaplayerlistutils.h"
35 #include "qgsvectorlayerlabeling.h"
36 #include "qgssettings.h"
38 #include "qgssymbol.h"
39 #include "qgsrenderer.h"
40 #include "qgssymbollayer.h"
41 #include "qgsvectorlayerutils.h"
42 #include "qgssymbollayerutils.h"
45 #include "qgsvectorlayerrenderer.h"
46 
48 
49 const QString QgsMapRendererJob::LABEL_CACHE_ID = QStringLiteral( "_labels_" );
50 const QString QgsMapRendererJob::LABEL_PREVIEW_CACHE_ID = QStringLiteral( "_preview_labels_" );
51 
52 bool LayerRenderJob::imageCanBeComposed() const
53 {
54  if ( imageInitialized )
55  {
56  if ( renderer )
57  {
58  return renderer->isReadyToCompose();
59  }
60  else
61  {
62  return true;
63  }
64  }
65  else
66  {
67  return false;
68  }
69 }
70 
72  : mSettings( settings )
73 
74 {
75 }
76 
77 
79  : QgsMapRendererJob( settings )
80 {
81 }
82 
83 
85 {
86  return mErrors;
87 }
88 
90 {
91  mCache = cache;
92 }
93 
94 QHash<QgsMapLayer *, int> QgsMapRendererJob::perLayerRenderingTime() const
95 {
96  QHash<QgsMapLayer *, int> result;
97  for ( auto it = mPerLayerRenderingTime.constBegin(); it != mPerLayerRenderingTime.constEnd(); ++it )
98  {
99  if ( auto &&lKey = it.key() )
100  result.insert( lKey, it.value() );
101  }
102  return result;
103 }
104 
105 void QgsMapRendererJob::setLayerRenderingTimeHints( const QHash<QString, int> &hints )
106 {
107  mLayerRenderingTimeHints = hints;
108 }
109 
111 {
112  return mSettings;
113 }
114 
116 {
117  bool canCache = mCache;
118 
119  // calculate which layers will be labeled
120  QSet< QgsMapLayer * > labeledLayers;
121  const QList<QgsMapLayer *> layers = mSettings.layers();
122  for ( QgsMapLayer *ml : layers )
123  {
125  labeledLayers << ml;
126 
127  switch ( ml->type() )
128  {
130  {
131  QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( ml );
132  if ( vl->labelsEnabled() && vl->labeling()->requiresAdvancedEffects() )
133  {
134  canCache = false;
135  }
136  break;
137  }
138 
140  {
141  // TODO -- add detection of advanced labeling effects for vector tile layers
142  break;
143  }
144 
150  break;
151  }
152 
153  if ( !canCache )
154  break;
155 
156  }
157 
159  {
160  // we may need to clear label cache and re-register labeled features - check for that here
161 
162  // can we reuse the cached label solution?
163  bool canUseCache = canCache && qgis::listToSet( mCache->dependentLayers( LABEL_CACHE_ID ) ) == labeledLayers;
164  if ( !canUseCache )
165  {
166  // no - participating layers have changed
168  }
169  }
170  return canCache;
171 }
172 
173 
174 bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 )
175 {
176  bool res = true;
177  // we can safely use ballpark transforms without bothering the user here -- at the likely scale of layer extents there
178  // won't be an appreciable difference, and we aren't actually transforming any rendered points here anyway (just the layer extent)
179  QgsCoordinateTransform approxTransform = ct;
180  approxTransform.setBallparkTransformsAreAppropriate( true );
181 
182  try
183  {
184 #ifdef QGISDEBUG
185  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
186 #endif
187  // Split the extent into two if the source CRS is
188  // geographic and the extent crosses the split in
189  // geographic coordinates (usually +/- 180 degrees,
190  // and is assumed to be so here), and draw each
191  // extent separately.
192  static const double SPLIT_COORD = 180.0;
193 
194  if ( ml->crs().isGeographic() )
195  {
196  if ( ml->type() == QgsMapLayerType::VectorLayer && !approxTransform.destinationCrs().isGeographic() )
197  {
198  // if we transform from a projected coordinate system check
199  // check if transforming back roughly returns the input
200  // extend - otherwise render the world.
202  QgsRectangle extent2 = approxTransform.transformBoundingBox( extent1, QgsCoordinateTransform::ForwardTransform );
203 
204  QgsDebugMsgLevel( QStringLiteral( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
205  .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
206  .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
207  .arg( std::fabs( 1.0 - extent2.width() / extent.width() ) )
208  .arg( std::fabs( 1.0 - extent2.height() / extent.height() ) )
209  , 3 );
210 
211  // can differ by a maximum of up to 20% of height/width
212  if ( qgsDoubleNear( extent2.xMinimum(), extent.xMinimum(), extent.width() * 0.2 )
213  && qgsDoubleNear( extent2.xMaximum(), extent.xMaximum(), extent.width() * 0.2 )
214  && qgsDoubleNear( extent2.yMinimum(), extent.yMinimum(), extent.height() * 0.2 )
215  && qgsDoubleNear( extent2.yMaximum(), extent.yMaximum(), extent.height() * 0.2 )
216  )
217  {
218  extent = extent1;
219  }
220  else
221  {
222  extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
223  res = false;
224  }
225  }
226  else
227  {
228  // Note: ll = lower left point
229  QgsPointXY ll = approxTransform.transform( extent.xMinimum(), extent.yMinimum(),
231 
232  // and ur = upper right point
233  QgsPointXY ur = approxTransform.transform( extent.xMaximum(), extent.yMaximum(),
235 
236  QgsDebugMsgLevel( QStringLiteral( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ), 4 );
237 
238  extent = approxTransform.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
239 
240  QgsDebugMsgLevel( QStringLiteral( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ), 4 );
241 
242  if ( ll.x() > ur.x() )
243  {
244  // the coordinates projected in reverse order than what one would expect.
245  // we are probably looking at an area that includes longitude of 180 degrees.
246  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
247  // so let's use (-180,180). This hopefully does not add too much overhead. It is
248  // more straightforward than rendering with two separate extents and more consistent
249  // for rendering, labeling and caching as everything is rendered just in one go
250  extent.setXMinimum( -SPLIT_COORD );
251  extent.setXMaximum( SPLIT_COORD );
252  res = false;
253  }
254  }
255 
256  // TODO: the above rule still does not help if using a projection that covers the whole
257  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
258  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
259  // but in fact the extent should cover the whole world.
260  }
261  else // can't cross 180
262  {
263  if ( approxTransform.destinationCrs().isGeographic() &&
264  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
265  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
266  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
267  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
268  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
269  // but this seems like a safer choice.
270  {
271  extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
272  res = false;
273  }
274  else
275  extent = approxTransform.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform );
276  }
277  }
278  catch ( QgsCsException & )
279  {
280  QgsDebugMsg( QStringLiteral( "Transform error caught" ) );
281  extent = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
282  r2 = QgsRectangle( std::numeric_limits<double>::lowest(), std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
283  res = false;
284  }
285 
286  return res;
287 }
288 
289 QImage *QgsMapRendererJob::allocateImage( QString layerId )
290 {
291  QImage *image = new QImage( mSettings.deviceOutputSize(),
293  image->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
294  if ( image->isNull() )
295  {
296  mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
297  delete image;
298  return nullptr;
299  }
300  return image;
301 }
302 
303 QPainter *QgsMapRendererJob::allocateImageAndPainter( QString layerId, QImage *&image )
304 {
305  QPainter *painter = nullptr;
306  image = allocateImage( layerId );
307  if ( image )
308  {
309  painter = new QPainter( image );
310  painter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
311 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
312  painter->setRenderHint( QPainter::LosslessImageRendering, mSettings.testFlag( QgsMapSettings::LosslessImageRendering ) );
313 #endif
314  }
315  return painter;
316 }
317 
318 LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet )
319 {
320  LayerRenderJobs layerJobs;
321 
322  // render all layers in the stack, starting at the base
323  QListIterator<QgsMapLayer *> li( mSettings.layers() );
324  li.toBack();
325 
326  if ( mCache )
327  {
329  Q_UNUSED( cacheValid )
330  QgsDebugMsgLevel( QStringLiteral( "CACHE VALID: %1" ).arg( cacheValid ), 4 );
331  }
332 
333  bool requiresLabelRedraw = !( mCache && mCache->hasCacheImage( LABEL_CACHE_ID ) );
334 
335  while ( li.hasPrevious() )
336  {
337  QgsMapLayer *ml = li.previous();
338 
339  QgsDebugMsgLevel( QStringLiteral( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5 isValid:%6" )
340  .arg( ml->name() )
341  .arg( ml->minimumScale() )
342  .arg( ml->maximumScale() )
343  .arg( ml->hasScaleBasedVisibility() )
344  .arg( ml->blendMode() )
345  .arg( ml->isValid() )
346  , 3 );
347 
348  if ( !ml->isValid() )
349  {
350  QgsDebugMsgLevel( QStringLiteral( "Invalid Layer skipped" ), 3 );
351  continue;
352  }
353 
354  if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
355  {
356  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not within the defined visibility scale range" ), 3 );
357  continue;
358  }
359 
361  {
362  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's time range" ), 3 );
363  continue;
364  }
365 
367  {
368  QgsDebugMsgLevel( QStringLiteral( "Layer not rendered because it is not visible within the map's z range" ), 3 );
369  continue;
370  }
371 
373  r1.grow( mSettings.extentBuffer() );
375 
376  ct = mSettings.layerTransform( ml );
377  bool haveExtentInLayerCrs = true;
378  if ( ct.isValid() )
379  {
380  haveExtentInLayerCrs = reprojectToLayerExtent( ml, ct, r1, r2 );
381  }
382  QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
383  if ( !r1.isFinite() || !r2.isFinite() )
384  {
385  mErrors.append( Error( ml->id(), tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
386  continue;
387  }
388 
389  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( ml );
390 
391  // Force render of layers that are being edited
392  // or if there's a labeling engine that needs the layer to register features
393  if ( mCache )
394  {
395  const bool requiresLabeling = ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( ml ) ) && requiresLabelRedraw;
396  if ( ( vl && vl->isEditable() ) || requiresLabeling )
397  {
398  mCache->clearCacheImage( ml->id() );
399  }
400  }
401 
402  layerJobs.append( LayerRenderJob() );
403  LayerRenderJob &job = layerJobs.last();
404  job.cached = false;
405  job.img = nullptr;
406  job.layer = ml;
407  job.layerId = ml->id();
408  job.estimatedRenderingTime = mLayerRenderingTimeHints.value( ml->id(), 0 );
409  job.renderingTime = -1;
410 
412  job.context.expressionContext().appendScope( QgsExpressionContextUtils::layerScope( ml ) );
413  job.context.setPainter( painter );
414  job.context.setLabelingEngine( labelingEngine2 );
415  job.context.setCoordinateTransform( ct );
416  job.context.setExtent( r1 );
417  if ( !haveExtentInLayerCrs )
418  job.context.setFlag( QgsRenderContext::ApplyClipAfterReprojection, true );
419 
420  if ( mFeatureFilterProvider )
421  job.context.setFeatureFilterProvider( mFeatureFilterProvider );
422 
423  QgsMapLayerStyleOverride styleOverride( ml );
424  if ( mSettings.layerStyleOverrides().contains( ml->id() ) )
425  styleOverride.setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
426 
427  job.blendMode = ml->blendMode();
428 
429  // raster layer opacity is handled directly within the raster layer renderer, so don't
430  // apply default opacity handling here!
431  job.opacity = ml->type() != QgsMapLayerType::RasterLayer ? ml->opacity() : 1.0;
432 
433  // if we can use the cache, let's do it and avoid rendering!
434  if ( mCache && mCache->hasCacheImage( ml->id() ) )
435  {
436  job.cached = true;
437  job.imageInitialized = true;
438  job.img = new QImage( mCache->cacheImage( ml->id() ) );
439  job.img->setDevicePixelRatio( static_cast<qreal>( mSettings.devicePixelRatio() ) );
440  job.renderer = nullptr;
441  job.context.setPainter( nullptr );
442  continue;
443  }
444 
445  QElapsedTimer layerTime;
446  layerTime.start();
447  job.renderer = ml->createMapRenderer( job.context );
448  if ( job.renderer )
449  job.renderer->setLayerRenderingTimeHint( job.estimatedRenderingTime );
450 
451  // If we are drawing with an alternative blending mode then we need to render to a separate image
452  // before compositing this on the map. This effectively flattens the layer and prevents
453  // blending occurring between objects on the layer
454  if ( mCache || ( !painter && !deferredPainterSet ) || ( job.renderer && job.renderer->forceRasterRender() ) )
455  {
456  // Flattened image for drawing when a blending mode is set
457  job.context.setPainter( allocateImageAndPainter( ml->id(), job.img ) );
458  if ( ! job.img )
459  {
460  delete job.renderer;
461  job.renderer = nullptr;
462  layerJobs.removeLast();
463  continue;
464  }
465  }
466 
467  job.renderingTime = layerTime.elapsed(); // include job preparation time in layer rendering time
468  }
469 
470  return layerJobs;
471 }
472 
473 LayerRenderJobs QgsMapRendererJob::prepareSecondPassJobs( LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob )
474 {
475  LayerRenderJobs secondPassJobs;
476 
477  // We will need to quickly access the associated rendering job of a layer
478  QHash<QString, LayerRenderJob *> layerJobMapping;
479 
480  // ... and whether a layer has a mask defined
481  QSet<QString> layerHasMask;
482 
483  struct MaskSource
484  {
485  QString layerId;
486  QString labelRuleId;
487  int labelMaskId;
488  MaskSource( const QString &layerId_, const QString &labelRuleId_, int labelMaskId_ ):
489  layerId( layerId_ ), labelRuleId( labelRuleId_ ), labelMaskId( labelMaskId_ ) {}
490  };
491 
492  // We collect for each layer, the set of symbol layers that will be "masked"
493  // and the list of source layers that have a mask
494  QHash<QString, QPair<QSet<QgsSymbolLayerId>, QList<MaskSource>>> maskedSymbolLayers;
495 
496  for ( LayerRenderJob &job : firstPassJobs )
497  {
498  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( job.layer );
499  if ( ! vl )
500  continue;
501 
502  layerJobMapping[job.layer->id()] = &job;
503 
504  // lambda function to factor code for both label masks and symbol layer masks
505  auto collectMasks = [&]( QHash<QString, QSet<QgsSymbolLayerId>> *masks, QString sourceLayerId, QString ruleId = QString(), int labelMaskId = -1 )
506  {
507  for ( auto it = masks->begin(); it != masks->end(); ++it )
508  {
509  auto lit = maskedSymbolLayers.find( it.key() );
510  if ( lit == maskedSymbolLayers.end() )
511  {
512  maskedSymbolLayers[it.key()] = qMakePair( it.value(), QList<MaskSource>() << MaskSource( sourceLayerId, ruleId, labelMaskId ) );
513  }
514  else
515  {
516  if ( lit->first != it.value() )
517  {
518  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() ) );
519  continue;
520  }
521  lit->second.push_back( MaskSource( sourceLayerId, ruleId, labelMaskId ) );
522  }
523  }
524  if ( ! masks->isEmpty() )
525  layerHasMask.insert( sourceLayerId );
526  };
527 
528  // collect label masks
529  QHash<QString, QHash<QString, QSet<QgsSymbolLayerId>>> labelMasks = QgsVectorLayerUtils::labelMasks( vl );
530  for ( auto it = labelMasks.begin(); it != labelMasks.end(); it++ )
531  {
532  QString labelRule = it.key();
533  QHash<QString, QSet<QgsSymbolLayerId>> masks = it.value();
534 
535  // group layers by QSet<QgsSymbolLayerReference>
536  QSet<QgsSymbolLayerReference> slRefs;
537  for ( auto mit = masks.begin(); mit != masks.end(); mit++ )
538  {
539  for ( auto slIt = mit.value().begin(); slIt != mit.value().end(); slIt++ )
540  {
541  slRefs.insert( QgsSymbolLayerReference( mit.key(), *slIt ) );
542  }
543  }
544  // generate a new mask id for this set
545  int labelMaskId = labelJob.maskIdProvider.insertLabelLayer( vl->id(), it.key(), slRefs );
546 
547  // now collect masks
548  collectMasks( &masks, vl->id(), labelRule, labelMaskId );
549  }
550 
551  // collect symbol layer masks
552  QHash<QString, QSet<QgsSymbolLayerId>> symbolLayerMasks = QgsVectorLayerUtils::symbolLayerMasks( vl );
553  collectMasks( &symbolLayerMasks, vl->id() );
554  }
555 
556  if ( maskedSymbolLayers.isEmpty() )
557  return secondPassJobs;
558 
559  // Now that we know some layers have a mask, we have to allocate a mask image and painter
560  // for them in the first pass job
561  for ( LayerRenderJob &job : firstPassJobs )
562  {
563  QgsMapLayer *ml = job.layer;
564 
565  if ( job.img == nullptr )
566  {
567  job.context.setPainter( allocateImageAndPainter( ml->id(), job.img ) );
568  }
569  if ( layerHasMask.contains( ml->id() ) )
570  {
571  // Note: we only need an alpha channel here, rather than a full RGBA image
572  job.context.setMaskPainter( allocateImageAndPainter( ml->id(), job.maskImage ) );
573  job.maskImage->fill( 0 );
574  }
575  }
576 
577  // Allocate an image for labels
578  if ( labelJob.img == nullptr )
579  {
580  labelJob.img = allocateImage( QStringLiteral( "labels" ) );
581  }
582 
583  // Prepare label mask images
584  for ( int maskId = 0; maskId < labelJob.maskIdProvider.size(); maskId++ )
585  {
586  QImage *maskImage;
587  labelJob.context.setMaskPainter( allocateImageAndPainter( QStringLiteral( "label mask" ), maskImage ), maskId );
588  maskImage->fill( 0 );
589  labelJob.maskImages.push_back( maskImage );
590  }
591  labelJob.context.setMaskIdProvider( &labelJob.maskIdProvider );
592 
593  // Prepare second pass jobs
594  for ( LayerRenderJob &job : firstPassJobs )
595  {
596  QgsMapLayer *ml = job.layer;
597 
598  auto it = maskedSymbolLayers.find( ml->id() );
599  if ( it == maskedSymbolLayers.end() )
600  continue;
601 
602  QList<MaskSource> &sourceList = it->second;
603  const QSet<QgsSymbolLayerId> &symbolList = it->first;
604 
605  // copy the initial job ...
606  secondPassJobs.append( LayerRenderJob() );
607  LayerRenderJob &job2 = secondPassJobs.last();
608  job2 = job;
609  job2.cached = false;
610  job2.firstPassJob = &job;
611  QgsVectorLayer *vl1 = qobject_cast<QgsVectorLayer *>( job.layer );
612 
613  // ... but clear the image
614  job2.context.setMaskPainter( nullptr );
615  job2.context.setPainter( allocateImageAndPainter( vl1->id(), job2.img ) );
616  if ( ! job2.img )
617  {
618  secondPassJobs.removeLast();
619  continue;
620  }
621 
622  // Points to the first pass job. This will be needed during the second pass composition.
623  for ( MaskSource &source : sourceList )
624  {
625  if ( source.labelMaskId != -1 )
626  job2.maskJobs.push_back( qMakePair( nullptr, source.labelMaskId ) );
627  else
628  job2.maskJobs.push_back( qMakePair( layerJobMapping[source.layerId], -1 ) );
629  }
630 
631  // FIXME: another possibility here, to avoid allocating a new map renderer and reuse the one from
632  // the first pass job, would be to be able to call QgsMapLayerRenderer::render() with a QgsRenderContext.
633  QgsVectorLayerRenderer *mapRenderer = static_cast<QgsVectorLayerRenderer *>( vl1->createMapRenderer( job2.context ) );
634  job2.renderer = mapRenderer;
635 
636  // Modify the render context so that symbol layers get disabled as needed.
637  // The map renderer stores a reference to the context, so we can modify it even after the map renderer creation (what we need here)
638  job2.context.setDisabledSymbolLayers( QgsSymbolLayerUtils::toSymbolLayerPointers( mapRenderer->featureRenderer(), symbolList ) );
639  }
640 
641  return secondPassJobs;
642 }
643 
644 LabelRenderJob QgsMapRendererJob::prepareLabelingJob( QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache )
645 {
646  LabelRenderJob job;
648  job.context.setPainter( painter );
649  job.context.setLabelingEngine( labelingEngine2 );
650  job.context.setExtent( mSettings.visibleExtent() );
651  job.context.setFeatureFilterProvider( mFeatureFilterProvider );
652 
653  // if we can use the cache, let's do it and avoid rendering!
654  bool hasCache = canUseLabelCache && mCache && mCache->hasCacheImage( LABEL_CACHE_ID );
655  if ( hasCache )
656  {
657  job.cached = true;
658  job.complete = true;
659  job.img = new QImage( mCache->cacheImage( LABEL_CACHE_ID ) );
660  Q_ASSERT( job.img->devicePixelRatio() == mSettings.devicePixelRatio() );
661  job.context.setPainter( nullptr );
662  }
663  else
664  {
665  if ( canUseLabelCache && ( mCache || !painter ) )
666  {
667  job.img = allocateImage( QStringLiteral( "labels" ) );
668  }
669  }
670 
671  return job;
672 }
673 
674 
675 void QgsMapRendererJob::cleanupJobs( LayerRenderJobs &jobs )
676 {
677  for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
678  {
679  LayerRenderJob &job = *it;
680  if ( job.img )
681  {
682  delete job.context.painter();
683  job.context.setPainter( nullptr );
684 
685  if ( mCache && !job.cached && job.completed && job.layer )
686  {
687  QgsDebugMsgLevel( QStringLiteral( "caching image for %1" ).arg( job.layerId ), 2 );
688  mCache->setCacheImageWithParameters( job.layerId, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
689  mCache->setCacheImageWithParameters( job.layerId + QStringLiteral( "_preview" ), *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), QList< QgsMapLayer * >() << job.layer );
690  }
691 
692  delete job.img;
693  job.img = nullptr;
694  }
695 
696  // delete the mask image and painter
697  if ( job.maskImage )
698  {
699  delete job.context.maskPainter();
700  job.context.setMaskPainter( nullptr );
701  delete job.maskImage;
702  }
703 
704  if ( job.renderer )
705  {
706  const auto constErrors = job.renderer->errors();
707  for ( const QString &message : constErrors )
708  mErrors.append( Error( job.renderer->layerId(), message ) );
709 
710  delete job.renderer;
711  job.renderer = nullptr;
712  }
713 
714  if ( job.layer )
715  mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
716  }
717 
718  jobs.clear();
719 }
720 
721 void QgsMapRendererJob::cleanupSecondPassJobs( LayerRenderJobs &jobs )
722 {
723  for ( auto &job : jobs )
724  {
725  if ( job.img )
726  {
727  delete job.context.painter();
728  job.context.setPainter( nullptr );
729 
730  delete job.img;
731  job.img = nullptr;
732  }
733 
734  if ( job.renderer )
735  {
736  delete job.renderer;
737  job.renderer = nullptr;
738  }
739 
740  if ( job.layer )
741  mPerLayerRenderingTime.insert( job.layer, job.renderingTime );
742  }
743 
744  jobs.clear();
745 }
746 
747 void QgsMapRendererJob::cleanupLabelJob( LabelRenderJob &job )
748 {
749  if ( job.img )
750  {
751  if ( mCache && !job.cached && !job.context.renderingStopped() )
752  {
753  QgsDebugMsgLevel( QStringLiteral( "caching label result image" ), 2 );
754  mCache->setCacheImageWithParameters( LABEL_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
755  mCache->setCacheImageWithParameters( LABEL_PREVIEW_CACHE_ID, *job.img, mSettings.visibleExtent(), mSettings.mapToPixel(), _qgis_listQPointerToRaw( job.participatingLayers ) );
756  }
757 
758  delete job.img;
759  job.img = nullptr;
760  }
761 
762  for ( int maskId = 0; maskId < job.maskImages.size(); maskId++ )
763  {
764  delete job.context.maskPainter( maskId );
765  job.context.setMaskPainter( nullptr, maskId );
766  delete job.maskImages[maskId];
767  }
768 }
769 
770 
771 #define DEBUG_RENDERING 0
772 
774  const QgsMapSettings &settings,
775  const LayerRenderJobs &jobs,
776  const LabelRenderJob &labelJob,
777  const QgsMapRendererCache *cache
778 )
779 {
780  QImage image( settings.deviceOutputSize(), settings.outputImageFormat() );
781  image.setDevicePixelRatio( settings.devicePixelRatio() );
782  image.setDotsPerMeterX( static_cast<int>( settings.outputDpi() * 39.37 ) );
783  image.setDotsPerMeterY( static_cast<int>( settings.outputDpi() * 39.37 ) );
784  image.fill( settings.backgroundColor().rgba() );
785 
786  QPainter painter( &image );
787 
788 #if DEBUG_RENDERING
789  int i = 0;
790 #endif
791  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
792  {
793  const LayerRenderJob &job = *it;
794 
795  if ( job.layer && job.layer->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool() )
796  continue; // skip layer for now, it will be rendered after labels
797 
798  QImage img = layerImageToBeComposed( settings, job, cache );
799  if ( img.isNull() )
800  continue; // image is not prepared and not even in cache
801 
802  painter.setCompositionMode( job.blendMode );
803  painter.setOpacity( job.opacity );
804 
805 #if DEBUG_RENDERING
806  img.save( QString( "/tmp/final_%1.png" ).arg( i ) );
807  i++;
808 #endif
809 
810  painter.drawImage( 0, 0, img );
811  }
812 
813  // IMPORTANT - don't draw labelJob img before the label job is complete,
814  // as the image is uninitialized and full of garbage before the label job
815  // commences
816  if ( labelJob.img && labelJob.complete )
817  {
818  painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
819  painter.setOpacity( 1.0 );
820  painter.drawImage( 0, 0, *labelJob.img );
821  }
822  // when checking for a label cache image, we only look for those which would be drawn between 30% and 300% of the
823  // original size. We don't want to draw massive pixelated labels on top of everything else, and we also don't need
824  // to draw tiny unreadable labels... better to draw nothing in this case and wait till the updated label results are ready!
825  else if ( cache && cache->hasAnyCacheImage( LABEL_PREVIEW_CACHE_ID, 0.3, 3 ) )
826  {
827  const QImage labelCacheImage = cache->transformedCacheImage( LABEL_PREVIEW_CACHE_ID, settings.mapToPixel() );
828  painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
829  painter.setOpacity( 1.0 );
830  painter.drawImage( 0, 0, labelCacheImage );
831  }
832 
833  // render any layers with the renderAboveLabels flag now
834  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
835  {
836  const LayerRenderJob &job = *it;
837 
838  if ( !job.layer || !job.layer->customProperty( QStringLiteral( "rendering/renderAboveLabels" ) ).toBool() )
839  continue;
840 
841  QImage img = layerImageToBeComposed( settings, job, cache );
842  if ( img.isNull() )
843  continue; // image is not prepared and not even in cache
844 
845  painter.setCompositionMode( job.blendMode );
846  painter.setOpacity( job.opacity );
847 
848  painter.drawImage( 0, 0, img );
849  }
850 
851  painter.end();
852 #if DEBUG_RENDERING
853  image.save( "/tmp/final.png" );
854 #endif
855  return image;
856 }
857 
859  const QgsMapSettings &settings,
860  const LayerRenderJob &job,
861  const QgsMapRendererCache *cache
862 )
863 {
864  if ( job.imageCanBeComposed() )
865  {
866  Q_ASSERT( job.img );
867  return *job.img;
868  }
869  else
870  {
871  if ( cache && cache->hasAnyCacheImage( job.layerId + QStringLiteral( "_preview" ) ) )
872  {
873  return cache->transformedCacheImage( job.layerId + QStringLiteral( "_preview" ), settings.mapToPixel() );
874  }
875  else
876  return QImage();
877  }
878 }
879 
880 void QgsMapRendererJob::composeSecondPass( LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob )
881 {
882 #if DEBUG_RENDERING
883  int i = 0;
884 #endif
885  // compose the second pass with the mask
886  for ( LayerRenderJob &job : secondPassJobs )
887  {
888 #if DEBUG_RENDERING
889  i++;
890  job.img->save( QString( "/tmp/second_%1.png" ).arg( i ) );
891  int mask = 0;
892 #endif
893 
894  // Merge all mask images into the first one if we have more than one mask image
895  if ( job.maskJobs.size() > 1 )
896  {
897  QPainter *maskPainter = nullptr;
898  for ( QPair<LayerRenderJob *, int> p : job.maskJobs )
899  {
900  QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
901 #if DEBUG_RENDERING
902  maskImage->save( QString( "/tmp/mask_%1_%2.png" ).arg( i ).arg( mask++ ) );
903 #endif
904  if ( ! maskPainter )
905  {
906  maskPainter = p.first ? p.first->context.maskPainter() : labelJob.context.maskPainter( p.second );
907  }
908  else
909  {
910  maskPainter->drawImage( 0, 0, *maskImage );
911  }
912  }
913  }
914 
915  if ( ! job.maskJobs.isEmpty() )
916  {
917  // All have been merged into the first
918  QPair<LayerRenderJob *, int> p = *job.maskJobs.begin();
919  QImage *maskImage = p.first ? p.first->maskImage : labelJob.maskImages[p.second];
920 #if DEBUG_RENDERING
921  maskImage->save( QString( "/tmp/mask_%1.png" ).arg( i ) );
922 #endif
923 
924  // Only retain parts of the second rendering that are "inside" the mask image
925  QPainter *painter = job.context.painter();
926  painter->setCompositionMode( QPainter::CompositionMode_DestinationIn );
927 
928  //Create an "alpha binarized" image of the maskImage to :
929  //* Eliminate antialiasing artifact
930  //* Avoid applying mask opacity to elements under the mask but not masked
931  QImage maskBinAlpha = maskImage->createMaskFromColor( 0 );
932  QVector<QRgb> mswTable;
933  mswTable.push_back( qRgba( 0, 0, 0, 255 ) );
934  mswTable.push_back( qRgba( 0, 0, 0, 0 ) );
935  maskBinAlpha.setColorTable( mswTable );
936  painter->drawImage( 0, 0, maskBinAlpha );
937 #if DEBUG_RENDERING
938  job.img->save( QString( "/tmp/second_%1_a.png" ).arg( i ) );
939 #endif
940 
941  // Modify the first pass' image ...
942  {
943  QPainter tempPainter;
944 
945  // reuse the first pass painter, if available
946  QPainter *painter1 = job.firstPassJob->context.painter();
947  if ( ! painter1 )
948  {
949  tempPainter.begin( job.firstPassJob->img );
950  painter1 = &tempPainter;
951  }
952 #if DEBUG_RENDERING
953  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_1.png" ).arg( i ) );
954 #endif
955  // ... first retain parts that are "outside" the mask image
956  painter1->setCompositionMode( QPainter::CompositionMode_DestinationOut );
957  painter1->drawImage( 0, 0, *maskImage );
958 
959 #if DEBUG_RENDERING
960  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_2.png" ).arg( i ) );
961 #endif
962  // ... and overpaint the second pass' image on it
963  painter1->setCompositionMode( QPainter::CompositionMode_DestinationOver );
964  painter1->drawImage( 0, 0, *job.img );
965 #if DEBUG_RENDERING
966  job.img->save( QString( "/tmp/second_%1_b.png" ).arg( i ) );
967  if ( job.firstPassJob )
968  job.firstPassJob->img->save( QString( "/tmp/second_%1_first_pass_3.png" ).arg( i ) );
969 #endif
970  }
971  }
972  }
973 }
974 
975 void QgsMapRendererJob::logRenderingTime( const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob )
976 {
977  QgsSettings settings;
978  if ( !settings.value( QStringLiteral( "Map/logCanvasRefreshEvent" ), false ).toBool() )
979  return;
980 
981  QMultiMap<int, QString> elapsed;
982  const auto constJobs = jobs;
983  for ( const LayerRenderJob &job : constJobs )
984  elapsed.insert( job.renderingTime, job.layerId );
985  const auto constSecondPassJobs = secondPassJobs;
986  for ( const LayerRenderJob &job : constSecondPassJobs )
987  elapsed.insert( job.renderingTime, job.layerId + QString( " (second pass)" ) );
988 
989  elapsed.insert( labelJob.renderingTime, tr( "Labeling" ) );
990 
991  QList<int> tt( elapsed.uniqueKeys() );
992  std::sort( tt.begin(), tt.end(), std::greater<int>() );
993  const auto constTt = tt;
994  for ( int t : constTt )
995  {
996  QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QLatin1String( ", " ) ) ), tr( "Rendering" ) );
997  }
998  QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
999 }
1000 
1001 void QgsMapRendererJob::drawLabeling( QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1002 {
1003  QgsDebugMsgLevel( QStringLiteral( "Draw labeling start" ), 5 );
1004 
1005  QElapsedTimer t;
1006  t.start();
1007 
1008  // Reset the composition mode before rendering the labels
1009  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
1010 
1011  renderContext.setPainter( painter );
1012 
1013  if ( labelingEngine2 )
1014  {
1015  labelingEngine2->run( renderContext );
1016  }
1017 
1018  QgsDebugMsgLevel( QStringLiteral( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ), 2 );
1019 }
1020 
1021 void QgsMapRendererJob::drawLabeling( const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter )
1022 {
1023  Q_UNUSED( settings )
1024 
1025  drawLabeling( renderContext, labelingEngine2, painter );
1026 }
1027 
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.
QgsPointXY transform(const QgsPointXY &point, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
@ ForwardTransform
Transform from source to destination CRS.
@ ReverseTransform
Transform from destination to source CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
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.
Definition: qgsexception.h:66
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition: qgsrange.h:247
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
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.
Definition: qgslogger.cpp:122
virtual bool isVisibleInZRange(const QgsDoubleRange &range) const
Returns true if the layer should be visible and rendered for the specified z range.
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:85
QString name
Definition: qgsmaplayer.h:88
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
QgsMapLayerType type
Definition: qgsmaplayer.h:92
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:91
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)=0
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context.
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
bool isValid
Definition: qgsmaplayer.h:93
double minimumScale() const
Returns the minimum map scale (i.e.
virtual QgsMapLayerElevationProperties * elevationProperties()
Returns the layer's elevation properties.
Definition: qgsmaplayer.h:1238
double opacity
Definition: qgsmaplayer.h:94
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1231
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 setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
void cleanupSecondPassJobs(LayerRenderJobs &jobs)
QHash< QgsMapLayer *, int > perLayerRenderingTime() const
Returns the render time (in ms) per layer.
static QImage layerImageToBeComposed(const QgsMapSettings &settings, const LayerRenderJob &job, const QgsMapRendererCache *cache)
LayerRenderJobs prepareSecondPassJobs(LayerRenderJobs &firstPassJobs, LabelRenderJob &labelJob)
Prepares jobs for a second pass, if selective masks exist (from labels or symbol layers).
QHash< QString, int > mLayerRenderingTimeHints
Approximate expected layer rendering time per layer, by layer ID.
void logRenderingTime(const LayerRenderJobs &jobs, const LayerRenderJobs &secondPassJobs, const LabelRenderJob &labelJob)
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)
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
static const QString LABEL_PREVIEW_CACHE_ID
QgsMapRendererCache ID string for cached label image during preview compositions only.
const QgsMapSettings & mapSettings() const
Returns map settings with which this job was started.
static void composeSecondPass(LayerRenderJobs &secondPassJobs, LabelRenderJob &labelJob)
Compose second pass images into first pass images.
QgsMapRendererCache * mCache
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob, const QgsMapRendererCache *cache=nullptr)
QgsMapSettings mSettings
QgsMapRendererJob(const QgsMapSettings &settings)
QList< QgsMapRendererJob::Error > Errors
static const QString LABEL_CACHE_ID
QgsMapRendererCache ID string for cached label image.
QHash< QgsWeakMapLayerPointer, int > mPerLayerRenderingTime
Render time (in ms) per layer, by layer ID.
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...
void cleanupJobs(LayerRenderJobs &jobs)
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.
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.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ Antialiasing
Enable anti-aliasing for map rendering.
QColor backgroundColor() const
Returns the background color of the map.
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...
const QgsMapToPixel & mapToPixel() const
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes takes output image size into accou...
QList< QgsMapLayer * > layers() const
Returns the list of layers which will be rendered in the map.
double outputDpi() const
Returns the DPI (dots per inch) used for conversion between real world units (e.g.
QMap< QString, QString > layerStyleOverrides() const
Returns the map of map layer style overrides (key: layer ID, value: style name) where a different sty...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::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:44
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
Definition: qgspointxy.cpp:51
Q_GADGET double x
Definition: qgspointxy.h:47
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:135
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:130
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:275
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
bool isFinite() const
Returns true if the rectangle has finite boundaries.
Definition: qgsrectangle.h:527
Contains information about the context of a rendering operation.
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
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
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Type used to refer to a specific symbol layer in a symbol of a layer.
static QSet< const QgsSymbolLayer * > toSymbolLayerPointers(QgsFeatureRenderer *renderer, const QSet< QgsSymbolLayerId > &symbolLayerIds)
Converts a set of symbol layer id to a set of pointers to actual symbol layers carried by the feature...
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.
QgsFeatureRenderer * featureRenderer()
Returns the feature renderer.
static QHash< QString, QHash< QString, QSet< QgsSymbolLayerId > > > labelMasks(const QgsVectorLayer *)
Returns masks defined in labeling options of a layer.
static QHash< QString, QSet< QgsSymbolLayerId > > symbolLayerMasks(const QgsVectorLayer *)
Returns all masks that may be defined on symbol layers for a given vector 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.
QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext) FINAL
Returns new instance of QgsMapLayerRenderer that will be used for rendering of given context.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
@ PointCloudLayer
Added in 3.18.
@ MeshLayer
Added in 3.2.
@ VectorTileLayer
Added in 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38