QGIS API Documentation  2.7.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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"
30 #include "qgsmaprenderercache.h"
31 #include "qgspallabeling.h"
32 #include "qgsvectorlayerrenderer.h"
33 
34 
36  : mSettings( settings )
37  , mCache( 0 )
38  , mRenderingTime( 0 )
39 {
40 }
41 
42 
44  : QgsMapRendererJob( settings )
45 {
46 }
47 
48 
50 {
51  return mErrors;
52 }
53 
55 {
56  mCache = cache;
57 }
58 
60 {
61  return mSettings;
62 }
63 
64 
65 bool QgsMapRendererJob::reprojectToLayerExtent( const QgsCoordinateTransform* ct, bool layerCrsGeographic, QgsRectangle& extent, QgsRectangle& r2 )
66 {
67  bool split = false;
68 
69  try
70  {
71 #ifdef QGISDEBUG
72  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
73 #endif
74  // Split the extent into two if the source CRS is
75  // geographic and the extent crosses the split in
76  // geographic coordinates (usually +/- 180 degrees,
77  // and is assumed to be so here), and draw each
78  // extent separately.
79  static const double splitCoord = 180.0;
80 
81  if ( layerCrsGeographic )
82  {
83  // Note: ll = lower left point
84  // and ur = upper right point
85  QgsPoint ll = ct->transform( extent.xMinimum(), extent.yMinimum(),
87 
88  QgsPoint ur = ct->transform( extent.xMaximum(), extent.yMaximum(),
90 
92 
93  if ( ll.x() > ur.x() )
94  {
95  // the coordinates projected in reverse order than what one would expect.
96  // we are probably looking at an area that includes longitude of 180 degrees.
97  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
98  // so let's use (-180,180). This hopefully does not add too much overhead. It is
99  // more straightforward than rendering with two separate extents and more consistent
100  // for rendering, labeling and caching as everything is rendered just in one go
101  extent.setXMinimum( -splitCoord );
102  extent.setXMaximum( splitCoord );
103  }
104 
105  // TODO: the above rule still does not help if using a projection that covers the whole
106  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
107  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
108  // but in fact the extent should cover the whole world.
109  }
110  else // can't cross 180
111  {
112  if ( ct->destCRS().geographicFlag() &&
113  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
114  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
115  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
116  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
117  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
118  // but this seems like a safer choice.
119  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
120  else
122  }
123  }
124  catch ( QgsCsException &cse )
125  {
126  Q_UNUSED( cse );
127  QgsDebugMsg( "Transform error caught" );
128  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
129  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
130  }
131 
132  return split;
133 }
134 
135 
136 
137 LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsPalLabeling* labelingEngine )
138 {
139  LayerRenderJobs layerJobs;
140 
141  // render all layers in the stack, starting at the base
142  QListIterator<QString> li( mSettings.layers() );
143  li.toBack();
144 
145  if ( mCache )
146  {
147  bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
148  QgsDebugMsg( QString( "CACHE VALID: %1" ).arg( cacheValid ) );
149  Q_UNUSED( cacheValid );
150  }
151 
152  mGeometryCaches.clear();
153 
154  while ( li.hasPrevious() )
155  {
156  QString layerId = li.previous();
157 
158  QgsDebugMsg( "Rendering at layer item " + layerId );
159 
161 
162  if ( !ml )
163  {
164  mErrors.append( Error( layerId, tr( "Layer not found in registry." ) ) );
165  continue;
166  }
167 
168  QgsDebugMsg( QString( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 extent:%5 blendmode:%6" )
169  .arg( ml->name() )
170  .arg( ml->minimumScale() )
171  .arg( ml->maximumScale() )
172  .arg( ml->hasScaleBasedVisibility() )
173  .arg( ml->extent().toString() )
174  .arg( ml->blendMode() )
175  );
176 
177  if ( ml->hasScaleBasedVisibility() && ( mSettings.scale() < ml->minimumScale() || mSettings.scale() > ml->maximumScale() ) ) //|| mOverview )
178  {
179  QgsDebugMsg( "Layer not rendered because it is not within the defined visibility scale range" );
180  continue;
181  }
182 
184  const QgsCoordinateTransform* ct = 0;
185 
187  {
188  ct = mSettings.layerTransform( ml );
189  if ( ct )
190  {
191  reprojectToLayerExtent( ct, ml->crs().geographicFlag(), r1, r2 );
192  }
193  QgsDebugMsg( "extent: " + r1.toString() );
194  if ( !r1.isFinite() || !r2.isFinite() )
195  {
196  mErrors.append( Error( layerId, tr( "There was a problem transforming the layer's extent. Layer skipped." ) ) );
197  continue;
198  }
199  }
200 
201  // Force render of layers that are being edited
202  // or if there's a labeling engine that needs the layer to register features
203  if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
204  {
205  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
206  if ( vl->isEditable() || ( labelingEngine && labelingEngine->willUseLayer( vl ) ) )
207  mCache->clearCacheImage( ml->id() );
208  }
209 
210  layerJobs.append( LayerRenderJob() );
211  LayerRenderJob& job = layerJobs.last();
212  job.cached = false;
213  job.img = 0;
214  job.blendMode = ml->blendMode();
215  job.layerId = ml->id();
216 
218  job.context.setPainter( painter );
219  job.context.setLabelingEngine( labelingEngine );
221  job.context.setExtent( r1 );
222 
223  // if we can use the cache, let's do it and avoid rendering!
224  if ( mCache && !mCache->cacheImage( ml->id() ).isNull() )
225  {
226  job.cached = true;
227  job.img = new QImage( mCache->cacheImage( ml->id() ) );
228  job.renderer = 0;
229  job.context.setPainter( 0 );
230  continue;
231  }
232 
233  // If we are drawing with an alternative blending mode then we need to render to a separate image
234  // before compositing this on the map. This effectively flattens the layer and prevents
235  // blending occuring between objects on the layer
236  if ( mCache || !painter || needTemporaryImage( ml ) )
237  {
238  // Flattened image for drawing when a blending mode is set
239  QImage * mypFlattenedImage = 0;
240  mypFlattenedImage = new QImage( mSettings.outputSize().width(),
241  mSettings.outputSize().height(),
243  if ( mypFlattenedImage->isNull() )
244  {
245  mErrors.append( Error( layerId, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( mSettings.outputSize().height() ) ) );
246  delete mypFlattenedImage;
247  layerJobs.removeLast();
248  continue;
249  }
250  mypFlattenedImage->fill( 0 );
251 
252  job.img = mypFlattenedImage;
253  QPainter* mypPainter = new QPainter( job.img );
254  mypPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
255  job.context.setPainter( mypPainter );
256  }
257 
258  job.renderer = ml->createMapRenderer( job.context );
259 
260  if ( mRequestedGeomCacheForLayers.contains( ml->id() ) )
261  {
262  if ( QgsVectorLayerRenderer* vlr = dynamic_cast<QgsVectorLayerRenderer*>( job.renderer ) )
263  {
264  vlr->setGeometryCachePointer( &mGeometryCaches[ ml->id()] );
265  }
266  }
267 
268  } // while (li.hasPrevious())
269 
270  return layerJobs;
271 }
272 
273 
275 {
276  for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
277  {
278  LayerRenderJob& job = *it;
279  if ( job.img )
280  {
281  delete job.context.painter();
282  job.context.setPainter( 0 );
283 
284  if ( mCache && !job.cached && !job.context.renderingStopped() )
285  {
286  QgsDebugMsg( "caching image for " + job.layerId );
287  mCache->setCacheImage( job.layerId, *job.img );
288  }
289 
290  delete job.img;
291  job.img = 0;
292  }
293 
294  if ( job.renderer )
295  {
296  foreach ( QString message, job.renderer->errors() )
297  mErrors.append( Error( job.renderer->layerID(), message ) );
298 
299  delete job.renderer;
300  job.renderer = 0;
301  }
302  }
303 
304  jobs.clear();
305 
307 }
308 
309 
310 QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs )
311 {
312  QImage image( settings.outputSize(), settings.outputImageFormat() );
313  image.fill( settings.backgroundColor().rgb() );
314 
315  QPainter painter( &image );
316 
317  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
318  {
319  const LayerRenderJob& job = *it;
320 
321  painter.setCompositionMode( job.blendMode );
322 
323  Q_ASSERT( job.img != 0 );
324  painter.drawImage( 0, 0, *job.img );
325  }
326 
327  painter.end();
328  return image;
329 }
A rectangle specified with double values.
Definition: qgsrectangle.h:35
Base class for all map layer types.
Definition: qgsmaplayer.h:48
QgsMapLayer::LayerType type() const
Get the type of the layer.
Definition: qgsmaplayer.cpp:89
Abstract base class for map rendering implementations.
double scale() const
Return the calculated scale of the map.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:163
void cleanupJobs(LayerRenderJobs &jobs)
const QgsCoordinateTransform * layerTransform(QgsMapLayer *layer) const
Return coordinate transform from layer's CRS to destination CRS.
void updateLayerGeometryCaches()
called when rendering has finished to update all layers' geometry caches
QList< Error > Errors
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:188
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QStringList mRequestedGeomCacheForLayers
list of layer IDs for which the geometry cache should be updated
QMap< QString, QgsGeometryCache > mGeometryCaches
map of geometry caches
static QgsMapLayerRegistry * instance()
Definition: qgssingleton.h:23
float minimumScale() const
Returns the minimum scale denominator at which the layer is visible.
QgsRectangle visibleExtent() const
Return the actual extent derived from requested extent that takes takes output image size into accoun...
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
QgsPoint transform(const QgsPoint &p, TransformDirection direction=ForwardTransform) const
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)
Return new instance of QgsMapLayerRenderer that will be used for rendering of given context...
Definition: qgsmaplayer.h:133
void setCacheImage(QString layerId, const QImage &img)
set cached image for the specified layer ID
void setExtent(const QgsRectangle &extent)
LayerRenderJobs prepareJobs(QPainter *painter, QgsPalLabeling *labelingEngine)
double x() const
Definition: qgspoint.h:126
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.
QString layerID() const
Get access to the ID of the layer rendered by this class.
void setCoordinateTransform(const QgsCoordinateTransform *t)
Sets coordinate transformation.
const QString & name() const
Get the display name of the layer.
QPainter::CompositionMode blendMode() const
Read blend mode for layer.
virtual bool willUseLayer(QgsVectorLayer *layer)
called to find out whether the layer is used for labeling
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:193
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:178
void clearCacheImage(QString layerId)
remove layer from the cache
bool renderingStopped() const
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.
QImage cacheImage(QString layerId)
get cached image for the specified layer ID. Returns null image if it is not cached.
void setPainter(QPainter *p)
QString id() const
Get this layer's unique ID, this ID is used to access this layer from map layer registry.
Definition: qgsmaplayer.cpp:95
A class to represent a point.
Definition: qgspoint.h:63
QgsMapSettings mSettings
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.
QPainter * painter()
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
QgsMapLayerRenderer * renderer
Class for doing transforms between two map coordinate systems.
QgsRenderContext context
const QgsCoordinateReferenceSystem & crs() const
Returns layer'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...
QgsMapLayer * mapLayer(QString theLayerId)
Retrieve a pointer to a loaded layer by id.
This class is responsible for keeping cache of rendered images of individual layers.
static bool reprojectToLayerExtent(const QgsCoordinateTransform *ct, bool layerCrsGeographic, QgsRectangle &extent, QgsRectangle &r2)
Convenience function to project an extent into the layer source CRS, but also split it into two exten...
Custom exception class for Coordinate Reference System related exceptions.
QList< LayerRenderJob > LayerRenderJobs
Errors errors() const
List of errors that happened during the rendering job - available when the rendering has been finishe...
QPainter::CompositionMode blendMode
bool init(QgsRectangle extent, double scale)
initialize cache: set new parameters and erase cache if parameters have changed
virtual bool isEditable() const
Returns true if the provider is in editing mode.
const QgsCoordinateReferenceSystem & destCRS() const
virtual QgsRectangle extent()
Return the extent of the layer.
Represents a vector layer which manages a vector based data sets.
QString toString(bool automaticPrecision=false) const
returns string representation of form xmin,ymin xmax,ymax
QgsMapRendererCache * mCache
double xMinimum() const
Get the x minimum value (left side of rectangle)
Definition: qgsrectangle.h:183
bool needTemporaryImage(QgsMapLayer *ml)
bool isNull(const QVariant &v)
QgsMapRendererJob(const QgsMapSettings &settings)
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:158
QgsRectangle transformBoundingBox(const QgsRectangle &theRect, TransformDirection direction=ForwardTransform, const bool handle180Crossover=false) const
Structure keeping low-level rendering job information.
#define tr(sourceText)