QGIS API Documentation  2.8.2-Wien
 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"
31 #include "qgsmaprenderercache.h"
32 #include "qgspallabeling.h"
33 #include "qgsvectorlayerrenderer.h"
34 
35 
37  : mSettings( settings )
38  , mCache( 0 )
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 
66 bool QgsMapRendererJob::reprojectToLayerExtent( const QgsCoordinateTransform* ct, bool layerCrsGeographic, QgsRectangle& extent, QgsRectangle& r2 )
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 ( layerCrsGeographic )
83  {
84  // Note: ll = lower left point
85  // and ur = upper right point
86  QgsPoint ll = ct->transform( extent.xMinimum(), extent.yMinimum(),
88 
89  QgsPoint ur = ct->transform( extent.xMaximum(), extent.yMaximum(),
91 
93 
94  if ( ll.x() > ur.x() )
95  {
96  // the coordinates projected in reverse order than what one would expect.
97  // we are probably looking at an area that includes longitude of 180 degrees.
98  // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180)
99  // so let's use (-180,180). This hopefully does not add too much overhead. It is
100  // more straightforward than rendering with two separate extents and more consistent
101  // for rendering, labeling and caching as everything is rendered just in one go
102  extent.setXMinimum( -splitCoord );
103  extent.setXMaximum( splitCoord );
104  }
105 
106  // TODO: the above rule still does not help if using a projection that covers the whole
107  // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx.
108  // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90,
109  // but in fact the extent should cover the whole world.
110  }
111  else // can't cross 180
112  {
113  if ( ct->destCRS().geographicFlag() &&
114  ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 ||
115  extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) )
116  // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates.
117  // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity.
118  // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat,
119  // but this seems like a safer choice.
120  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
121  else
123  }
124  }
125  catch ( QgsCsException &cse )
126  {
127  Q_UNUSED( cse );
128  QgsDebugMsg( "Transform error caught" );
129  extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
130  r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX );
131  }
132 
133  return split;
134 }
135 
136 
137 
138 LayerRenderJobs QgsMapRendererJob::prepareJobs( QPainter* painter, QgsPalLabeling* labelingEngine )
139 {
140  LayerRenderJobs layerJobs;
141 
142  // render all layers in the stack, starting at the base
143  QListIterator<QString> li( mSettings.layers() );
144  li.toBack();
145 
146  if ( mCache )
147  {
148  bool cacheValid = mCache->init( mSettings.visibleExtent(), mSettings.scale() );
149  QgsDebugMsg( QString( "CACHE VALID: %1" ).arg( cacheValid ) );
150  Q_UNUSED( cacheValid );
151  }
152 
153  mGeometryCaches.clear();
154 
155  while ( li.hasPrevious() )
156  {
157  QString layerId = li.previous();
158 
159  QgsDebugMsg( "Rendering at layer item " + layerId );
160 
162 
163  if ( !ml )
164  {
165  mErrors.append( Error( layerId, tr( "Layer not found in registry." ) ) );
166  continue;
167  }
168 
169  QgsDebugMsg( QString( "layer %1: minscale:%2 maxscale:%3 scaledepvis:%4 blendmode:%5" )
170  .arg( ml->name() )
171  .arg( ml->minimumScale() )
172  .arg( ml->maximumScale() )
173  .arg( ml->hasScaleBasedVisibility() )
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  bool hasStyleOverride = mSettings.layerStyleOverrides().contains( ml->id() );
259  if ( hasStyleOverride )
260  ml->styleManager()->setOverrideStyle( mSettings.layerStyleOverrides().value( ml->id() ) );
261 
262  job.renderer = ml->createMapRenderer( job.context );
263 
264  if ( hasStyleOverride )
266 
267  if ( mRequestedGeomCacheForLayers.contains( ml->id() ) )
268  {
269  if ( QgsVectorLayerRenderer* vlr = dynamic_cast<QgsVectorLayerRenderer*>( job.renderer ) )
270  {
271  vlr->setGeometryCachePointer( &mGeometryCaches[ ml->id()] );
272  }
273  }
274 
275  } // while (li.hasPrevious())
276 
277  return layerJobs;
278 }
279 
280 
282 {
283  for ( LayerRenderJobs::iterator it = jobs.begin(); it != jobs.end(); ++it )
284  {
285  LayerRenderJob& job = *it;
286  if ( job.img )
287  {
288  delete job.context.painter();
289  job.context.setPainter( 0 );
290 
291  if ( mCache && !job.cached && !job.context.renderingStopped() )
292  {
293  QgsDebugMsg( "caching image for " + job.layerId );
294  mCache->setCacheImage( job.layerId, *job.img );
295  }
296 
297  delete job.img;
298  job.img = 0;
299  }
300 
301  if ( job.renderer )
302  {
303  foreach ( QString message, job.renderer->errors() )
304  mErrors.append( Error( job.renderer->layerID(), message ) );
305 
306  delete job.renderer;
307  job.renderer = 0;
308  }
309  }
310 
311  jobs.clear();
312 
314 }
315 
316 
317 QImage QgsMapRendererJob::composeImage( const QgsMapSettings& settings, const LayerRenderJobs& jobs )
318 {
319  QImage image( settings.outputSize(), settings.outputImageFormat() );
320  image.fill( settings.backgroundColor().rgb() );
321 
322  QPainter painter( &image );
323 
324  for ( LayerRenderJobs::const_iterator it = jobs.constBegin(); it != jobs.constEnd(); ++it )
325  {
326  const LayerRenderJob& job = *it;
327 
328  painter.setCompositionMode( job.blendMode );
329 
330  Q_ASSERT( job.img != 0 );
331  painter.drawImage( 0, 0, *job.img );
332  }
333 
334  painter.end();
335  return image;
336 }