QGIS API Documentation  2.99.0-Master (b8fd1fd)
qgsmaprendererparalleljob.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaprendererparalleljob.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 "qgslabelingengine.h"
20 #include "qgslogger.h"
21 #include "qgsmaplayerrenderer.h"
22 #include "qgspallabeling.h"
23 #include "qgsproject.h"
24 #include "qgsmaplayer.h"
25 #include "qgsmaplayerlistutils.h"
26 
27 #include <QtConcurrentMap>
28 
30  : QgsMapRendererQImageJob( settings )
31  , mStatus( Idle )
32 {
33 }
34 
36 {
37  if ( isActive() )
38  {
39  cancel();
40  }
41 }
42 
44 {
45  if ( isActive() )
46  return;
47 
48  mRenderingStart.start();
49 
50  mStatus = RenderingLayers;
51 
52  mLabelingEngineV2.reset();
53 
55  {
56  mLabelingEngineV2.reset( new QgsLabelingEngine() );
57  mLabelingEngineV2->readSettingsFromProject( QgsProject::instance() );
58  mLabelingEngineV2->setMapSettings( mSettings );
59  }
60 
61  bool canUseLabelCache = prepareLabelCache();
62  mLayerJobs = prepareJobs( nullptr, mLabelingEngineV2.get() );
63  mLabelJob = prepareLabelingJob( nullptr, mLabelingEngineV2.get(), canUseLabelCache );
64 
65  QgsDebugMsg( QString( "QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ) );
66 
67  // start async job
68 
69  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
70 
71  mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
72  mFutureWatcher.setFuture( mFuture );
73 }
74 
76 {
77  if ( !isActive() )
78  return;
79 
80  QgsDebugMsg( QString( "PARALLEL cancel at status %1" ).arg( mStatus ) );
81 
82  mLabelJob.context.setRenderingStopped( true );
83  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
84  {
85  it->context.setRenderingStopped( true );
86  if ( it->renderer && it->renderer->feedback() )
87  it->renderer->feedback()->cancel();
88  }
89 
90  if ( mStatus == RenderingLayers )
91  {
92  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
93 
94  mFutureWatcher.waitForFinished();
95 
96  renderLayersFinished();
97  }
98 
99  if ( mStatus == RenderingLabels )
100  {
101  disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
102 
103  mLabelingFutureWatcher.waitForFinished();
104 
105  renderingFinished();
106  }
107 
108  Q_ASSERT( mStatus == Idle );
109 }
110 
112 {
113  if ( !isActive() )
114  return;
115 
116  QgsDebugMsg( QString( "PARALLEL cancel at status %1" ).arg( mStatus ) );
117 
118  mLabelJob.context.setRenderingStopped( true );
119  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
120  {
121  it->context.setRenderingStopped( true );
122  if ( it->renderer && it->renderer->feedback() )
123  it->renderer->feedback()->cancel();
124  }
125 
126  if ( mStatus == RenderingLayers )
127  {
128  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
129  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
130  }
131 }
132 
134 {
135  if ( !isActive() )
136  return;
137 
138  if ( mStatus == RenderingLayers )
139  {
140  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
141 
142  QTime t;
143  t.start();
144 
145  mFutureWatcher.waitForFinished();
146 
147  QgsDebugMsg( QString( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ) );
148 
149  renderLayersFinished();
150  }
151 
152  if ( mStatus == RenderingLabels )
153  {
154  disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
155 
156  QTime t;
157  t.start();
158 
159  mLabelingFutureWatcher.waitForFinished();
160 
161  QgsDebugMsg( QString( "waitForFinished (2): %1 ms" ).arg( t.elapsed() / 1000.0 ) );
162 
163  renderingFinished();
164  }
165 
166  Q_ASSERT( mStatus == Idle );
167 }
168 
170 {
171  return mStatus != Idle;
172 }
173 
175 {
176  return mLabelJob.cached;
177 }
178 
180 {
181  if ( mLabelingEngineV2 )
182  return mLabelingEngineV2->takeResults();
183  else
184  return nullptr;
185 }
186 
188 {
189  if ( mStatus == RenderingLayers )
190  return composeImage( mSettings, mLayerJobs, mLabelJob );
191  else
192  return mFinalImage; // when rendering labels or idle
193 }
194 
195 void QgsMapRendererParallelJob::renderLayersFinished()
196 {
197  Q_ASSERT( mStatus == RenderingLayers );
198 
199  // compose final image
200  mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
201 
202  QgsDebugMsg( "PARALLEL layers finished" );
203 
204  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
205  {
206  mStatus = RenderingLabels;
207 
208  connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
209 
210  // now start rendering of labeling!
211  mLabelingFuture = QtConcurrent::run( renderLabelsStatic, this );
212  mLabelingFutureWatcher.setFuture( mLabelingFuture );
214  }
215  else
216  {
217  renderingFinished();
218  }
219 }
220 
221 void QgsMapRendererParallelJob::renderingFinished()
222 {
223  QgsDebugMsg( "PARALLEL finished" );
224 
225  logRenderingTime( mLayerJobs, mLabelJob );
226 
227  cleanupJobs( mLayerJobs );
228 
229  cleanupLabelJob( mLabelJob );
230 
231  mStatus = Idle;
232 
233  mRenderingTime = mRenderingStart.elapsed();
234 
235  emit finished();
236 }
237 
238 void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
239 {
240  if ( job.context.renderingStopped() )
241  return;
242 
243  if ( job.cached )
244  return;
245 
246  if ( job.img )
247  job.img->fill( 0 );
248 
249  QTime t;
250  t.start();
251  QgsDebugMsgLevel( QString( "job %1 start (layer %2)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.layer ? job.layer->id() : QString() ), 2 );
252 
253  try
254  {
255  job.renderer->render();
256  }
257  catch ( QgsException &e )
258  {
259  Q_UNUSED( e );
260  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
261  }
262  catch ( std::exception &e )
263  {
264  Q_UNUSED( e );
265  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromAscii( e.what() ) );
266  }
267  catch ( ... )
268  {
269  QgsDebugMsg( "Caught unhandled unknown exception" );
270  }
271 
272  job.renderingTime = t.elapsed();
273  QgsDebugMsgLevel( QString( "job %1 end [%2 ms] (layer %3)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layer ? job.layer->id() : QString() ), 2 );
274 }
275 
276 
277 void QgsMapRendererParallelJob::renderLabelsStatic( QgsMapRendererParallelJob *self )
278 {
279  LabelRenderJob &job = self->mLabelJob;
280 
281  if ( !job.cached )
282  {
283  QTime labelTime;
284  labelTime.start();
285 
286  QPainter painter;
287  if ( job.img )
288  {
289  job.img->fill( 0 );
290  painter.begin( job.img );
291  }
292  else
293  {
294  painter.begin( &self->mFinalImage );
295  }
296 
297  // draw the labels!
298  try
299  {
300  drawLabeling( self->mSettings, job.context, self->mLabelingEngineV2.get(), &painter );
301  }
302  catch ( QgsException &e )
303  {
304  Q_UNUSED( e );
305  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
306  }
307  catch ( std::exception &e )
308  {
309  Q_UNUSED( e );
310  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromAscii( e.what() ) );
311  }
312  catch ( ... )
313  {
314  QgsDebugMsg( "Caught unhandled unknown exception" );
315  }
316 
317  painter.end();
318 
319  job.renderingTime = labelTime.elapsed();
320  job.complete = true;
321  job.participatingLayers = _qgis_listRawToQPointer( self->mLabelingEngineV2->participatingLayers() );
322  if ( job.img )
323  {
324  self->mFinalImage = composeImage( self->mSettings, self->mLayerJobs, self->mLabelJob );
325  }
326  }
327 }
328 
void finished()
emitted when asynchronous rendering is finished (or canceled).
virtual void waitForFinished() override
Block until the job has finished.
void cleanupJobs(LayerRenderJobs &jobs)
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
static void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
void renderingLayersFinished()
Emitted when the layers are rendered.
virtual void cancel() override
Stop the rendering job - does not return until the job has terminated.
Enable drawing of labels on top of the map.
QString what() const
Definition: qgsexception.h:38
The QgsMapSettings class contains configuration for rendering of the map.
QgsMapRendererParallelJob(const QgsMapSettings &settings)
static QImage composeImage(const QgsMapSettings &settings, const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
LabelRenderJob prepareLabelingJob(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool canUseLabelCache=true)
Prepares a labeling job.
virtual bool usedCachedLabels() const override
Returns true if the render job was able to use a cached labeling solution.
bool prepareLabelCache() const
Prepares the cache for storing the result of labeling.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:37
Job implementation that renders all layers in parallel.
QgsMapSettings mSettings
void cleanupLabelJob(LabelRenderJob &job)
Handles clean up tasks for a label job, including deletion of images and storing cached label results...
The QgsLabelingEngine class provides map labeling functionality.
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2)
void logRenderingTime(const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
virtual void cancelWithoutBlocking() override
Triggers cancelation of the rendering job without blocking.
Intermediate base class adding functionality that allows client to query the rendered image...
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:359
virtual void start() override
Start the rendering job and immediately return.
Class that stores computed placement from labeling engine.
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
Defines a qgis exception class.
Definition: qgsexception.h:27
virtual bool isActive() const override
Tell whether the rendering job is currently running in background.
virtual QImage renderedImage() override
Get a preview/resulting image.
virtual QgsLabelingResults * takeLabelingResults() override
Get pointer to internal labeling engine (in order to get access to the results).