QGIS API Documentation  3.2.0-Bonn (bc43194)
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 "qgsproject.h"
23 #include "qgsmaplayer.h"
24 #include "qgsmaplayerlistutils.h"
25 
26 #include <QtConcurrentMap>
27 #include <QtConcurrentRun>
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->setMapSettings( mSettings );
58  }
59 
60  bool canUseLabelCache = prepareLabelCache();
61  mLayerJobs = prepareJobs( nullptr, mLabelingEngineV2.get() );
62  mLabelJob = prepareLabelingJob( nullptr, mLabelingEngineV2.get(), canUseLabelCache );
63 
64  QgsDebugMsg( QString( "QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ) );
65 
66  // start async job
67 
68  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
69 
70  mFuture = QtConcurrent::map( mLayerJobs, renderLayerStatic );
71  mFutureWatcher.setFuture( mFuture );
72 }
73 
75 {
76  if ( !isActive() )
77  return;
78 
79  QgsDebugMsg( QString( "PARALLEL cancel at status %1" ).arg( mStatus ) );
80 
81  mLabelJob.context.setRenderingStopped( true );
82  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
83  {
84  it->context.setRenderingStopped( true );
85  if ( it->renderer && it->renderer->feedback() )
86  it->renderer->feedback()->cancel();
87  }
88 
89  if ( mStatus == RenderingLayers )
90  {
91  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
92 
93  mFutureWatcher.waitForFinished();
94 
95  renderLayersFinished();
96  }
97 
98  if ( mStatus == RenderingLabels )
99  {
100  disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
101 
102  mLabelingFutureWatcher.waitForFinished();
103 
104  renderingFinished();
105  }
106 
107  Q_ASSERT( mStatus == Idle );
108 }
109 
111 {
112  if ( !isActive() )
113  return;
114 
115  QgsDebugMsg( QString( "PARALLEL cancel at status %1" ).arg( mStatus ) );
116 
117  mLabelJob.context.setRenderingStopped( true );
118  for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
119  {
120  it->context.setRenderingStopped( true );
121  if ( it->renderer && it->renderer->feedback() )
122  it->renderer->feedback()->cancel();
123  }
124 
125  if ( mStatus == RenderingLayers )
126  {
127  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
128  connect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
129  }
130 }
131 
133 {
134  if ( !isActive() )
135  return;
136 
137  if ( mStatus == RenderingLayers )
138  {
139  disconnect( &mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderLayersFinished );
140 
141  QTime t;
142  t.start();
143 
144  mFutureWatcher.waitForFinished();
145 
146  QgsDebugMsg( QString( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ) );
147 
148  renderLayersFinished();
149  }
150 
151  if ( mStatus == RenderingLabels )
152  {
153  disconnect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
154 
155  QTime t;
156  t.start();
157 
158  mLabelingFutureWatcher.waitForFinished();
159 
160  QgsDebugMsg( QString( "waitForFinished (2): %1 ms" ).arg( t.elapsed() / 1000.0 ) );
161 
162  renderingFinished();
163  }
164 
165  Q_ASSERT( mStatus == Idle );
166 }
167 
169 {
170  return mStatus != Idle;
171 }
172 
174 {
175  return mLabelJob.cached;
176 }
177 
179 {
180  if ( mLabelingEngineV2 )
181  return mLabelingEngineV2->takeResults();
182  else
183  return nullptr;
184 }
185 
187 {
188  if ( mStatus == RenderingLayers )
189  return composeImage( mSettings, mLayerJobs, mLabelJob );
190  else
191  return mFinalImage; // when rendering labels or idle
192 }
193 
194 void QgsMapRendererParallelJob::renderLayersFinished()
195 {
196  Q_ASSERT( mStatus == RenderingLayers );
197 
198  // compose final image
199  mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
200 
201  QgsDebugMsg( "PARALLEL layers finished" );
202 
203  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
204  {
205  mStatus = RenderingLabels;
206 
207  connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
208 
209  // now start rendering of labeling!
210  mLabelingFuture = QtConcurrent::run( renderLabelsStatic, this );
211  mLabelingFutureWatcher.setFuture( mLabelingFuture );
213  }
214  else
215  {
216  renderingFinished();
217  }
218 }
219 
220 void QgsMapRendererParallelJob::renderingFinished()
221 {
222  QgsDebugMsg( "PARALLEL finished" );
223 
224  logRenderingTime( mLayerJobs, mLabelJob );
225 
226  cleanupJobs( mLayerJobs );
227 
228  cleanupLabelJob( mLabelJob );
229 
230  mStatus = Idle;
231 
232  mRenderingTime = mRenderingStart.elapsed();
233 
234  emit finished();
235 }
236 
237 void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
238 {
239  if ( job.context.renderingStopped() )
240  return;
241 
242  if ( job.cached )
243  return;
244 
245  if ( job.img )
246  {
247  job.img->fill( 0 );
248  job.imageInitialized = true;
249  }
250 
251  QTime t;
252  t.start();
253  QgsDebugMsgLevel( QString( "job %1 start (layer %2)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.layer ? job.layer->id() : QString() ), 2 );
254  try
255  {
256  job.renderer->render();
257  }
258  catch ( QgsException &e )
259  {
260  Q_UNUSED( e );
261  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
262  }
263  catch ( std::exception &e )
264  {
265  Q_UNUSED( e );
266  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
267  }
268  catch ( ... )
269  {
270  QgsDebugMsg( "Caught unhandled unknown exception" );
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::fromLatin1( 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).
void waitForFinished() override
Block until the job has finished.
void cleanupJobs(LayerRenderJobs &jobs)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
static void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
void renderingLayersFinished()
Emitted when the layers are rendered.
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:48
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.
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:39
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)
void cancelWithoutBlocking() override
Triggers cancelation of the rendering job without blocking.
Intermediate base class adding functionality that allows client to query the rendered image...
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:34
bool isActive() const override
Tell whether the rendering job is currently running in background.
QImage renderedImage() override
Gets a preview/resulting image.
QgsLabelingResults * takeLabelingResults() override
Gets pointer to internal labeling engine (in order to get access to the results). ...