QGIS API Documentation  2.99.0-Master (0a63d1f)
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 <QTime>
20 #include <QTimer>
21 #include <QtConcurrentMap>
22 #include <QSettings>
23 
24 #include "qgslogger.h"
25 #include "qgsrendercontext.h"
26 #include "qgsmaplayer.h"
27 #include "qgsproject.h"
28 #include "qgsmaplayerrenderer.h"
30 #include "qgsmaprenderercache.h"
31 #include "qgsmessagelog.h"
32 #include "qgspallabeling.h"
33 #include "qgsvectorlayerrenderer.h"
34 #include "qgsvectorlayer.h"
35 #include "qgscsexception.h"
36 
38  : mSettings( settings )
39  , mCache( nullptr )
40  , mRenderingTime( 0 )
41  , mFeatureFilterProvider( nullptr )
42 {
43 }
44 
45 
47  : QgsMapRendererJob( settings )
48 {
49 }
50 
51 
53 {
54  return mErrors;
55 }
56 
58 {
59  mCache = cache;
60 }
61 
63 {
64  return mSettings;
65 }
66 
67 
69 {
70  bool split = false;
71 
72  try
73  {
74 #ifdef QGISDEBUG
75  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
76 #endif
77  // Split the extent into two if the source CRS is
78  // geographic and the extent crosses the split in
79  // geographic coordinates (usually +/- 180 degrees,
80  // and is assumed to be so here), and draw each
81  // extent separately.
82  static const double SPLIT_COORD = 180.0;
83 
84  if ( ml->crs().isGeographic() )
85  {
86  if ( ml->type() == QgsMapLayer::VectorLayer && !ct.destinationCrs().isGeographic() )
87  {
88  // if we transform from a projected coordinate system check
89  // check if transforming back roughly returns the input
90  // extend - otherwise render the world.
93 
94  QgsDebugMsgLevel( QString( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
95  .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
96  .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
97  .arg( fabs( 1.0 - extent2.width() / extent.width() ) )
98  .arg( fabs( 1.0 - extent2.height() / extent.height() ) )
99  , 3 );
100 
101  if ( fabs( 1.0 - extent2.width() / extent.width() ) < 0.5 &&
102  fabs( 1.0 - extent2.height() / extent.height() ) < 0.5 )
103  {
104  extent = extent1;
105  }
106  else
107  {
108  extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
109  }
110  }
111  else
112  {
113  // Note: ll = lower left point
114  QgsPoint ll = ct.transform( extent.xMinimum(), extent.yMinimum(),
116 
117  // and ur = upper right point
118  QgsPoint ur = ct.transform( extent.xMaximum(), extent.yMaximum(),
120 
121  QgsDebugMsg( QString( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ) );
122 
124 
125  QgsDebugMsg( QString( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ) );
126 
127  if ( ll.x() > ur.x() )
128  {
129  // the coordinates projected in reverse order than what one would expect.
130  // we are probably looking at an area that includes longitude of 180 degrees.
131  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
132  // so let's use (-180,180). This hopefully does not add too much overhead. It is
133  // more straightforward than rendering with two separate extents and more consistent
134  // for rendering, labeling and caching as everything is rendered just in one go
135  extent.setXMinimum( -SPLIT_COORD );
136  extent.setXMaximum( SPLIT_COORD );
137  }
138  }
139 
140  // TODO: the above rule still does not help if using a projection that covers the whole
141  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
142  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
143  // but in fact the extent should cover the whole world.
144  }
145  else // can't cross 180
146  {
147  if ( ct.destinationCrs().isGeographic() &&
148  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
149  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
150  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
151  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
152  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
153  // but this seems like a safer choice.
154  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
155  else
157  }
158  }
159  catch ( QgsCsException &cse )
160  {
161  Q_UNUSED( cse );
162  QgsDebugMsg( "Transform error caught" );
163  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
164  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
165  }
166 
167  return split;
168 }
169 
170 
171 
173 {
174  LayerRenderJobs layerJobs;
175 
176  // render all layers in the stack, starting at the base
177  QListIterator<QgsMapLayer*> li( mSettings.layers() );
178  li.toBack();
179 
180  if ( mCache )
181  {
182  bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
183  QgsDebugMsg( QString( "CACHE VALID: %1" ).arg( cacheValid ) );
184  Q_UNUSED( cacheValid );
185  }
186 
187  mGeometryCaches.clear();
188 
189  while ( li.hasPrevious() )
190  {
191  QgsMapLayer* ml = li.previous();
192 
193  QgsDebugMsgLevel( QString( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5" )
194  .arg( ml->name() )
195  .arg( ml->minimumScale() )
196  .arg( ml->maximumScale() )
197  .arg( ml->hasScaleBasedVisibility() )
198  .arg( ml->blendMode() )
199  , 3 );
200 
201  if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
202  {
203  QgsDebugMsgLevel( "Layer not rendered because it is not within the defined visibility scale range", 3 );
204  continue;
205  }
206 
209 
211  {
212  ct = mSettings.layerTransform( ml );
213  if ( ct.isValid() )
214  {
215  reprojectToLayerExtent( ml, ct, r1, r2 );
216  }
217  QgsDebugMsgLevel( "extent: " + r1.toString(), 3 );
218  if ( !r1.isFinite() || !r2.isFinite() )
219  {
220  mErrors.append( Error( ml->id(), tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
221  continue;
222  }
223  }
224 
225  // Force render of layers that are being edited
226  // or if there's a labeling engine that needs the layer to register features
227  if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
228  {
229  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
230  if ( vl->isEditable() || ( labelingEngine2 && QgsPalLabeling::staticWillUseLayer( vl ) ) )
231  mCache->clearCacheImage( ml->id() );
232  }
233 
234  layerJobs.append( LayerRenderJob() );
235  LayerRenderJob& job = layerJobs.last();
236  job.cached = false;
237  job.img = nullptr;
238  job.blendMode = ml->blendMode();
239  job.opacity = 1.0;
240  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml ) )
241  {
242  job.opacity = 1.0 - vl->layerTransparency() / 100.0;
243  }
244  job.layerId = ml->id();
245  job.renderingTime = -1;
246 
248  job.context.setPainter( painter );
249  job.context.setLabelingEngine( labelingEngine2 );
251  job.context.setExtent( r1 );
252 
255 
256  // if we can use the cache, let's do it and avoid rendering!
257  if ( mCache && !mCache->cacheImage( ml->id() ).isNull() )
258  {
259  job.cached = true;
260  job.img = new QImage( mCache->cacheImage( ml->id() ) );
261  job.renderer = nullptr;
262  job.context.setPainter( nullptr );
263  continue;
264  }
265 
266  // If we are drawing with an alternative blending mode then we need to render to a separate image
267  // before compositing this on the map. This effectively flattens the layer and prevents
268  // blending occurring between objects on the layer
269  if ( mCache || !painter || needTemporaryImage( ml ) )
270  {
271  // Flattened image for drawing when a blending mode is set
272  QImage * mypFlattenedImage = nullptr;
273  mypFlattenedImage = new QImage( mSettings.outputSize().width(),
274  mSettings.outputSize().height(),
276  if ( mypFlattenedImage->isNull() )
277  {
278  mErrors.append( Error( ml->id(), tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
279  delete mypFlattenedImage;
280  layerJobs.removeLast();
281  continue;
282  }
283 
284  job.img = mypFlattenedImage;
285  QPainter* mypPainter = new QPainter( job.img );
286  mypPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
287  job.context.setPainter( mypPainter );
288  }
289 
290  bool hasStyleOverride = mSettings.layerStyleOverrides().contains( ml->id() );
291  if ( hasStyleOverride )
292  ml->styleManager()->setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
293 
294  job.renderer = ml->createMapRenderer( job.context );
295 
296  if ( hasStyleOverride )
298 
299  if ( mRequestedGeomCacheForLayers.contains( ml->id() ) )
300  {
301  if ( QgsVectorLayerRenderer* vlr = dynamic_cast<QgsVectorLayerRenderer*>( job.renderer ) )
302  {
303  vlr->setGeometryCachePointer( &mGeometryCaches[ ml->id()] );
304  }
305  }
306 
307  } // while (li.hasPrevious())
308 
309  return layerJobs;
310 }
311 
312 
314 {
315  for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
316  {
317  LayerRenderJob& job = *it;
318  if ( job.img )
319  {
320  delete job.context.painter();
321  job.context.setPainter( nullptr );
322 
323  if ( mCache && !job.cached && !job.context.renderingStopped() )
324  {
325  QgsDebugMsg( "caching image for " + job.layerId );
326  mCache->setCacheImage( job.layerId, *job.img );
327  }
328 
329  delete job.img;
330  job.img = nullptr;
331  }
332 
333  if ( job.renderer )
334  {
335  Q_FOREACH ( const QString& message, job.renderer->errors() )
336  mErrors.append( Error( job.renderer->layerId(), message ) );
337 
338  delete job.renderer;
339  job.renderer = nullptr;
340  }
341  }
342 
343  jobs.clear();
344 
346 }
347 
348 
349 QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs )
350 {
351  QImage image( settings.outputSize(), settings.outputImageFormat() );
352  image.fill( settings.backgroundColor().rgba() );
353 
354  QPainter painter( &image );
355 
356  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
357  {
358  const LayerRenderJob& job = *it;
359 
360  painter.setCompositionMode( job.blendMode );
361  painter.setOpacity( job.opacity );
362 
363  Q_ASSERT( job.img );
364 
365  painter.drawImage( 0, 0, *job.img );
366  }
367 
368  painter.end();
369  return image;
370 }
371 
373 {
374  QSettings settings;
375  if ( !settings.value( QStringLiteral( "/Map/logCanvasRefreshEvent" ), false ).toBool() )
376  return;
377 
378  QMultiMap<int, QString> elapsed;
379  Q_FOREACH ( const LayerRenderJob& job, jobs )
380  elapsed.insert( job.renderingTime, job.layerId );
381 
382  QList<int> tt( elapsed.uniqueKeys() );
383  qSort( tt.begin(), tt.end(), qGreater<int>() );
384  Q_FOREACH ( int t, tt )
385  {
386  QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( QStringLiteral( ", " ) ) ), tr( "Rendering" ) );
387  }
388  QgsMessageLog::logMessage( QStringLiteral( "---" ), tr( "Rendering" ) );
389 }
bool restoreOverrideStyle()
Restore the original store after a call to setOverrideStyle()
const QgsMapSettings & mapSettings() const
Return map settings with which this job was started.
A rectangle specified with double values.
Definition: qgsrectangle.h:36
Base class for all map layer types.
Definition: qgsmaplayer.h:50
Abstract base class for map rendering implementations.
void setLabelingEngine(QgsLabelingEngine *engine2)
Assign new labeling engine.
const QgsFeatureFilterProvider * mFeatureFilterProvider
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:177
void cleanupJobs(LayerRenderJobs &jobs)
void setCoordinateTransform(const QgsCoordinateTransform &t)
Sets coordinate transformation.
void updateLayerGeometryCaches()
called when rendering has finished to update all layers&#39; geometry caches
QList< Error > Errors
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs)
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
QStringList mRequestedGeomCacheForLayers
list of layer IDs for which the geometry cache should be updated
void logRenderingTime(const LayerRenderJobs &jobs)
QMap< QString, QgsGeometryCache > mGeometryCaches
map of geometry caches
QColor backgroundColor() const
Get the background color of the map.
void clearCacheImage(const QString &layerId)
remove layer from the cache
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, const bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
bool renderingStopped() const
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
static bool reprojectToLayerExtent(const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2)
Convenience function to project an extent into the layer source CRS, but also split it into two exten...
QList< QgsMapLayer * > layers() const
Get list of layers for map rendering The layers are stored in the reverse order of how they are rende...
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
QgsMapLayer::LayerType type() const
Returns the type of the layer.
Definition: qgsmaplayer.cpp:96
void setExtent(const QgsRectangle &extent)
QgsRectangle visibleExtent() const
Return the actual extent derived from requested extent that takes takes output image size into accoun...
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
The QgsMapSettings class contains configuration for rendering of the map.
QgsMapLayerStyleManager * styleManager() const
Get access to the layer&#39;s style manager.
static bool staticWillUseLayer(QgsVectorLayer *layer)
called to find out whether the layer is used for labeling
QString toString(bool automaticPrecision=false) const
returns string representation of form xmin,ymin xmax,ymax
void setCacheImage(const QString &layerId, const QImage &img)
set cached image for the specified layer ID
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QgsPoint transform(const QgsPoint &point, TransformDirection direction=ForwardTransform) const
Transform the point from the source CRS to the destination CRS.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:37
bool init(const QgsRectangle &extent, double scale)
initialize cache: set new parameters and erase cache if parameters have changed
double scale() const
Return the calculated scale of the map.
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:212
Enable anti-aliasing for map rendering.
static void logMessage(const QString &message, const QString &tag=QString::null, MessageLevel level=WARNING)
add a message to the instance (and create it if necessary)
QImage::Format outputImageFormat() const
format of internal QImage, default QImage::Format_ARGB32_Premultiplied
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsCoordinateReferenceSystem crs() const
Returns the layer&#39;s spatial reference system.
bool isGeographic() const
Returns whether the CRS is a geographic CRS (using lat/lon coordinates)
double minimumScale() const
Returns the minimum scale denominator at which the layer is visible.
double maximumScale() const
Returns the maximum scale denominator at which the layer is visible.
A class to represent a point.
Definition: qgspoint.h:143
QString toString() const
String representation of the point (x,y)
Definition: qgspoint.cpp:163
QgsMapSettings mSettings
bool isFinite() const
Returns true if the rectangle has finite boundaries.
double yMinimum() const
Get the y minimum value (bottom side of rectangle)
Definition: qgsrectangle.h:207
QgsCoordinateTransform layerTransform(QgsMapLayer *layer) const
Return coordinate transform from layer&#39;s CRS to destination CRS.
Implementation of threaded rendering for vector layers.
double xMaximum() const
Get the x maximum value (right side of rectangle)
Definition: qgsrectangle.h:192
The QgsLabelingEngine class provides map labeling functionality.
QMap< QString, QString > layerStyleOverrides() const
Get map of map layer style overrides (key: layer ID, value: style name) where a different style shoul...
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2)
QPainter * painter()
Returns the destination QPainter for the render operation.
Transform from destination to source CRS.
int renderingTime
Time it took to render the layer in ms (it is -1 if not rendered or still rendering) ...
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
QString layerId() const
Get access to the ID of the layer rendered by this class.
QgsMapLayerRenderer * renderer
Class for doing transforms between two map coordinate systems.
double xMinimum() const
Get the x minimum value (left side of rectangle)
Definition: qgsrectangle.h:197
QgsRenderContext context
void setFeatureFilterProvider(const QgsFeatureFilterProvider *ffp)
Set a filter feature provider used for additional filtering of rendered features. ...
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)=0
Return new instance of QgsMapLayerRenderer that will be used for rendering of given context...
QgsMapRendererQImageJob(const QgsMapSettings &settings)
double yMaximum() const
Get the y maximum value (top side of rectangle)
Definition: qgsrectangle.h:202
QImage cacheImage(const QString &layerId)
get cached image for the specified layer ID. Returns null image if it is not cached.
QString name
Read property of QString layerName.
Definition: qgsmaplayer.h:54
Transform from source to destination CRS.
QStringList errors() const
Return list of errors (problems) that happened during the rendering.
This class is responsible for keeping cache of rendered images of individual layers.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
bool setOverrideStyle(const QString &styleDef)
Temporarily apply a different style to the layer.
Custom exception class for Coordinate Reference System related exceptions.
QList< LayerRenderJob > LayerRenderJobs
QPainter::CompositionMode blendMode
Represents a vector layer which manages a vector based data sets.
QgsMapRendererCache * mCache
bool needTemporaryImage(QgsMapLayer *ml)
QSize outputSize() const
Return the size of the resulting map image.
bool isNull(const QVariant &v)
QgsMapRendererJob(const QgsMapSettings &settings)
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:172
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:217
Structure keeping low-level rendering job information.
double x
Definition: qgspoint.h:147