QGIS API Documentation  2.4.0-Chugiak
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups 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 
59 
60 bool QgsMapRendererJob::reprojectToLayerExtent( const QgsCoordinateTransform* ct, bool layerCrsGeographic, QgsRectangle& extent, QgsRectangle& r2 )
61 {
62  bool split = false;
63 
64  try
65  {
66 #ifdef QGISDEBUG
67  // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__);
68 #endif
69  // Split the extent into two if the source CRS is
70  // geographic and the extent crosses the split in
71  // geographic coordinates (usually +/- 180 degrees,
72  // and is assumed to be so here), and draw each
73  // extent separately.
74  static const double splitCoord = 180.0;
75 
76  if ( layerCrsGeographic )
77  {
78  // Note: ll = lower left point
79  // and ur = upper right point
80  QgsPoint ll = ct->transform( extent.xMinimum(), extent.yMinimum(),
82 
83  QgsPoint ur = ct->transform( extent.xMaximum(), extent.yMaximum(),
85 
87 
88  if ( ll.x() > ur.x() )
89  {
90  // the coordinates projected in reverse order than what one would expect.
91  // we are probably looking at an area that includes longitude of 180 degrees.
92  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
93  // so let's use (-180,180). This hopefully does not add too much overhead. It is
94  // more straightforward than rendering with two separate extents and more consistent
95  // for rendering, labeling and caching as everything is rendered just in one go
96  extent.setXMinimum( -splitCoord );
97  extent.setXMaximum( splitCoord );
98  }
99 
100  // TODO: the above rule still does not help if using a projection that covers the whole
101  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
102  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
103  // but in fact the extent should cover the whole world.
104  }
105  else // can't cross 180
106  {
107  if ( ct->destCRS().geographicFlag() &&
108  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
109  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
110  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
111  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
112  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
113  // but this seems like a safer choice.
114  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
115  else
117  }
118  }
119  catch ( QgsCsException &cse )
120  {
121  Q_UNUSED( cse );
122  QgsDebugMsg( "Transform error caught" );
123  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
124  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
125  }
126 
127  return split;
128 }
129 
130 
131 
132 LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsPalLabeling* labelingEngine )
133 {
134  LayerRenderJobs layerJobs;
135 
136  // render all layers in the stack, starting at the base
137  QListIterator<QString> li( mSettings.layers() );
138  li.toBack();
139 
140  if ( mCache )
141  {
142  bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
143  QgsDebugMsg( QString( "CACHE VALID: %1" ).arg( cacheValid ) );
144  Q_UNUSED( cacheValid );
145  }
146 
147  mGeometryCaches.clear();
148 
149  while ( li.hasPrevious() )
150  {
151  QString layerId = li.previous();
152 
153  QgsDebugMsg( "Rendering at layer item " + layerId );
154 
156 
157  if ( !ml )
158  {
159  mErrors.append( Error( layerId, "Layer not found in registry." ) );
160  continue;
161  }
162 
163  QgsDebugMsg( QString( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 extent:%5 blendmode:%6" )
164  .arg( ml->name() )
165  .arg( ml->minimumScale() )
166  .arg( ml->maximumScale() )
167  .arg( ml->hasScaleBasedVisibility() )
168  .arg( ml->extent().toString() )
169  .arg( ml->blendMode() )
170  );
171 
172  if ( ml->hasScaleBasedVisibility() && ( mSettings.scale() < ml->minimumScale() || mSettings.scale() > ml->maximumScale() ) ) //|| mOverview )
173  {
174  QgsDebugMsg( "Layer not rendered because it is not within the defined visibility scale range" );
175  continue;
176  }
177 
179  const QgsCoordinateTransform* ct = 0;
180 
182  {
183  ct = mSettings.layerTransfrom( ml );
184  if ( ct )
185  {
186  reprojectToLayerExtent( ct, ml->crs().geographicFlag(), r1, r2 );
187  }
188  QgsDebugMsg( "extent: " + r1.toString() );
189  if ( !r1.isFinite() || !r2.isFinite() )
190  {
191  mErrors.append( Error( layerId, "There was a problem transforming layer's' extent. Layer skipped." ) );
192  continue;
193  }
194  }
195 
196  // Force render of layers that are being edited
197  // or if there's a labeling engine that needs the layer to register features
198  if ( mCache && ml->type() == QgsMapLayer::VectorLayer )
199  {
200  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
201  if ( vl->isEditable() || ( labelingEngine && labelingEngine->willUseLayer( vl ) ) )
202  mCache->clearCacheImage( ml->id() );
203  }
204 
205  layerJobs.append( LayerRenderJob() );
206  LayerRenderJob& job = layerJobs.last();
207  job.cached = false;
208  job.img = 0;
209  job.blendMode = ml->blendMode();
210  job.layerId = ml->id();
211 
213  job.context.setPainter( painter );
214  job.context.setLabelingEngine( labelingEngine );
216  job.context.setExtent( r1 );
217 
218  // if we can use the cache, let's do it and avoid rendering!
219  if ( mCache && !mCache->cacheImage( ml->id() ).isNull() )
220  {
221  job.cached = true;
222  job.img = new QImage( mCache->cacheImage( ml->id() ) );
223  job.renderer = 0;
224  job.context.setPainter( 0 );
225  continue;
226  }
227 
228  // If we are drawing with an alternative blending mode then we need to render to a separate image
229  // before compositing this on the map. This effectively flattens the layer and prevents
230  // blending occuring between objects on the layer
231  if ( mCache || !painter || needTemporaryImage( ml ) )
232  {
233  // Flattened image for drawing when a blending mode is set
234  QImage * mypFlattenedImage = 0;
235  mypFlattenedImage = new QImage( mSettings.outputSize().width(),
236  mSettings.outputSize().height(),
238  if ( mypFlattenedImage->isNull() )
239  {
240  mErrors.append( Error( layerId, "Insufficient memory for image " + QString::number( mSettings.outputSize().width() ) + "x" + QString::number( mSettings.outputSize().height() ) ) );
241  delete mypFlattenedImage;
242  layerJobs.removeLast();
243  continue;
244  }
245  mypFlattenedImage->fill( 0 );
246 
247  job.img = mypFlattenedImage;
248  QPainter* mypPainter = new QPainter( job.img );
249  mypPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
250  job.context.setPainter( mypPainter );
251  }
252 
253  job.renderer = ml->createMapRenderer( job.context );
254 
255  if ( mRequestedGeomCacheForLayers.contains( ml->id() ) )
256  {
257  if ( QgsVectorLayerRenderer* vlr = dynamic_cast<QgsVectorLayerRenderer*>( job.renderer ) )
258  {
259  vlr->setGeometryCachePointer( &mGeometryCaches[ ml->id()] );
260  }
261  }
262 
263  } // while (li.hasPrevious())
264 
265  return layerJobs;
266 }
267 
268 
270 {
271  for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
272  {
273  LayerRenderJob& job = *it;
274  if ( job.img )
275  {
276  delete job.context.painter();
277  job.context.setPainter( 0 );
278 
279  if ( mCache && !job.cached && !job.context.renderingStopped() )
280  {
281  QgsDebugMsg( "caching image for " + job.layerId );
282  mCache->setCacheImage( job.layerId, *job.img );
283  }
284 
285  delete job.img;
286  job.img = 0;
287  }
288 
289  if ( job.renderer )
290  {
291  foreach ( QString message, job.renderer->errors() )
292  mErrors.append( Error( job.renderer->layerID(), message ) );
293 
294  delete job.renderer;
295  job.renderer = 0;
296  }
297  }
298 
299  jobs.clear();
300 
302 }
303 
304 
305 QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs )
306 {
307  QImage image( settings.outputSize(), settings.outputImageFormat() );
308  image.fill( settings.backgroundColor().rgb() );
309 
310  QPainter painter( &image );
311 
312  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
313  {
314  const LayerRenderJob& job = *it;
315 
316  painter.setCompositionMode( job.blendMode );
317 
318  Q_ASSERT( job.img != 0 );
319  painter.drawImage( 0, 0, *job.img );
320  }
321 
322  painter.end();
323  return image;
324 }
A rectangle specified with double values.
Definition: qgsrectangle.h:35
Base class for all map layer types.
Definition: qgsmaplayer.h:47
QgsMapLayer::LayerType type() const
Get the type of the layer.
Definition: qgsmaplayer.cpp:86
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:169
void cleanupJobs(LayerRenderJobs &jobs)
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:194
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
QStringList mRequestedGeomCacheForLayers
list of layer IDs for which the geometry cache should be updated
QMap< QString, QgsGeometryCache > mGeometryCaches
map of geometry caches
float minimumScale() const
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
virtual QgsMapLayerRenderer * createMapRenderer(QgsRenderContext &rendererContext)
Return new instance of QgsMapLayerRenderer that will be used for rendering of given context...
Definition: qgsmaplayer.h:136
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:110
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
double yMinimum() const
Get the y minimum value (bottom side of rectangle)
Definition: qgsrectangle.h:199
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:184
void clearCacheImage(QString layerId)
remove layer from the cache
bool renderingStopped() const
float maximumScale() const
QStringList errors() const
Return list of errors (problems) that happened during the rendering.
Enable anti-aliasin for map rendering.
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:92
QgsPoint transform(const QgsPoint p, TransformDirection direction=ForwardTransform) const
A class to represent a point geometry.
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.
QgsRectangle transformBoundingBox(const QgsRectangle theRect, TransformDirection direction=ForwardTransform) const
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
QPainter * painter()
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
void setLabelingEngine(QgsLabelingEngineInterface *iface)
Added in QGIS v1.4.
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
const QgsCoordinateTransform * layerTransfrom(QgsMapLayer *layer) const
Return coordinate transform from layer's CRS to destination CRS.
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:189
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:164
Structure keeping low-level rendering job information.