QGIS API Documentation  2.17.0-Master (0497e4a)
qgsmaprenderercustompainterjob.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaprenderercustompainterjob.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 
17 
18 #include "qgsfeedback.h"
19 #include "qgslabelingenginev2.h"
20 #include "qgslogger.h"
21 #include "qgsmaplayerregistry.h"
22 #include "qgsmaplayerrenderer.h"
23 #include "qgspallabeling.h"
24 #include "qgsvectorlayer.h"
25 #include "qgsrendererv2.h"
26 
27 #define LABELING_V2
28 
30  : QgsMapRendererJob( settings )
31  , mPainter( painter )
32  , mLabelingEngine( nullptr )
33  , mLabelingEngineV2( nullptr )
34  , mActive( false )
35  , mRenderSynchronously( false )
36 {
37  QgsDebugMsg( "QPAINTER construct" );
38 }
39 
41 {
42  QgsDebugMsg( "QPAINTER destruct" );
43  Q_ASSERT( !mFutureWatcher.isRunning() );
44  //cancel();
45 
46  delete mLabelingEngine;
47  mLabelingEngine = nullptr;
48 
49  delete mLabelingEngineV2;
50  mLabelingEngineV2 = nullptr;
51 }
52 
54 {
55  if ( isActive() )
56  return;
57 
59 
60  mActive = true;
61 
62  mErrors.clear();
63 
64  QgsDebugMsg( "QPAINTER run!" );
65 
66  QgsDebugMsg( "Preparing list of layer jobs for rendering" );
67  QTime prepareTime;
68  prepareTime.start();
69 
70  // clear the background
72 
73  mPainter->setRenderHint( QPainter::Antialiasing, mSettings.testFlag( QgsMapSettings::Antialiasing ) );
74 
75 #ifndef QT_NO_DEBUG
76  QPaintDevice* thePaintDevice = mPainter->device();
77  QString errMsg = QString( "pre-set DPI not equal to painter's DPI (%1 vs %2)" ).arg( thePaintDevice->logicalDpiX() ).arg( mSettings.outputDpi() );
78  Q_ASSERT_X( qgsDoubleNear( thePaintDevice->logicalDpiX(), mSettings.outputDpi() ), "Job::startRender()", errMsg.toAscii().data() );
79 #endif
80 
81  delete mLabelingEngine;
82  mLabelingEngine = nullptr;
83 
84  delete mLabelingEngineV2;
85  mLabelingEngineV2 = nullptr;
86 
88  {
89 #ifdef LABELING_V2
90  mLabelingEngineV2 = new QgsLabelingEngineV2();
91  mLabelingEngineV2->readSettingsFromProject();
92  mLabelingEngineV2->setMapSettings( mSettings );
93 #else
94  mLabelingEngine = new QgsPalLabeling;
95  mLabelingEngine->loadEngineSettings();
96  mLabelingEngine->init( mSettings );
97 #endif
98  }
99 
100  mLayerJobs = prepareJobs( mPainter, mLabelingEngine, mLabelingEngineV2 );
101  // prepareJobs calls mapLayer->createMapRenderer may involve cloning a RasterDataProvider,
102  // whose constructor may need to download some data (i.e. WMS, AMS) and doing so runs a
103  // QEventLoop waiting for the network request to complete. If unluckily someone calls
104  // mapCanvas->refresh() while this is happening, QgsMapRendererCustomPainterJob::cancel is
105  // called, deleting the QgsMapRendererCustomPainterJob while this function is running.
106  // Hence we need to check whether the job is still active before proceeding
107  if ( !isActive() )
108  return;
109 
110  QgsDebugMsg( "Rendering prepared in (seconds): " + QString( "%1" ).arg( prepareTime.elapsed() / 1000.0 ) );
111 
112  if ( mRenderSynchronously )
113  {
114  // do the rendering right now!
115  doRender();
116  return;
117  }
118 
119  // now we are ready to start rendering!
120  connect( &mFutureWatcher, SIGNAL( finished() ), SLOT( futureFinished() ) );
121 
122  mFuture = QtConcurrent::run( staticRender, this );
123  mFutureWatcher.setFuture( mFuture );
124 }
125 
126 
128 {
129  if ( !isActive() )
130  {
131  QgsDebugMsg( "QPAINTER not running!" );
132  return;
133  }
134 
135  QgsDebugMsg( "QPAINTER cancelling" );
136  disconnect( &mFutureWatcher, SIGNAL( finished() ), this, SLOT( futureFinished() ) );
137 
138  mLabelingRenderContext.setRenderingStopped( true );
139  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
140  {
141  it->context.setRenderingStopped( true );
142  if ( it->renderer && it->renderer->feedback() )
143  it->renderer->feedback()->cancel();
144  }
145 
146  QTime t;
147  t.start();
148 
149  mFutureWatcher.waitForFinished();
150 
151  QgsDebugMsg( QString( "QPAINER cancel waited %1 ms" ).arg( t.elapsed() / 1000.0 ) );
152 
153  futureFinished();
154 
155  QgsDebugMsg( "QPAINTER cancelled" );
156 }
157 
159 {
160  if ( !isActive() )
161  return;
162 
163  disconnect( &mFutureWatcher, SIGNAL( finished() ), this, SLOT( futureFinished() ) );
164 
165  QTime t;
166  t.start();
167 
168  mFutureWatcher.waitForFinished();
169 
170  QgsDebugMsg( QString( "waitForFinished: %1 ms" ).arg( t.elapsed() / 1000.0 ) );
171 
172  futureFinished();
173 }
174 
176 {
177  return mActive;
178 }
179 
180 
182 {
183  if ( mLabelingEngine )
184  return mLabelingEngine->takeResults();
185  else if ( mLabelingEngineV2 )
186  return mLabelingEngineV2->takeResults();
187  else
188  return nullptr;
189 }
190 
191 
193 {
194  QEventLoop loop;
195  connect( &mFutureWatcher, SIGNAL( finished() ), &loop, SLOT( quit() ) );
196  loop.exec( flags );
197 }
198 
199 
201 {
202  mRenderSynchronously = true;
203  start();
204  futureFinished();
205  mRenderSynchronously = false;
206 }
207 
208 
210 {
211  mActive = false;
213  QgsDebugMsg( "QPAINTER futureFinished" );
214 
215  logRenderingTime( mLayerJobs );
216 
217  // final cleanup
218  cleanupJobs( mLayerJobs );
219 
220  emit finished();
221 }
222 
223 
225 {
226  try
227  {
228  self->doRender();
229  }
230  catch ( QgsException & e )
231  {
232  Q_UNUSED( e );
233  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
234  }
235  catch ( std::exception & e )
236  {
237  Q_UNUSED( e );
238  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromAscii( e.what() ) );
239  }
240  catch ( ... )
241  {
242  QgsDebugMsg( "Caught unhandled unknown exception" );
243  }
244 }
245 
247 {
248  QgsDebugMsg( "Starting to render layer stack." );
249  QTime renderTime;
250  renderTime.start();
251 
252  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
253  {
254  LayerRenderJob& job = *it;
255 
256  if ( job.context.renderingStopped() )
257  break;
258 
259  if ( job.context.useAdvancedEffects() )
260  {
261  // Set the QPainter composition mode so that this layer is rendered using
262  // the desired blending mode
263  mPainter->setCompositionMode( job.blendMode );
264  }
265 
266  if ( !job.cached )
267  {
268  QTime layerTime;
269  layerTime.start();
270 
271  job.renderer->render();
272 
273  job.renderingTime = layerTime.elapsed();
274  }
275 
276  if ( job.img )
277  {
278  // If we flattened this layer for alternate blend modes, composite it now
279  mPainter->drawImage( 0, 0, *job.img );
280  }
281 
282  }
283 
284  QgsDebugMsg( "Done rendering map layers" );
285 
286  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelingRenderContext.renderingStopped() )
287  drawLabeling( mSettings, mLabelingRenderContext, mLabelingEngine, mLabelingEngineV2, mPainter );
288 
289  QgsDebugMsg( "Rendering completed in (seconds): " + QString( "%1" ).arg( renderTime.elapsed() / 1000.0 ) );
290 }
291 
292 
293 void QgsMapRendererJob::drawLabeling( const QgsMapSettings& settings, QgsRenderContext& renderContext, QgsPalLabeling* labelingEngine, QgsLabelingEngineV2* labelingEngine2, QPainter* painter )
294 {
295  QgsDebugMsg( "Draw labeling start" );
296 
297  QTime t;
298  t.start();
299 
300  // Reset the composition mode before rendering the labels
301  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
302 
303  // TODO: this is not ideal - we could override rendering stopped flag that has been set in meanwhile
304  renderContext = QgsRenderContext::fromMapSettings( settings );
305  renderContext.setPainter( painter );
306  renderContext.setLabelingEngine( labelingEngine );
307 
308 #if !defined(QGIS_DISABLE_DEPRECATED)
309  // old labeling - to be removed at some point...
310  drawOldLabeling( settings, renderContext );
311 #endif
312  drawNewLabeling( settings, renderContext, labelingEngine );
313 
314  if ( labelingEngine2 )
315  {
316  // set correct extent
317  renderContext.setExtent( settings.visibleExtent() );
318  renderContext.setCoordinateTransform( nullptr );
319 
320  labelingEngine2->run( renderContext );
321  }
322 
323  QgsDebugMsg( QString( "Draw labeling took (seconds): %1" ).arg( t.elapsed() / 1000. ) );
324 }
325 
326 
328 {
329  // render all labels for vector layers in the stack, starting at the base
330  QListIterator<QString> li( settings.layers() );
331  li.toBack();
332  while ( li.hasPrevious() )
333  {
334  if ( renderContext.renderingStopped() )
335  {
336  break;
337  }
338 
339  QString layerId = li.previous();
340 
342 
343  if ( !ml || ( ml->type() != QgsMapLayer::VectorLayer ) )
344  continue;
345 
346  // only make labels if the layer is visible
347  // after scale dep viewing settings are checked
348  if ( !ml->isInScaleRange( settings.scale() ) )
349  continue;
350 
351  const QgsCoordinateTransform* ct = nullptr;
352  QgsRectangle r1 = settings.visibleExtent(), r2;
353 
354  if ( settings.hasCrsTransformEnabled() )
355  {
356  ct = settings.layerTransform( ml );
357  if ( ct )
358  reprojectToLayerExtent( ml, ct, r1, r2 );
359  }
360 
361  renderContext.setCoordinateTransform( ct );
362  renderContext.setExtent( r1 );
363 
364  ml->drawLabels( renderContext );
365  }
366 }
367 
368 
369 void QgsMapRendererJob::drawNewLabeling( const QgsMapSettings& settings, QgsRenderContext& renderContext, QgsPalLabeling* labelingEngine )
370 {
371  if ( labelingEngine && !renderContext.renderingStopped() )
372  {
373  // set correct extent
374  renderContext.setExtent( settings.visibleExtent() );
375  renderContext.setCoordinateTransform( nullptr );
376 
377  labelingEngine->drawLabeling( renderContext );
378  labelingEngine->exit();
379  }
380 }
381 
383 {
385  for ( ; it != mGeometryCaches.constEnd(); ++it )
386  {
387  const QgsGeometryCache& cache = it.value();
388  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( it.key() ) ) )
389  * vl->cache() = cache;
390  }
392 }
393 
394 
396 {
397  if ( ml->type() == QgsMapLayer::VectorLayer )
398  {
399  QgsVectorLayer* vl = qobject_cast<QgsVectorLayer *>( ml );
400  if ( vl->rendererV2() && vl->rendererV2()->forceRasterRender() )
401  {
402  //raster rendering is forced for this layer
403  return true;
404  }
406  (( vl->blendMode() != QPainter::CompositionMode_SourceOver )
407  || ( vl->featureBlendMode() != QPainter::CompositionMode_SourceOver )
408  || ( vl->layerTransparency() != 0 ) ) )
409  {
410  //layer properties require rasterisation
411  return true;
412  }
413  }
414  else if ( ml->type() == QgsMapLayer::RasterLayer )
415  {
416  // preview of intermediate raster rendering results requires a temporary output image
418  return true;
419  }
420 
421  return false;
422 }
423 
QgsMapRendererCustomPainterJob(const QgsMapSettings &settings, QPainter *painter)
void clear()
QString fromAscii(const char *str, int size)
void finished()
emitted when asynchronous rendering is finished (or canceled).
static void drawNewLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsPalLabeling *labelingEngine)
void setRenderingStopped(bool stopped)
A rectangle specified with double values.
Definition: qgsrectangle.h:35
Base class for all map layer types.
Definition: qgsmaplayer.h:49
Job implementation that renders everything sequentially using a custom painter.
int width() const
Abstract base class for map rendering implementations.
virtual void drawLabels(QgsRenderContext &rendererContext)
Draw labels.
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setCompositionMode(CompositionMode mode)
void setRenderHint(RenderHint hint, bool on)
void cleanupJobs(LayerRenderJobs &jobs)
QgsLabelingResults * takeResults()
Return pointer to recently computed results (in drawLabeling()) and pass the ownership of results to ...
void updateLayerGeometryCaches()
called when rendering has finished to update all layers&#39; geometry caches
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
void loadEngineSettings()
load/save engine settings to project file
QgsMapLayer * mapLayer(const QString &theLayerId) const
Retrieve a pointer to a registered layer by layer ID.
void logRenderingTime(const LayerRenderJobs &jobs)
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
QColor backgroundColor() const
Get the background color of the map.
const_iterator constBegin() const
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
bool renderingStopped() const
The QgsLabelingEngineV2 class provides map labeling functionality.
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
virtual void cancel() override
Stop the rendering job - does not return until the job has terminated.
void readSettingsFromProject()
Read configuration of the labeling engine from the current project file.
void clear()
Enable layer transparency and blending effects.
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QgsMapLayer::LayerType type() const
Get the type of the layer.
Definition: qgsmaplayer.cpp:97
void setExtent(const QgsRectangle &extent)
QgsRectangle visibleExtent() const
Return the actual extent derived from requested extent that takes takes output image size into accoun...
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:353
Enable drawing of labels on top of the map.
QString what() const
Definition: qgsexception.h:36
The QgsMapSettings class contains configuration for rendering of the map.
void setCoordinateTransform(const QgsCoordinateTransform *t)
Sets coordinate transformation.
QgsFeatureRendererV2 * rendererV2()
Return renderer V2.
int elapsed() const
virtual void start() override
Start the rendering job and immediately return.
int exec(QFlags< QEventLoop::ProcessEventsFlag > flags)
double scale() const
Return the calculated scale of the map.
static void staticRender(QgsMapRendererCustomPainterJob *self)
virtual void drawLabeling(QgsRenderContext &context) override
called when the map is drawn and labels should be placed
const_iterator constEnd() const
Enable anti-aliasing for map rendering.
QPaintDevice * device() const
void setPainter(QPainter *p)
QFuture< T > run(Function function,...)
int layerTransparency() const
Returns the current transparency for the vector layer.
const QgsCoordinateTransform * layerTransform(QgsMapLayer *layer) const
Return coordinate transform from layer&#39;s CRS to destination CRS.
void setFuture(const QFuture< T > &future)
QPainter::CompositionMode featureBlendMode() const
Returns the current blending mode for features.
QgsMapSettings mSettings
int logicalDpiX() const
iterator end()
void setMapSettings(const QgsMapSettings &mapSettings)
Associate map settings instance.
void run(QgsRenderContext &context)
compute the labeling with given map settings and providers
static void drawOldLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext)
const Key key(const T &value) const
bool isRunning() const
void waitForFinished()
double outputDpi() const
Return DPI used for conversion between real world units (e.g.
static void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsPalLabeling *labelingEngine, QgsLabelingEngineV2 *labelingEngine2, QPainter *painter)
typedef ProcessEventsFlags
Contains information about the context of a rendering operation.
virtual bool render()=0
Do the rendering (based on data stored in the class)
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)
LayerRenderJobs prepareJobs(QPainter *painter, QgsPalLabeling *labelingEngine, QgsLabelingEngineV2 *labelingEngine2)
int renderingTime
time it took to render the layer in ms (it is -1 if not rendered or still rendering) ...
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
void setLabelingEngine(QgsLabelingEngineInterface *iface)
bool useAdvancedEffects() const
Returns true if advanced effects such as blend modes such be used.
int height() const
QgsMapLayerRenderer * renderer
Class for doing transforms between two map coordinate systems.
char * data()
QgsRenderContext context
void start()
bool forceRasterRender() const
Returns whether the renderer must render as a raster.
Class that stores computed placement from labeling engine.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
virtual QgsLabelingResults * takeLabelingResults() override
Get pointer to internal labeling engine (in order to get access to the results)
QStringList layers() const
Get list of layer IDs for map rendering The layers are stored in the reverse order of how they are re...
QPainter::CompositionMode blendMode
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual void waitForFinished() override
Block until the job has finished.
Represents a vector layer which manages a vector based data sets.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
bool needTemporaryImage(QgsMapLayer *ml)
void waitForFinishedWithEventLoop(const QEventLoop::ProcessEventsFlags &flags=QEventLoop::AllEvents)
Wait for the job to be finished - and keep the thread&#39;s event loop running while waiting.
Defines a qgis exception class.
Definition: qgsexception.h:25
void renderSynchronously()
Render the map synchronously in this thread.
QgsLabelingResults * takeResults()
Return pointer to recently computed results and pass the ownership of results to the caller...
iterator begin()
QSize outputSize() const
Return the size of the resulting map image.
QByteArray toAscii() const
virtual void exit() override
called when we&#39;re done with rendering
QPainter::CompositionMode blendMode() const
Returns the current blending mode for a layer.
Structure keeping low-level rendering job information.
const T value(const Key &key) const
virtual bool isActive() const override
Tell whether the rendering job is currently running in background.