QGIS API Documentation  2.5.0-Master
 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, tr( "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, tr( "There was a problem transforming the 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, tr( "Insufficient memory for image %1x%2" ).arg( mSettings.outputSize().width() ).arg( 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 }