QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 QgsDefaultLabelingEngine() );
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  QgsDebugMsgLevel( QStringLiteral( "QThreadPool max thread count is %1" ).arg( QThreadPool::globalInstance()->maxThreadCount() ), 2 );
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  QgsDebugMsgLevel( QStringLiteral( "PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
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  QgsDebugMsgLevel( QStringLiteral( "PARALLEL cancel at status %1" ).arg( mStatus ), 2 );
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  QgsDebugMsgLevel( QStringLiteral( "waitForFinished (1): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
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  QgsDebugMsgLevel( QStringLiteral( "waitForFinished (2): %1 ms" ).arg( t.elapsed() / 1000.0 ), 2 );
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  LayerRenderJobs::const_iterator it = mLayerJobs.constBegin();
199  for ( ; it != mLayerJobs.constEnd(); ++it )
200  {
201  if ( !it->errors.isEmpty() )
202  {
203  mErrors.append( Error( it->layer->id(), it->errors.join( ',' ) ) );
204  }
205  }
206 
207  // compose final image
208  mFinalImage = composeImage( mSettings, mLayerJobs, mLabelJob );
209 
210  QgsDebugMsgLevel( QStringLiteral( "PARALLEL layers finished" ), 2 );
211 
212  if ( mSettings.testFlag( QgsMapSettings::DrawLabeling ) && !mLabelJob.context.renderingStopped() )
213  {
214  mStatus = RenderingLabels;
215 
216  connect( &mLabelingFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsMapRendererParallelJob::renderingFinished );
217 
218  // now start rendering of labeling!
219  mLabelingFuture = QtConcurrent::run( renderLabelsStatic, this );
220  mLabelingFutureWatcher.setFuture( mLabelingFuture );
222  }
223  else
224  {
225  renderingFinished();
226  }
227 }
228 
229 void QgsMapRendererParallelJob::renderingFinished()
230 {
231  QgsDebugMsgLevel( QStringLiteral( "PARALLEL finished" ), 2 );
232 
233  logRenderingTime( mLayerJobs, mLabelJob );
234 
235  cleanupJobs( mLayerJobs );
236 
237  cleanupLabelJob( mLabelJob );
238 
239  mStatus = Idle;
240 
241  mRenderingTime = mRenderingStart.elapsed();
242 
243  emit finished();
244 }
245 
246 void QgsMapRendererParallelJob::renderLayerStatic( LayerRenderJob &job )
247 {
248  if ( job.context.renderingStopped() )
249  return;
250 
251  if ( job.cached )
252  return;
253 
254  if ( job.img )
255  {
256  job.img->fill( 0 );
257  job.imageInitialized = true;
258  }
259 
260  QTime t;
261  t.start();
262  QgsDebugMsgLevel( QStringLiteral( "job %1 start (layer %2)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.layerId ), 2 );
263  try
264  {
265  job.renderer->render();
266  }
267  catch ( QgsException &e )
268  {
269  Q_UNUSED( e )
270  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
271  }
272  catch ( std::exception &e )
273  {
274  Q_UNUSED( e )
275  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
276  }
277  catch ( ... )
278  {
279  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
280  }
281 
282  job.errors = job.renderer->errors();
283  job.renderingTime += t.elapsed();
284  QgsDebugMsgLevel( QStringLiteral( "job %1 end [%2 ms] (layer %3)" ).arg( reinterpret_cast< quint64 >( &job ), 0, 16 ).arg( job.renderingTime ).arg( job.layerId ), 2 );
285 }
286 
287 
288 void QgsMapRendererParallelJob::renderLabelsStatic( QgsMapRendererParallelJob *self )
289 {
290  LabelRenderJob &job = self->mLabelJob;
291 
292  if ( !job.cached )
293  {
294  QTime labelTime;
295  labelTime.start();
296 
297  QPainter painter;
298  if ( job.img )
299  {
300  job.img->fill( 0 );
301  painter.begin( job.img );
302  }
303  else
304  {
305  painter.begin( &self->mFinalImage );
306  }
307 
308  // draw the labels!
309  try
310  {
311  drawLabeling( job.context, self->mLabelingEngineV2.get(), &painter );
312  }
313  catch ( QgsException &e )
314  {
315  Q_UNUSED( e )
316  QgsDebugMsg( "Caught unhandled QgsException: " + e.what() );
317  }
318  catch ( std::exception &e )
319  {
320  Q_UNUSED( e )
321  QgsDebugMsg( "Caught unhandled std::exception: " + QString::fromLatin1( e.what() ) );
322  }
323  catch ( ... )
324  {
325  QgsDebugMsg( QStringLiteral( "Caught unhandled unknown exception" ) );
326  }
327 
328  painter.end();
329 
330  job.renderingTime = labelTime.elapsed();
331  job.complete = true;
332  job.participatingLayers = _qgis_listRawToQPointer( self->mLabelingEngineV2->participatingLayers() );
333  if ( job.img )
334  {
335  self->mFinalImage = composeImage( self->mSettings, self->mLayerJobs, self->mLabelJob );
336  }
337  }
338 }
339 
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 Q_DECL_DEPRECATED void drawLabeling(const QgsMapSettings &settings, QgsRenderContext &renderContext, QgsLabelingEngine *labelingEngine2, QPainter *painter)
Default QgsLabelingEngine implementation, which completes the whole labeling operation (including lab...
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.
LayerRenderJobs prepareJobs(QPainter *painter, QgsLabelingEngine *labelingEngine2, bool deferredPainterSet=false)
Creates a list of layer rendering jobs and prepares them for later render.
#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...
void logRenderingTime(const LayerRenderJobs &jobs, const LabelRenderJob &labelJob)
void cancelWithoutBlocking() override
Triggers cancellation 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). ...