QGIS API Documentation  2.13.0-Master
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 "qgspallabeling.h"
33 #include "qgsvectorlayerrenderer.h"
34 #include "qgsvectorlayer.h"
35 
37  : mSettings( settings )
38  , mCache( nullptr )
39  , mRenderingTime( 0 )
40 {
41 }
42 
43 
45  : QgsMapRendererJob( settings )
46 {
47 }
48 
49 
51 {
52  return mErrors;
53 }
54 
56 {
57  mCache = cache;
58 }
59 
61 {
62  return mSettings;
63 }
64 
65 
67 {
68  bool split = false;
69 
70  try
71  {
72 #ifdef QGISDEBUG
73  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
74 #endif
75  // Split the extent into two if the source CRS is
76  // geographic and the extent crosses the split in
77  // geographic coordinates (usually +/- 180 degrees,
78  // and is assumed to be so here), and draw each
79  // extent separately.
80  static const double splitCoord = 180.0;
81 
82  if ( ml->crs().geographicFlag() )
83  {
84  if ( ml->type() == QgsMapLayer::VectorLayer && !ct->destCRS().geographicFlag() )
85  {
86  // if we transform from a projected coordinate system check
87  // check if transforming back roughly returns the input
88  // extend - otherwise render the world.
91 
92  QgsDebugMsg( QString( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" )
93  .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() )
94  .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() )
95  .arg( fabs( 1.0 - extent2.width() / extent.width() ) )
96  .arg( fabs( 1.0 - extent2.height() / extent.height() ) )
97  );
98 
99  if ( fabs( 1.0 - extent2.width() / extent.width() ) < 0.5 &&
100  fabs( 1.0 - extent2.height() / extent.height() ) < 0.5 )
101  {
102  extent = extent1;
103  }
104  else
105  {
106  extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 );
107  }
108  }
109  else
110  {
111  // Note: ll = lower left point
112  QgsPoint ll = ct->transform( extent.xMinimum(), extent.yMinimum(),
114 
115  // and ur = upper right point
116  QgsPoint ur = ct->transform( extent.xMaximum(), extent.yMaximum(),
118 
119  QgsDebugMsg( QString( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ) );
120 
122 
123  QgsDebugMsg( QString( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ) );
124 
125  if ( ll.x() > ur.x() )
126  {
127  // the coordinates projected in reverse order than what one would expect.
128  // we are probably looking at an area that includes longitude of 180 degrees.
129  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
130  // so let's use (-180,180). This hopefully does not add too much overhead. It is
131  // more straightforward than rendering with two separate extents and more consistent
132  // for rendering, labeling and caching as everything is rendered just in one go
133  extent.setXMinimum( -splitCoord );
134  extent.setXMaximum( splitCoord );
135  }
136  }
137 
138  // TODO: the above rule still does not help if using a projection that covers the whole
139  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
140  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
141  // but in fact the extent should cover the whole world.
142  }
143  else // can't cross 180
144  {
145  if ( ct->destCRS().geographicFlag() &&
146  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
147  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
148  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
149  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
150  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
151  // but this seems like a safer choice.
152  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
153  else
155  }
156  }
157  catch ( QgsCsException &cse )
158  {
159  Q_UNUSED( cse );
160  QgsDebugMsg( "Transform error caught" );
161  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
162  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
163  }
164 
165  return split;
166 }
167 
168 
169 
171 {
172  LayerRenderJobs layerJobs;
173 
174  // render all layers in the stack, starting at the base
176  li.toBack();
177 
178  if ( mCache )
179  {
180  bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
181  QgsDebugMsg( QString( "CACHE VALID: %1" ).arg( cacheValid ) );
182  Q_UNUSED( cacheValid );
183  }
184 
186 
187  while ( li.hasPrevious() )
188  {
189  QString layerId = li.previous();
190 
191  QgsDebugMsg( "Rendering at layer item " + layerId );
192 
194 
195  if ( !ml )
196  {
197  mErrors.append( Error( layerId, tr( "Layer not found in registry." ) ) );
198  continue;
199  }
200 
201  QgsDebugMsg( QString( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5" )
202  .arg( ml->name() )
203  .arg( ml->minimumScale() )
204  .arg( ml->maximumScale() )
205  .arg( ml->hasScaleBasedVisibility() )
206  .arg( ml->blendMode() )
207  );
208 
209  if ( ml->hasScaleBasedVisibility() && ( mSettings.scale() < ml->minimumScale() || mSettings.scale() > ml->maximumScale() ) ) //|| mOverview )
210  {
211  QgsDebugMsg( "Layer not rendered because it is not within the defined visibility scale range" );
212  continue;
213  }
214 
216  const QgsCoordinateTransform* ct = nullptr;
217 
219  {
220  ct = mSettings.layerTransform( ml );
221  if ( ct )
222  {
223  reprojectToLayerExtent( ml, ct, r1, r2 );
224  }
225  QgsDebugMsg( "extent: " + r1.toString() );
226  if ( !r1.isFinite() || !r2.isFinite() )
227  {
228  mErrors.append( Error( layerId, tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
229  continue;
230  }
231  }
232 
233  // Force render of layers that are being edited
234  // or if there's a labeling engine that needs the layer to register features
235  if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
236  {
237  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
238  if ( vl->isEditable() || (( labelingEngine || labelingEngine2 ) && QgsPalLabeling::staticWillUseLayer( vl ) ) )
239  mCache->clearCacheImage( ml->id() );
240  }
241 
242  layerJobs.append( LayerRenderJob() );
243  LayerRenderJob& job = layerJobs.last();
244  job.cached = false;
245  job.img = nullptr;
246  job.blendMode = ml->blendMode();
247  job.layerId = ml->id();
248 
250  job.context.setPainter( painter );
251  job.context.setLabelingEngine( labelingEngine );
252  job.context.setLabelingEngineV2( labelingEngine2 );
254  job.context.setExtent( r1 );
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(),
276  if ( mypFlattenedImage->isNull() )
277  {
278  mErrors.append( Error( layerId, 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  mypFlattenedImage->fill( 0 );
284 
285  job.img = mypFlattenedImage;
286  QPainter* mypPainter = new QPainter( job.img );
287  mypPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
288  job.context.setPainter( mypPainter );
289  }
290 
291  bool hasStyleOverride = mSettings.layerStyleOverrides().contains( ml->id() );
292  if ( hasStyleOverride )
294 
295  job.renderer = ml->createMapRenderer( job.context );
296 
297  if ( hasStyleOverride )
299 
301  {
302  if ( QgsVectorLayerRenderer* vlr = dynamic_cast<QgsVectorLayerRenderer*>( job.renderer ) )
303  {
304  vlr->setGeometryCachePointer( &mGeometryCaches[ ml->id()] );
305  }
306  }
307 
308  } // while (li.hasPrevious())
309 
310  return layerJobs;
311 }
312 
313 
315 {
316  for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
317  {
318  LayerRenderJob& job = *it;
319  if ( job.img )
320  {
321  delete job.context.painter();
322  job.context.setPainter( nullptr );
323 
324  if ( mCache && !job.cached && !job.context.renderingStopped() )
325  {
326  QgsDebugMsg( "caching image for " + job.layerId );
327  mCache->setCacheImage( job.layerId, *job.img );
328  }
329 
330  delete job.img;
331  job.img = nullptr;
332  }
333 
334  if ( job.renderer )
335  {
336  Q_FOREACH ( const QString& message, job.renderer->errors() )
337  mErrors.append( Error( job.renderer->layerID(), message ) );
338 
339  delete job.renderer;
340  job.renderer = nullptr;
341  }
342  }
343 
344  jobs.clear();
345 
347 }
348 
349 
351 {
352  QImage image( settings.outputSize(), settings.outputImageFormat() );
353  image.fill( settings.backgroundColor().rgba() );
354 
355  QPainter painter( &image );
356 
357  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
358  {
359  const LayerRenderJob& job = *it;
360 
361  painter.setCompositionMode( job.blendMode );
362 
363  Q_ASSERT( job.img );
364  painter.drawImage( 0, 0, *job.img );
365  }
366 
367  painter.end();
368  return image;
369 }
bool restoreOverrideStyle()
Restore the original store after a call to setOverrideStyle()
void clear()
A rectangle specified with double values.
Definition: qgsrectangle.h:35
Base class for all map layer types.
Definition: qgsmaplayer.h:49
QgsMapLayer::LayerType type() const
Get the type of the layer.
Definition: qgsmaplayer.cpp:99
int width() const
Abstract base class for map rendering implementations.
bool end()
bool contains(const Key &key) const
double scale() const
Return the calculated scale of the map.
void setCompositionMode(CompositionMode mode)
QString name() const
Get the display name of the layer.
void setRenderHint(RenderHint hint, bool on)
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:171
void cleanupJobs(LayerRenderJobs &jobs)
const QgsCoordinateTransform * layerTransform(QgsMapLayer *layer) const
Return coordinate transform from layer&#39;s CRS to destination CRS.
void updateLayerGeometryCaches()
called when rendering has finished to update all layers&#39; geometry caches
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs)
bool isFinite() const
Returns true if the rectangle has finite boundaries.
double yMaximum() const
Get the y maximum value (top side of rectangle)
Definition: qgsrectangle.h:196
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QStringList mRequestedGeomCacheForLayers
list of layer IDs for which the geometry cache should be updated
QgsMapLayerStyleManager * styleManager() const
Get access to the layer&#39;s style manager.
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
float minimumScale() const
Returns the minimum scale denominator at which the layer is visible.
void clearCacheImage(const QString &layerId)
remove layer from the cache
bool contains(const QString &str, Qt::CaseSensitivity cs) const
QgsRectangle visibleExtent() const
Return the actual extent derived from requested extent that takes takes output image size into accoun...
QMap< QString, QString > layerStyleOverrides() const
Get map of map layer style overrides (key: layer ID, value: style name) where a different style shoul...
The QgsLabelingEngineV2 class provides map labeling functionality.
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
QgsPoint transform(const QgsPoint &p, TransformDirection direction=ForwardTransform) const
Transform the point from Source Coordinate System to Destination Coordinate System If the direction i...
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:241
void clear()
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
QString tr(const char *sourceText, const char *disambiguation, int n)
void setExtent(const QgsRectangle &extent)
double x() const
Get the x value of the point.
Definition: qgspoint.h:128
void setCache(QgsMapRendererCache *cache)
Assign a cache to be used for reading and storing rendered images of individual layers.
QgsMapLayer * mapLayer(const QString &theLayerId)
Retrieve a pointer to a loaded layer by id.
The QgsMapSettings class contains configuration for rendering of the map.
QString layerID() const
Get access to the ID of the layer rendered by this class.
void setCoordinateTransform(const QgsCoordinateTransform *t)
Sets coordinate transformation.
static bool staticWillUseLayer(QgsVectorLayer *layer)
called to find out whether the layer is used for labeling
void setCacheImage(const QString &layerId, const QImage &img)
set cached image for the specified layer ID
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
void append(const T &value)
bool hasScaleBasedVisibility() const
Returns whether scale based visibility is enabled for the layer.
double yMinimum() const
Get the y minimum value (bottom side of rectangle)
Definition: qgsrectangle.h:201
void fill(uint pixelValue)
QSize outputSize() const
Return the size of the resulting map image.
double xMaximum() const
Get the x maximum value (right side of rectangle)
Definition: qgsrectangle.h:186
bool renderingStopped() const
bool init(const QgsRectangle &extent, double scale)
initialize cache: set new parameters and erase cache if parameters have changed
float maximumScale() const
Returns the maximum scale denominator at which the layer is visible.
QStringList errors() const
Return list of errors (problems) that happened during the rendering.
Enable anti-aliasin for map rendering.
const QgsMapSettings & mapSettings() const
Return map settings with which this job was started.
void setPainter(QPainter *p)
QString toString() const
String representation of the point (x,y)
Definition: qgspoint.cpp:126
QString id() const
Get this layer&#39;s unique ID, this ID is used to access this layer from map layer registry.
A class to represent a point.
Definition: qgspoint.h:65
QgsMapSettings mSettings
void setLabelingEngineV2(QgsLabelingEngineV2 *engine2)
Assign new labeling engine.
iterator end()
QColor backgroundColor() const
Get the background color of the map.
Implementation of threaded rendering for vector layers.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
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)
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
void setLabelingEngine(QgsLabelingEngineInterface *iface)
QImage::Format outputImageFormat() const
format of internal QImage, default QImage::Format_ARGB32_Premultiplied
T & last()
int height() const
QgsMapLayerRenderer * renderer
void removeLast()
Class for doing transforms between two map coordinate systems.
QgsRenderContext context
const QgsCoordinateReferenceSystem & crs() const
Returns layer&#39;s spatial reference system.
QgsMapRendererQImageJob(const QgsMapSettings &settings)
QStringList layers() const
Get list of layer IDs for map rendering The layers are stored in the reverse order of how they are re...
QImage cacheImage(const QString &layerId)
get cached image for the specified layer ID. Returns null image if it is not cached.
This class is responsible for keeping cache of rendered images of individual layers.
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
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
QPainter::CompositionMode blendMode
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:206
const QgsCoordinateReferenceSystem & destCRS() const
Represents a vector layer which manages a vector based data sets.
bool geographicFlag() const
Returns whether the CRS is a geographic CRS.
QString toString(bool automaticPrecision=false) const
returns string representation of form xmin,ymin xmax,ymax
QgsMapRendererCache * mCache
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
double xMinimum() const
Get the x minimum value (left side of rectangle)
Definition: qgsrectangle.h:191
bool needTemporaryImage(QgsMapLayer *ml)
iterator begin()
bool isNull(const QVariant &v)
QgsMapRendererJob(const QgsMapSettings &settings)
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:166
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...
QRgb rgba() const
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:211
Structure keeping low-level rendering job information.
const T value(const Key &key) const