Skip to content

Commit

Permalink
[FEATURE] Ability to cancel rendering of rasters + WMS/WCS support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wonder-sk committed Jul 24, 2016
1 parent 0974f0b commit b6b5afd
Show file tree
Hide file tree
Showing 63 changed files with 328 additions and 132 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -53,6 +53,7 @@
%Include qgsfeaturefilterprovider.sip
%Include qgsfeatureiterator.sip
%Include qgsfeaturerequest.sip
%Include qgsfeedback.sip
%Include qgsfield.sip
%Include qgsgeometrysimplifier.sip
%Include qgsgeometryvalidator.sip
Expand Down
4 changes: 4 additions & 0 deletions python/core/qgsmaplayerrenderer.sip
Expand Up @@ -12,6 +12,10 @@ class QgsMapLayerRenderer
//! Do the rendering (based on data stored in the class)
virtual bool render() = 0;

//! Access to feedback object of the layer renderer (may be null)
//! @note added in QGIS 3.0
virtual QgsFeedback* feedback() const;

//! Return list of errors (problems) that happened during the rendering
QStringList errors() const;

Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgsbrightnesscontrastfilter.sip
Expand Up @@ -15,7 +15,7 @@ class QgsBrightnessContrastFilter : QgsRasterInterface

bool setInput( QgsRasterInterface* input );

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

void setBrightness( int brightness );
int brightness() const;
Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgshillshaderenderer.sip
Expand Up @@ -31,7 +31,7 @@ class QgsHillshadeRenderer : QgsRasterRenderer
*/
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterInterface* input ) /Factory/;

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

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

Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgshuesaturationfilter.sip
Expand Up @@ -25,7 +25,7 @@ class QgsHueSaturationFilter : QgsRasterInterface

bool setInput( QgsRasterInterface* input );

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

void setSaturation( int saturation );
int saturation() const;
Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgsmultibandcolorrenderer.sip
Expand Up @@ -12,7 +12,7 @@ class QgsMultiBandColorRenderer: QgsRasterRenderer

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

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

int redBand() const;
void setRedBand( int band );
Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgspalettedrasterrenderer.sip
Expand Up @@ -10,7 +10,7 @@ class QgsPalettedRasterRenderer : QgsRasterRenderer
virtual QgsPalettedRasterRenderer * clone() const /Factory/;
static QgsRasterRenderer* create( const QDomElement& elem, QgsRasterInterface* input ) /Factory/;

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

/** Returns number of colors*/
int nColors() const;
Expand Down
4 changes: 2 additions & 2 deletions python/core/raster/qgsrasterdataprovider.sip
Expand Up @@ -102,7 +102,7 @@ class QgsRasterDataProvider : QgsDataProvider, QgsRasterInterface
virtual int ySize() const;

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

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

/** Returns true if user no data contains value */
bool userNoDataValuesContains( int bandNo, double value ) const;
Expand Down
4 changes: 2 additions & 2 deletions python/core/raster/qgsrasterdrawer.sip
Expand Up @@ -13,9 +13,9 @@ class QgsRasterDrawer
* @param p destination QPainter
* @param viewPort viewport to render
* @param theQgsMapToPixel map to pixel convertor
* @param ctx render context
* @param feedback optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
*/
void draw( QPainter* p, QgsRasterViewPort* viewPort, const QgsMapToPixel* theQgsMapToPixel, const QgsRenderContext *ctx = nullptr );
void draw( QPainter* p, QgsRasterViewPort* viewPort, const QgsMapToPixel* theQgsMapToPixel, QgsRasterBlockFeedback* feedback = nullptr );

protected:
/** Draws raster part
Expand Down
13 changes: 12 additions & 1 deletion python/core/raster/qgsrasterinterface.sip
@@ -1,4 +1,14 @@

/** Feedback object tailored for raster block reading. */
class QgsRasterBlockFeedback : QgsFeedback
{
%TypeHeaderCode
#include <qgsrasterinterface.h>
%End
// TODO: extend with preview functionality??
};


/** Base class for processing modules.
*
*/
Expand Down Expand Up @@ -129,8 +139,9 @@ class QgsRasterInterface
* @param extent extent of block
* @param width pixel width of block
* @param height pixel height of block
* @param feedback optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
*/
virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height ) = 0 /Factory/;
virtual QgsRasterBlock *block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback = nullptr ) = 0 /Factory/;

/** Set input.
* Returns true if set correctly, false if cannot use that input */
Expand Down
3 changes: 2 additions & 1 deletion python/core/raster/qgsrasteriterator.sip
Expand Up @@ -12,8 +12,9 @@ class QgsRasterIterator
@param nCols number of columns
@param nRows number of rows
@param extent area to read
@param feedback optional raster feedback object for cancellation/preview. Added in QGIS 3.0.
*/
void startRasterRead( int bandNumber, int nCols, int nRows, const QgsRectangle& extent );
void startRasterRead( int bandNumber, int nCols, int nRows, const QgsRectangle& extent, QgsRasterBlockFeedback* feedback = nullptr );

/** Fetches next part of raster data, caller takes ownership of the block and
caller should delete the block.
Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgsrasternuller.sip
Expand Up @@ -19,7 +19,7 @@ class QgsRasterNuller : QgsRasterInterface

Qgis::DataType dataType( int bandNo ) const;

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

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

Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgsrasterprojector.sip
Expand Up @@ -74,7 +74,7 @@ class QgsRasterProjector : QgsRasterInterface
// Translated precision mode, for use in ComboBox etc.
static QString precisionLabel( Precision precision );

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

/** Calculate destination extent and size from source extent and size */
bool destExtentSize( const QgsRectangle& theSrcExtent, int theSrcXSize, int theSrcYSize,
Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgsrasterrenderer.sip
Expand Up @@ -38,7 +38,7 @@ class QgsRasterRenderer : QgsRasterInterface

virtual bool setInput( QgsRasterInterface* input );

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

bool usesTransparency() const;

Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgsrasterresamplefilter.sip
Expand Up @@ -19,7 +19,7 @@ class QgsRasterResampleFilter : QgsRasterInterface

bool setInput( QgsRasterInterface* input );

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

/** Set resampler for zoomed in scales. Takes ownership of the object*/
void setZoomedInResampler( QgsRasterResampler* r /Transfer/ );
Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgssinglebandcolordatarenderer.sip
Expand Up @@ -12,7 +12,7 @@ class QgsSingleBandColorDataRenderer: QgsRasterRenderer

bool setInput( QgsRasterInterface* input );

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

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

Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgssinglebandgrayrenderer.sip
Expand Up @@ -16,7 +16,7 @@ class QgsSingleBandGrayRenderer: QgsRasterRenderer

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

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

int grayBand() const;
void setGrayBand( int band );
Expand Down
2 changes: 1 addition & 1 deletion python/core/raster/qgssinglebandpseudocolorrenderer.sip
Expand Up @@ -11,7 +11,7 @@ class QgsSingleBandPseudoColorRenderer: QgsRasterRenderer

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

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

/** Takes ownership of the shader*/
void setShader( QgsRasterShader* shader /Transfer/ );
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -457,6 +457,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsdataprovider.h
qgsdbfilterproxymodel.h
qgseditformconfig.h
qgsfeedback.h
qgsgeometryvalidator.h
qgsgml.h
qgsgmlschema.h
Expand Down
73 changes: 73 additions & 0 deletions src/core/qgsfeedback.h
@@ -0,0 +1,73 @@
/***************************************************************************
qgsfeedback.h
--------------------------------------
Date : July 2016
Copyright : (C) 2016 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSFEEDBACK_H
#define QGSFEEDBACK_H

#include <QObject>

/** Base class for feedback objects to be used for cancellation of something running in a worker thread.
*
* The class may be used as is or it may be subclassed for extended functionality
* for a particular operation (e.g. report progress or pass some data for preview).
*
* When cancel() is called, the internal code has two options to check for cancellation state:
* - if the worker thread uses an event loop (e.g. for network communication), the code can
* make a queued connection to cancelled() signal and handle the cancellation in its slot.
* - if the worker thread does not use an event loop, it can poll isCancelled() method regularly
* to see if the operation should be cancelled.
*
* The class is meant to be created and destroyed in the main thread.
*
* For map rendering, the object may be created in constructor of a QgsMapLayerRenderer
* subclass and available with QgsMapLayerRenderer::feedback() method. When a map rendering job
* gets cancelled, the cancel() method is called on the feedback object of all layers.
*
* @note added in QGIS 3.0
*/
class CORE_EXPORT QgsFeedback : public QObject
{
Q_OBJECT
public:
//! Construct a feedback object
QgsFeedback( QObject* parent = nullptr )
: QObject( parent )
, mCancelled( false )
{}

virtual ~QgsFeedback() {}

//! Tells the internal routines that the current operation should be cancelled. This should be run by the main thread
void cancel()
{
if ( mCancelled )
return; // only emit the signal once
mCancelled = true;
emit cancelled();
}

//! Tells whether the operation has been cancelled already
bool isCancelled() const { return mCancelled; }

signals:
//! Internal routines can connect to this signal if they use event loop
void cancelled();

private:
//! Whether the operation has been cancelled already. False by default.
bool mCancelled;
};

#endif // QGSFEEDBACK_H
6 changes: 6 additions & 0 deletions src/core/qgsmaplayerrenderer.h
Expand Up @@ -18,6 +18,8 @@

#include <QStringList>

class QgsFeedback;

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

//! Access to feedback object of the layer renderer (may be null)
//! @note added in QGIS 3.0
virtual QgsFeedback* feedback() const { return nullptr; }

//! Return list of errors (problems) that happened during the rendering
QStringList errors() const { return mErrors; }

Expand Down
3 changes: 3 additions & 0 deletions src/core/qgsmaprenderercustompainterjob.cpp
Expand Up @@ -15,6 +15,7 @@

#include "qgsmaprenderercustompainterjob.h"

#include "qgsfeedback.h"
#include "qgslabelingenginev2.h"
#include "qgslogger.h"
#include "qgsmaplayerregistry.h"
Expand Down Expand Up @@ -124,6 +125,8 @@ void QgsMapRendererCustomPainterJob::cancel()
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
if ( it->renderer->feedback() )
it->renderer->feedback()->cancel();
}

QTime t;
Expand Down
3 changes: 3 additions & 0 deletions src/core/qgsmaprendererparalleljob.cpp
Expand Up @@ -15,6 +15,7 @@

#include "qgsmaprendererparalleljob.h"

#include "qgsfeedback.h"
#include "qgslabelingenginev2.h"
#include "qgslogger.h"
#include "qgsmaplayerrenderer.h"
Expand Down Expand Up @@ -91,6 +92,8 @@ void QgsMapRendererParallelJob::cancel()
for ( LayerRenderJobs::iterator it = mLayerJobs.begin(); it != mLayerJobs.end(); ++it )
{
it->context.setRenderingStopped( true );
if ( it->renderer->feedback() )
it->renderer->feedback()->cancel();
}

if ( mStatus == RenderingLayers )
Expand Down
4 changes: 2 additions & 2 deletions src/core/raster/qgsbrightnesscontrastfilter.cpp
Expand Up @@ -109,7 +109,7 @@ bool QgsBrightnessContrastFilter::setInput( QgsRasterInterface* input )
return true;
}

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

// At this moment we know that we read rendered image
int bandNumber = 1;
QgsRasterBlock *inputBlock = mInput->block( bandNumber, extent, width, height );
QgsRasterBlock *inputBlock = mInput->block( bandNumber, extent, width, height, feedback );
if ( !inputBlock || inputBlock->isEmpty() )
{
QgsDebugMsg( "No raster data!" );
Expand Down
2 changes: 1 addition & 1 deletion src/core/raster/qgsbrightnesscontrastfilter.h
Expand Up @@ -39,7 +39,7 @@ class CORE_EXPORT QgsBrightnessContrastFilter : public QgsRasterInterface

bool setInput( QgsRasterInterface* input ) override;

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

void setBrightness( int brightness ) { mBrightness = qBound( -255, brightness, 255 ); }
int brightness() const { return mBrightness; }
Expand Down
6 changes: 3 additions & 3 deletions src/core/raster/qgshillshaderenderer.cpp
Expand Up @@ -83,7 +83,7 @@ void QgsHillshadeRenderer::writeXml( QDomDocument &doc, QDomElement &parentElem
parentElem.appendChild( rasterRendererElem );
}

QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height )
QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &extent, int width, int height, QgsRasterBlockFeedback* feedback )
{
Q_UNUSED( bandNo );
QgsRasterBlock *outputBlock = new QgsRasterBlock();
Expand All @@ -93,7 +93,7 @@ QgsRasterBlock *QgsHillshadeRenderer::block( int bandNo, const QgsRectangle &ext
return outputBlock;
}

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

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

alphaBlock = mInput->block( mAlphaBand, extent, width, height );
alphaBlock = mInput->block( mAlphaBand, extent, width, height, feedback );
if ( !alphaBlock || alphaBlock->isEmpty() )
{
// TODO: better to render without alpha
Expand Down
2 changes: 1 addition & 1 deletion src/core/raster/qgshillshaderenderer.h
Expand Up @@ -55,7 +55,7 @@ class CORE_EXPORT QgsHillshadeRenderer : public QgsRasterRenderer

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

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

QList<int> usesBands() const override;

Expand Down

0 comments on commit b6b5afd

Please sign in to comment.