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