QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 "qgscrscache.h"
25 #include "qgslogger.h"
26 #include "qgsrendercontext.h"
27 #include "qgsmaplayer.h"
28 #include "qgsmaplayerregistry.h"
29 #include "qgsmaplayerrenderer.h"
31 #include "qgsmaprenderercache.h"
32 #include "qgsmessagelog.h"
33 #include "qgspallabeling.h"
34 #include "qgsvectorlayerrenderer.h"
35 #include "qgsvectorlayer.h"
36 
38  : mSettings( settings )
39  , mCache( nullptr )
40  , mRenderingTime( 0 )
41 {
42 }
43 
44 
46  : QgsMapRendererJob( settings )
47 {
48 }
49 
50 
52 {
53  return mErrors;
54 }
55 
57 {
58  mCache = cache;
59 }
60 
62 {
63  return mSettings;
64 }
65 
66 
68 {
69  bool split = false;
70 
71  try
72  {
73 #ifdef QGISDEBUG
74  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
75 #endif
76  // Split the extent into two if the source CRS is
77  // geographic and the extent crosses the split in
78  // geographic coordinates (usually +/- 180 degrees,
79  // and is assumed to be so here), and draw each
80  // extent separately.
81  static const double splitCoord = 180.0;
82 
83  if ( ml->crs().geographicFlag() )
84  {
85  if ( ml->type() == QgsMapLayer::VectorLayer && !ct->destCRS().geographicFlag() )
86  {
87  // if we transform from a projected coordinate system check
88  // check if transforming back roughly returns the input
89  // extend - otherwise render the world.
92 
93  QgsDebugMsg( QString( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
94  .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
95  .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
96  .arg( fabs( 1.0 - extent2.width() / extent.width() ) )
97  .arg( fabs( 1.0 - extent2.height() / extent.height() ) )
98  );
99 
100  if ( fabs( 1.0 - extent2.width() / extent.width() ) < 0.5 &&
101  fabs( 1.0 - extent2.height() / extent.height() ) < 0.5 )
102  {
103  extent = extent1;
104  }
105  else
106  {
107  extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
108  }
109  }
110  else
111  {
112  // Note: ll = lower left point
113  QgsPoint ll = ct->transform( extent.xMinimum(), extent.yMinimum(),
115 
116  // and ur = upper right point
117  QgsPoint ur = ct->transform( extent.xMaximum(), extent.yMaximum(),
119 
120  QgsDebugMsg( QString( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ) );
121 
123 
124  QgsDebugMsg( QString( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ) );
125 
126  if ( ll.x() > ur.x() )
127  {
128  // the coordinates projected in reverse order than what one would expect.
129  // we are probably looking at an area that includes longitude of 180 degrees.
130  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
131  // so let's use (-180,180). This hopefully does not add too much overhead. It is
132  // more straightforward than rendering with two separate extents and more consistent
133  // for rendering, labeling and caching as everything is rendered just in one go
134  extent.setXMinimum( -splitCoord );
135  extent.setXMaximum( splitCoord );
136  }
137  }
138 
139  // TODO: the above rule still does not help if using a projection that covers the whole
140  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
141  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
142  // but in fact the extent should cover the whole world.
143  }
144  else // can't cross 180
145  {
146  if ( ct->destCRS().geographicFlag() &&
147  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
148  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
149  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
150  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
151  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
152  // but this seems like a safer choice.
153  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
154  else
156  }
157  }
158  catch ( QgsCsException &cse )
159  {
160  Q_UNUSED( cse );
161  QgsDebugMsg( "Transform error caught" );
162  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
163  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
164  }
165 
166  return split;
167 }
168 
169 
170 
172 {
173  LayerRenderJobs layerJobs;
174 
175  // render all layers in the stack, starting at the base
177  li.toBack();
178 
179  if ( mCache )
180  {
181  bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
182  QgsDebugMsg( QString( "CACHE VALID: %1" ).arg( cacheValid ) );
183  Q_UNUSED( cacheValid );
184  }
185 
187 
188  while ( li.hasPrevious() )
189  {
190  QString layerId = li.previous();
191 
192  QgsDebugMsg( "Rendering at layer item " + layerId );
193 
195 
196  if ( !ml )
197  {
198  mErrors.append( Error( layerId, tr( "Layer not found in registry." ) ) );
199  continue;
200  }
201 
202  QgsDebugMsg( QString( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5" )
203  .arg( ml->name() )
204  .arg( ml->minimumScale() )
205  .arg( ml->maximumScale() )
206  .arg( ml->hasScaleBasedVisibility() )
207  .arg( ml->blendMode() )
208  );
209 
210  if ( !ml->isInScaleRange( mSettings.scale() ) ) //|| mOverview )
211  {
212  QgsDebugMsg( "Layer not rendered because it is not within the defined visibility scale range" );
213  continue;
214  }
215 
217  const QgsCoordinateTransform* ct = nullptr;
218 
220  {
221  ct = mSettings.layerTransform( ml );
222  if ( ct )
223  {
224  reprojectToLayerExtent( ml, ct, r1, r2 );
225  }
226  QgsDebugMsg( "extent: " + r1.toString() );
227  if ( !r1.isFinite() || !r2.isFinite() )
228  {
229  mErrors.append( Error( layerId, tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
230  continue;
231  }
232  }
233 
234  // Force render of layers that are being edited
235  // or if there's a labeling engine that needs the layer to register features
236  if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
237  {
238  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
239  if ( vl->isEditable() || (( labelingEngine || labelingEngine2 ) && QgsPalLabeling::staticWillUseLayer( vl ) ) )
240  mCache->clearCacheImage( ml->id() );
241  }
242 
243  layerJobs.append( LayerRenderJob() );
244  LayerRenderJob& job = layerJobs.last();
245  job.cached = false;
246  job.img = nullptr;
247  job.blendMode = ml->blendMode();
248  job.opacity = 1.0;
249  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml ) )
250  {
251  job.opacity = 1.0 - vl->layerTransparency() / 100.0;
252  }
253  job.layerId = ml->id();
254  job.renderingTime = -1;
255 
257  job.context.setPainter( painter );
258  job.context.setLabelingEngine( labelingEngine );
259  job.context.setLabelingEngineV2( labelingEngine2 );
261  job.context.setExtent( r1 );
262 
263  // if we can use the cache, let's do it and avoid rendering!
264  if ( mCache && !mCache->cacheImage( ml->id() ).isNull() )
265  {
266  job.cached = true;
267  job.img = new QImage( mCache->cacheImage( ml->id() ) );
268  job.renderer = nullptr;
269  job.context.setPainter( nullptr );
270  continue;
271  }
272 
273  // If we are drawing with an alternative blending mode then we need to render to a separate image
274  // before compositing this on the map. This effectively flattens the layer and prevents
275  // blending occurring between objects on the layer
276  if ( mCache || !painter || needTemporaryImage( ml ) )
277  {
278  // Flattened image for drawing when a blending mode is set
279  QImage * mypFlattenedImage = nullptr;
280  mypFlattenedImage = new QImage( mSettings.outputSize().width(),
283  if ( mypFlattenedImage->isNull() )
284  {
285  mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
286  delete mypFlattenedImage;
287  layerJobs.removeLast();
288  continue;
289  }
290  mypFlattenedImage->fill( 0 );
291 
292  job.img = mypFlattenedImage;
293  QPainter* mypPainter = new QPainter( job.img );
294  mypPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
295  job.context.setPainter( mypPainter );
296  }
297 
298  bool hasStyleOverride = mSettings.layerStyleOverrides().contains( ml->id() );
299  if ( hasStyleOverride )
301 
302  job.renderer = ml->createMapRenderer( job.context );
303 
304  if ( hasStyleOverride )
306 
308  {
309  if ( QgsVectorLayerRenderer* vlr = dynamic_cast<QgsVectorLayerRenderer*>( job.renderer ) )
310  {
311  vlr->setGeometryCachePointer( &mGeometryCaches[ ml->id()] );
312  }
313  }
314 
315  } // while (li.hasPrevious())
316 
317  return layerJobs;
318 }
319 
320 
322 {
323  for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
324  {
325  LayerRenderJob& job = *it;
326  if ( job.img )
327  {
328  delete job.context.painter();
329  job.context.setPainter( nullptr );
330 
331  if ( mCache && !job.cached && !job.context.renderingStopped() )
332  {
333  QgsDebugMsg( "caching image for " + job.layerId );
334  mCache->setCacheImage( job.layerId, *job.img );
335  }
336 
337  delete job.img;
338  job.img = nullptr;
339  }
340 
341  if ( job.renderer )
342  {
343  Q_FOREACH ( const QString& message, job.renderer->errors() )
344  mErrors.append( Error( job.renderer->layerID(), message ) );
345 
346  delete job.renderer;
347  job.renderer = nullptr;
348  }
349  }
350 
351  jobs.clear();
352 
354 }
355 
356 
358 {
359  QImage image( settings.outputSize(), settings.outputImageFormat() );
360  image.fill( settings.backgroundColor().rgba() );
361 
362  QPainter painter( &image );
363 
364  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
365  {
366  const LayerRenderJob& job = *it;
367 
368  painter.setCompositionMode( job.blendMode );
369  painter.setOpacity( job.opacity );
370 
371  Q_ASSERT( job.img );
372 
373  painter.drawImage( 0, 0, *job.img );
374  }
375 
376  painter.end();
377  return image;
378 }
379 
381 {
382  QSettings settings;
383  if ( !settings.value( "/Map/logCanvasRefreshEvent", false ).toBool() )
384  return;
385 
386  QMultiMap<int, QString> elapsed;
387  Q_FOREACH ( const LayerRenderJob& job, jobs )
388  elapsed.insert( job.renderingTime, job.layerId );
389 
390  QList<int> tt( elapsed.uniqueKeys() );
391  qSort( tt.begin(), tt.end(), qGreater<int>() );
392  Q_FOREACH ( int t, tt )
393  {
394  QgsMessageLog::logMessage( tr( "%1 ms: %2" ).arg( t ).arg( QStringList( elapsed.values( t ) ).join( ", " ) ), tr( "Rendering" ) );
395  }
396  QgsMessageLog::logMessage( "---", tr( "Rendering" ) );
397 }
bool restoreOverrideStyle()
Restore the original store after a call to setOverrideStyle()
QgsPoint transform(const QgsPoint &p, TransformDirection direction=ForwardTransform) const
Transform the point from Source Coordinate System to Destination Coordinate System If the direction i...
void clear()
const QgsMapSettings & mapSettings() const
Return map settings with which this job was started.
void setOpacity(qreal opacity)
A rectangle specified with double values.
Definition: qgsrectangle.h:35
Base class for all map layer types.
Definition: qgsmaplayer.h:49
int width() const
Abstract base class for map rendering implementations.
bool end()
bool contains(const Key &key) const
void setCompositionMode(CompositionMode mode)
void setRenderHint(RenderHint hint, bool on)
QList< T > values() const
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:172
void cleanupJobs(LayerRenderJobs &jobs)
void updateLayerGeometryCaches()
called when rendering has finished to update all layers&#39; geometry caches
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs)
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QStringList mRequestedGeomCacheForLayers
list of layer IDs for which the geometry cache should be updated
QgsMapLayer * mapLayer(const QString &theLayerId) const
Retrieve a pointer to a registered layer by layer ID.
void logRenderingTime(const LayerRenderJobs &jobs)
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...
QMap< QString, QgsGeometryCache > mGeometryCaches
map of geometry caches
QColor backgroundColor() const
Get the background color of the map.
const QgsCoordinateReferenceSystem & crs() const
Returns layer&#39;s spatial reference system.
void clearCacheImage(const QString &layerId)
remove layer from the cache
bool contains(const QString &str, Qt::CaseSensitivity cs) const
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
bool renderingStopped() const
The QgsLabelingEngineV2 class provides map labeling functionality.
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
bool isNull() const
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)
Return new instance of QgsMapLayerRenderer that will be used for rendering of given context...
Definition: qgsmaplayer.h:255
void clear()
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
QgsMapLayer::LayerType type() const
Get the type of the layer.
Definition: qgsmaplayer.cpp:99
QString tr(const char *sourceText, const char *disambiguation, int n)
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.
void setCoordinateTransform(const QgsCoordinateTransform *t)
Sets coordinate transformation.
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
QgsRectangle transformBoundingBox(const QgsRectangle &theRect, TransformDirection direction=ForwardTransform, const bool handle180Crossover=false) const
Transform a QgsRectangle to the dest Coordinate system If the direction is ForwardTransform then coor...
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
void append(const T &value)
QString id() const
Get this layer&#39;s unique ID, this ID is used to access this layer from map layer registry.
void fill(uint pixelValue)
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:207
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)
QMap< Key, T >::iterator insert(const Key &key, const T &value)
const QgsCoordinateTransform * layerTransform(QgsMapLayer *layer) const
Return coordinate transform from layer&#39;s CRS to destination CRS.
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:117
QString toString() const
String representation of the point (x,y)
Definition: qgspoint.cpp:134
QgsMapSettings mSettings
void setLabelingEngineV2(QgsLabelingEngineV2 *engine2)
Assign new labeling engine.
bool isFinite() const
Returns true if the rectangle has finite boundaries.
iterator end()
double yMinimum() const
Get the y minimum value (bottom side of rectangle)
Definition: qgsrectangle.h:202
Implementation of threaded rendering for vector layers.
double xMaximum() const
Get the x maximum value (right side of rectangle)
Definition: qgsrectangle.h:187
QVariant value(const QString &key, const QVariant &defaultValue) const
QMap< QString, QString > layerStyleOverrides() const
Get map of map layer style overrides (key: layer ID, value: style name) where a different style shoul...
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, QFlags< Qt::ImageConversionFlag > flags)
QPainter * painter()
LayerRenderJobs prepareJobs(QPainter *painter, QgsPalLabeling *labelingEngine, QgsLabelingEngineV2 *labelingEngine2)
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.
QString layerID() const
Get access to the ID of the layer rendered by this class.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
void setLabelingEngine(QgsLabelingEngineInterface *iface)
T & last()
int height() const
QgsMapLayerRenderer * renderer
const QgsCoordinateReferenceSystem & destCRS() const
void removeLast()
Class for doing transforms between two map coordinate systems.
bool toBool() const
double xMinimum() const
Get the x minimum value (left side of rectangle)
Definition: qgsrectangle.h:192
QgsRenderContext context
QgsMapRendererQImageJob(const QgsMapSettings &settings)
double yMaximum() const
Get the y maximum value (top side of rectangle)
Definition: qgsrectangle.h:197
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:53
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.
const_iterator constEnd() const
const_iterator constBegin() const
QStringList layers() const
Get list of layer IDs for map rendering The layers are stored in the reverse order of how they are re...
QPainter::CompositionMode blendMode
Represents a vector layer which manages a vector based data sets.
QgsMapRendererCache * mCache
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
bool needTemporaryImage(QgsMapLayer *ml)
iterator begin()
QSize outputSize() const
Return the size of the resulting map image.
bool isNull(const QVariant &v)
QgsMapRendererJob(const QgsMapSettings &settings)
double x() const
Get the x value of the point.
Definition: qgspoint.h:185
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:167
QRgb rgba() const
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:212
Structure keeping low-level rendering job information.
bool geographicFlag() const
Returns whether the CRS is a geographic CRS.
const T value(const Key &key) const
QList< Key > uniqueKeys() const