Skip to content

Commit b6b5afd

Browse files
committed
[FEATURE] Ability to cancel rendering of rasters + WMS/WCS support
The improvement allows immediate cancellation of raster rendering in progress. Until now, even when map rendering got cancelled (e.g. by zooming of panning canvas), the GUI got blocked while waiting for raster layers to finish their rendering (only vector layers have had support for cancellation). This should allow for much smoother user experience while browsing maps including rasters. The cancellation is supported currently by WMS/WMTS and WCS providers. GDAL provider may also get support thanks to improvements in GDAL 2. Funded by Land Information New Zealand.
1 parent 0974f0b commit b6b5afd

File tree

63 files changed

+328
-132
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+328
-132
lines changed

python/core/core.sip

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
%Include qgsfeaturefilterprovider.sip
5454
%Include qgsfeatureiterator.sip
5555
%Include qgsfeaturerequest.sip
56+
%Include qgsfeedback.sip
5657
%Include qgsfield.sip
5758
%Include qgsgeometrysimplifier.sip
5859
%Include qgsgeometryvalidator.sip

python/core/qgsmaplayerrenderer.sip

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ class QgsMapLayerRenderer
1212
//! Do the rendering (based on data stored in the class)
1313
virtual bool render() = 0;
1414

15+
//! Access to feedback object of the layer renderer (may be null)
16+
//! @note added in QGIS 3.0
17+
virtual QgsFeedback* feedback() const;
18+
1519
//! Return list of errors (problems) that happened during the rendering
1620
QStringList errors() const;
1721

python/core/raster/qgsbrightnesscontrastfilter.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class QgsBrightnessContrastFilter : QgsRasterInterface
1515

1616
bool setInput( QgsRasterInterface* input );
1717

18-
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height );
18+
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) /Factory/;
1919

2020
void setBrightness( int brightness );
2121
int brightness() const;

python/core/raster/qgshillshaderenderer.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class QgsHillshadeRenderer : QgsRasterRenderer
3131
*/
3232
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterInterface* input ) /Factory/;
3333

34-
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height ) /Factory/;
34+
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) /Factory/;
3535

3636
void writeXml( QDomDocument& doc, QDomElement& parentElem ) const;
3737

python/core/raster/qgshuesaturationfilter.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class QgsHueSaturationFilter : QgsRasterInterface
2525

2626
bool setInput( QgsRasterInterface* input );
2727

28-
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height );
28+
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) /Factory/;
2929

3030
void setSaturation( int saturation );
3131
int saturation() const;

python/core/raster/qgsmultibandcolorrenderer.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class QgsMultiBandColorRenderer: QgsRasterRenderer
1212

1313
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterDataProvider* provider ) /Factory/;
1414

15-
QgsRasterBlock* block( int bandNo, const QgsRectangle & extent, int width, int height ) /Factory/;
15+
QgsRasterBlock* block( int bandNo, const QgsRectangle & extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) /Factory/;
1616

1717
int redBand() const;
1818
void setRedBand( int band );

python/core/raster/qgspalettedrasterrenderer.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class QgsPalettedRasterRenderer : QgsRasterRenderer
1010
virtual QgsPalettedRasterRenderer * clone() const /Factory/;
1111
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterInterface* input ) /Factory/;
1212

13-
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height ) /Factory/;
13+
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) /Factory/;
1414

1515
/** Returns number of colors*/
1616
int nColors() const;

python/core/raster/qgsrasterdataprovider.sip

+2-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class QgsRasterDataProvider : QgsDataProvider, QgsRasterInterface
102102
virtual int ySize() const;
103103

104104
/** Read block of data using given extent and size. */
105-
virtual QgsRasterBlock *block( int theBandNo, const QgsRectangle &theExtent, int theWidth, int theHeight ) / Factory /;
105+
virtual QgsRasterBlock *block( int theBandNo, const QgsRectangle &theExtent, int theWidth, int theHeight, QgsRasterBlockFeedback* feedback = nullptr ) / Factory /;
106106

107107
/** Return true if source band has no data value */
108108
virtual bool sourceHasNoDataValue( int bandNo ) const;
@@ -306,7 +306,7 @@ class QgsRasterDataProvider : QgsDataProvider, QgsRasterInterface
306306
/** Read block of data using give extent and size
307307
* @note not available in python bindings
308308
*/
309-
//virtual void readBlock( int bandNo, QgsRectangle const & viewExtent, int width, int height, void *data );
309+
//virtual void readBlock( int bandNo, QgsRectangle const & viewExtent, int width, int height, void *data, QgsRasterBlockFeedback* feedback = nullptr );
310310

311311
/** Returns true if user no data contains value */
312312
bool userNoDataValuesContains( int bandNo, double value ) const;

python/core/raster/qgsrasterdrawer.sip

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ class QgsRasterDrawer
1313
* @param p destination QPainter
1414
* @param viewPort viewport to render
1515
* @param theQgsMapToPixel map to pixel convertor
16-
* @param ctx render context
16+
* @param feedback optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
1717
*/
18-
void draw( QPainter* p, QgsRasterViewPort* viewPort, const QgsMapToPixel* theQgsMapToPixel, const QgsRenderContext *ctx = nullptr );
18+
void draw( QPainter* p, QgsRasterViewPort* viewPort, const QgsMapToPixel* theQgsMapToPixel, QgsRasterBlockFeedback* feedback = nullptr );
1919

2020
protected:
2121
/** Draws raster part

python/core/raster/qgsrasterinterface.sip

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11

2+
/** Feedback object tailored for raster block reading. */
3+
class QgsRasterBlockFeedback : QgsFeedback
4+
{
5+
%TypeHeaderCode
6+
#include <qgsrasterinterface.h>
7+
%End
8+
// TODO: extend with preview functionality??
9+
};
10+
11+
212
/** Base class for processing modules.
313
*
414
*/
@@ -129,8 +139,9 @@ class QgsRasterInterface
129139
* @param extent extent of block
130140
* @param width pixel width of block
131141
* @param height pixel height of block
142+
* @param feedback optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
132143
*/
133-
virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height ) = 0 /Factory/;
144+
virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) = 0 /Factory/;
134145

135146
/** Set input.
136147
* Returns true if set correctly, false if cannot use that input */

python/core/raster/qgsrasteriterator.sip

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ class QgsRasterIterator
1212
@param nCols number of columns
1313
@param nRows number of rows
1414
@param extent area to read
15+
@param feedback optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
1516
*/
16-
void startRasterRead( int bandNumber, int nCols, int nRows, const QgsRectangle& extent );
17+
void startRasterRead( int bandNumber, int nCols, int nRows, const QgsRectangle& extent, QgsRasterBlockFeedback* feedback = nullptr );
1718

1819
/** Fetches next part of raster data, caller takes ownership of the block and
1920
caller should delete the block.

python/core/raster/qgsrasternuller.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class QgsRasterNuller : QgsRasterInterface
1919

2020
Qgis::DataType dataType( int bandNo ) const;
2121

22-
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height ) / Factory /;
22+
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) / Factory /;
2323

2424
void setNoData( int bandNo, const QgsRasterRangeList& noData );
2525

python/core/raster/qgsrasterprojector.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class QgsRasterProjector : QgsRasterInterface
7474
// Translated precision mode, for use in ComboBox etc.
7575
static QString precisionLabel( Precision precision );
7676

77-
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height ) / Factory /;
77+
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) / Factory /;
7878

7979
/** Calculate destination extent and size from source extent and size */
8080
bool destExtentSize( const QgsRectangle& theSrcExtent, int theSrcXSize, int theSrcYSize,

python/core/raster/qgsrasterrenderer.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class QgsRasterRenderer : QgsRasterInterface
3838

3939
virtual bool setInput( QgsRasterInterface* input );
4040

41-
virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height ) = 0 / Factory /;
41+
virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) = 0 / Factory /;
4242

4343
bool usesTransparency() const;
4444

python/core/raster/qgsrasterresamplefilter.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class QgsRasterResampleFilter : QgsRasterInterface
1919

2020
bool setInput( QgsRasterInterface* input );
2121

22-
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height ) /Factory/;
22+
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) /Factory/;
2323

2424
/** Set resampler for zoomed in scales. Takes ownership of the object*/
2525
void setZoomedInResampler( QgsRasterResampler* r /Transfer/ );

python/core/raster/qgssinglebandcolordatarenderer.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class QgsSingleBandColorDataRenderer: QgsRasterRenderer
1212

1313
bool setInput( QgsRasterInterface* input );
1414

15-
QgsRasterBlock* block( int bandNo, const QgsRectangle & extent, int width, int height ) / Factory /;
15+
QgsRasterBlock* block( int bandNo, const QgsRectangle & extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) / Factory /;
1616

1717
void writeXml( QDomDocument& doc, QDomElement& parentElem ) const;
1818

python/core/raster/qgssinglebandgrayrenderer.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class QgsSingleBandGrayRenderer: QgsRasterRenderer
1616

1717
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterDataProvider* provider ) /Factory/;
1818

19-
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height ) / Factory /;
19+
QgsRasterBlock *block( int bandNo, const QgsRectangle & extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) / Factory /;
2020

2121
int grayBand() const;
2222
void setGrayBand( int band );

python/core/raster/qgssinglebandpseudocolorrenderer.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class QgsSingleBandPseudoColorRenderer: QgsRasterRenderer
1111

1212
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterDataProvider* provider ) /Factory/;
1313

14-
QgsRasterBlock* block( int bandNo, const QgsRectangle & extent, int width, int height ) / Factory /;
14+
QgsRasterBlock* block( int bandNo, const QgsRectangle & extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) / Factory /;
1515

1616
/** Takes ownership of the shader*/
1717
void setShader( QgsRasterShader* shader /Transfer/ );

src/core/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ SET(QGIS_CORE_MOC_HDRS
457457
qgsdataprovider.h
458458
qgsdbfilterproxymodel.h
459459
qgseditformconfig.h
460+
qgsfeedback.h
460461
qgsgeometryvalidator.h
461462
qgsgml.h
462463
qgsgmlschema.h

src/core/qgsfeedback.h

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/***************************************************************************
2+
qgsfeedback.h
3+
--------------------------------------
4+
Date : July 2016
5+
Copyright : (C) 2016 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+
16+
#ifndef QGSFEEDBACK_H
17+
#define QGSFEEDBACK_H
18+
19+
#include <QObject>
20+
21+
/** Base class for feedback objects to be used for cancellation of something running in a worker thread.
22+
*
23+
* The class may be used as is or it may be subclassed for extended functionality
24+
* for a particular operation (e.g. report progress or pass some data for preview).
25+
*
26+
* When cancel() is called, the internal code has two options to check for cancellation state:
27+
* - if the worker thread uses an event loop (e.g. for network communication), the code can
28+
* make a queued connection to cancelled() signal and handle the cancellation in its slot.
29+
* - if the worker thread does not use an event loop, it can poll isCancelled() method regularly
30+
* to see if the operation should be cancelled.
31+
*
32+
* The class is meant to be created and destroyed in the main thread.
33+
*
34+
* For map rendering, the object may be created in constructor of a QgsMapLayerRenderer
35+
* subclass and available with QgsMapLayerRenderer::feedback() method. When a map rendering job
36+
* gets cancelled, the cancel() method is called on the feedback object of all layers.
37+
*
38+
* @note added in QGIS 3.0
39+
*/
40+
class CORE_EXPORT QgsFeedback : public QObject
41+
{
42+
Q_OBJECT
43+
public:
44+
//! Construct a feedback object
45+
QgsFeedback( QObject* parent = nullptr )
46+
: QObject( parent )
47+
, mCancelled( false )
48+
{}
49+
50+
virtual ~QgsFeedback() {}
51+
52+
//! Tells the internal routines that the current operation should be cancelled. This should be run by the main thread
53+
void cancel()
54+
{
55+
if ( mCancelled )
56+
return; // only emit the signal once
57+
mCancelled = true;
58+
emit cancelled();
59+
}
60+
61+
//! Tells whether the operation has been cancelled already
62+
bool isCancelled() const { return mCancelled; }
63+
64+
signals:
65+
//! Internal routines can connect to this signal if they use event loop
66+
void cancelled();
67+
68+
private:
69+
//! Whether the operation has been cancelled already. False by default.
70+
bool mCancelled;
71+
};
72+
73+
#endif // QGSFEEDBACK_H

src/core/qgsmaplayerrenderer.h

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
#include <QStringList>
2020

21+
class QgsFeedback;
22+
2123
/** \ingroup core
2224
* Base class for utility classes that encapsulate information necessary
2325
* for rendering of map layers. The rendering is typically done in a background
@@ -49,6 +51,10 @@ class CORE_EXPORT QgsMapLayerRenderer
4951
//! Do the rendering (based on data stored in the class)
5052
virtual bool render() = 0;
5153

54+
//! Access to feedback object of the layer renderer (may be null)
55+
//! @note added in QGIS 3.0
56+
virtual QgsFeedback* feedback() const { return nullptr; }
57+
5258
//! Return list of errors (problems) that happened during the rendering
5359
QStringList errors() const { return mErrors; }
5460

src/core/qgsmaprenderercustompainterjob.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "qgsmaprenderercustompainterjob.h"
1717

18+
#include "qgsfeedback.h"
1819
#include "qgslabelingenginev2.h"
1920
#include "qgslogger.h"
2021
#include "qgsmaplayerregistry.h"
@@ -124,6 +125,8 @@ void QgsMapRendererCustomPainterJob::cancel()
124125
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
125126
{
126127
it->context.setRenderingStopped( true );
128+
if ( it->renderer->feedback() )
129+
it->renderer->feedback()->cancel();
127130
}
128131

129132
QTime t;

src/core/qgsmaprendererparalleljob.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "qgsmaprendererparalleljob.h"
1717

18+
#include "qgsfeedback.h"
1819
#include "qgslabelingenginev2.h"
1920
#include "qgslogger.h"
2021
#include "qgsmaplayerrenderer.h"
@@ -91,6 +92,8 @@ void QgsMapRendererParallelJob::cancel()
9192
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
9293
{
9394
it->context.setRenderingStopped( true );
95+
if ( it->renderer->feedback() )
96+
it->renderer->feedback()->cancel();
9497
}
9598

9699
if ( mStatus == RenderingLayers )

src/core/raster/qgsbrightnesscontrastfilter.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ bool QgsBrightnessContrastFilter::setInput( QgsRasterInterface* input )
109109
return true;
110110
}
111111

112-
QgsRasterBlock * QgsBrightnessContrastFilter::block( int bandNo, QgsRectangle const & extent, int width, int height )
112+
QgsRasterBlock * QgsBrightnessContrastFilter::block( int bandNo, QgsRectangle const & extent, int width, int height, QgsRasterBlockFeedback* feedback )
113113
{
114114
Q_UNUSED( bandNo );
115115
QgsDebugMsgLevel( QString( "width = %1 height = %2 extent = %3" ).arg( width ).arg( height ).arg( extent.toString() ), 4 );
@@ -122,7 +122,7 @@ QgsRasterBlock * QgsBrightnessContrastFilter::block( int bandNo, QgsRectangle c
122122

123123
// At this moment we know that we read rendered image
124124
int bandNumber = 1;
125-
QgsRasterBlock *inputBlock = mInput->block( bandNumber, extent, width, height );
125+
QgsRasterBlock *inputBlock = mInput->block( bandNumber, extent, width, height, feedback );
126126
if ( !inputBlock || inputBlock->isEmpty() )
127127
{
128128
QgsDebugMsg( "No raster data!" );

src/core/raster/qgsbrightnesscontrastfilter.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class CORE_EXPORT QgsBrightnessContrastFilter : public QgsRasterInterface
3939

4040
bool setInput( QgsRasterInterface* input ) override;
4141

42-
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height ) override;
42+
QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) override;
4343

4444
void setBrightness( int brightness ) { mBrightness = qBound( -255, brightness, 255 ); }
4545
int brightness() const { return mBrightness; }

src/core/raster/qgshillshaderenderer.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ void QgsHillshadeRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem
8383
parentElem.appendChild( rasterRendererElem );
8484
}
8585

86-
QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height )
86+
QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback )
8787
{
8888
Q_UNUSED( bandNo );
8989
QgsRasterBlock *outputBlock = new QgsRasterBlock();
@@ -93,7 +93,7 @@ QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &ext
9393
return outputBlock;
9494
}
9595

96-
QgsRasterBlock *inputBlock = mInput->block( mBand, extent, width, height );
96+
QgsRasterBlock *inputBlock = mInput->block( mBand, extent, width, height, feedback );
9797

9898
if ( !inputBlock || inputBlock->isEmpty() )
9999
{
@@ -107,7 +107,7 @@ QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &ext
107107
if ( mAlphaBand > 0 && mBand != mAlphaBand )
108108
{
109109

110-
alphaBlock = mInput->block( mAlphaBand, extent, width, height );
110+
alphaBlock = mInput->block( mAlphaBand, extent, width, height, feedback );
111111
if ( !alphaBlock || alphaBlock->isEmpty() )
112112
{
113113
// TODO: better to render without alpha

src/core/raster/qgshillshaderenderer.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class CORE_EXPORT QgsHillshadeRenderer : public QgsRasterRenderer
5555

5656
void writeXml( QDomDocument& doc, QDomElement& parentElem ) const override;
5757

58-
QgsRasterBlock *block( int bandNo, QgsRectangle const & extent, int width, int height ) override;
58+
QgsRasterBlock *block( int bandNo, QgsRectangle const & extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) override;
5959

6060
QList<int> usesBands() const override;
6161

0 commit comments

Comments
 (0)