From 110828feabfde7e7c077ea369638a83201a3661e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 6 Apr 2017 15:17:26 +1000 Subject: [PATCH] [FEATURE] Background saving of raster layers Use the task manager framework to handle saving of raster layers in the background --- python/core/core.sip | 1 + python/core/raster/qgsrasterfilewriter.sip | 4 +- .../core/raster/qgsrasterfilewritertask.sip | 68 ++++++++++++ src/app/qgisapp.cpp | 50 +++++---- src/core/CMakeLists.txt | 2 + src/core/qgsvectorfilewritertask.cpp | 2 +- src/core/qgsvectorfilewritertask.h | 1 + src/core/raster/qgsrasterfilewriter.cpp | 77 ++++++------- src/core/raster/qgsrasterfilewriter.h | 30 +++-- src/core/raster/qgsrasterfilewritertask.cpp | 61 ++++++++++ src/core/raster/qgsrasterfilewritertask.h | 91 +++++++++++++++ tests/src/python/CMakeLists.txt | 1 + .../python/test_qgsrasterfilewritertask.py | 104 ++++++++++++++++++ 13 files changed, 413 insertions(+), 79 deletions(-) create mode 100644 python/core/raster/qgsrasterfilewritertask.sip mode change 100644 => 100755 src/core/CMakeLists.txt create mode 100644 src/core/raster/qgsrasterfilewritertask.cpp create mode 100644 src/core/raster/qgsrasterfilewritertask.h mode change 100644 => 100755 tests/src/python/CMakeLists.txt create mode 100755 tests/src/python/test_qgsrasterfilewritertask.py diff --git a/python/core/core.sip b/python/core/core.sip index 8a04cc1fa46..d484816762a 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -298,6 +298,7 @@ %Include raster/qgsrasterdataprovider.sip %Include raster/qgsrasterdrawer.sip %Include raster/qgsrasterfilewriter.sip +%Include raster/qgsrasterfilewritertask.sip %Include raster/qgsrasterhistogram.sip %Include raster/qgsrasteridentifyresult.sip %Include raster/qgsrasterinterface.sip diff --git a/python/core/raster/qgsrasterfilewriter.sip b/python/core/raster/qgsrasterfilewriter.sip index 2b3db651c23..20a631b7233 100644 --- a/python/core/raster/qgsrasterfilewriter.sip +++ b/python/core/raster/qgsrasterfilewriter.sip @@ -49,7 +49,9 @@ class QgsRasterFileWriter @param crs crs to reproject to @param p dialog to show progress in */ WriterError writeRaster( const QgsRasterPipe* pipe, int nCols, int nRows, const QgsRectangle& outputExtent, - const QgsCoordinateReferenceSystem& crs, QProgressDialog* p = 0 ); + const QgsCoordinateReferenceSystem& crs, QgsRasterBlockFeedback *feedback = nullptr ); + + QString outputUrl() const; void setOutputFormat( const QString& format ); QString outputFormat() const; diff --git a/python/core/raster/qgsrasterfilewritertask.sip b/python/core/raster/qgsrasterfilewritertask.sip new file mode 100644 index 00000000000..c3143a438f9 --- /dev/null +++ b/python/core/raster/qgsrasterfilewritertask.sip @@ -0,0 +1,68 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/raster/qgsrasterfilewritertask.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsRasterFileWriterTask : QgsTask +{ +%Docstring + QgsTask task which performs a QgsRasterFileWriter layer saving operation as a background + task. This can be used to save a raster layer out to a file without blocking the + QGIS interface. + \see QgsVectorFileWriterTask +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsrasterfilewritertask.h" +%End + + public: + + QgsRasterFileWriterTask( const QgsRasterFileWriter &writer, QgsRasterPipe *pipe /Transfer/, + int columns, int rows, + const QgsRectangle &outputExtent, + const QgsCoordinateReferenceSystem &crs ); +%Docstring + Constructor for QgsRasterFileWriterTask. Takes a source writer, + columns, rows, outputExtent and destination crs. + Ownership of the pipe is transferred to the writer task, and will + be deleted when the task completes. +%End + + virtual void cancel(); + + signals: + + void writeComplete( const QString &outputUrl ); +%Docstring + Emitted when writing the layer is successfully completed. The outputUrl + parameter indicates the file path for the written file(s). +%End + + void errorOccurred( int error ); +%Docstring + Emitted when an error occurs which prevented the file being written (or if + the task is canceled). The writing error will be reported. +%End + + protected: + + virtual bool run(); + virtual void finished( bool result ); + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/raster/qgsrasterfilewritertask.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 8c894596b1f..3538072be9c 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -219,6 +219,7 @@ #include "qgsquerybuilder.h" #include "qgsrastercalcdialog.h" #include "qgsrasterfilewriter.h" +#include "qgsrasterfilewritertask.h" #include "qgsrasteriterator.h" #include "qgsrasterlayer.h" #include "qgsrasterlayerproperties.h" @@ -6453,13 +6454,6 @@ void QgisApp::saveAsRasterFile() fileWriter.setMaxTileHeight( d.maximumTileSizeY() ); } - QProgressDialog pd( QString(), tr( "Abort..." ), 0, 0 ); - // Show the dialo immediately because cloning pipe can take some time (WCS) - pd.setLabelText( tr( "Reading raster" ) ); - pd.setWindowTitle( tr( "Saving raster" ) ); - pd.show(); - pd.setWindowModality( Qt::WindowModal ); - // TODO: show error dialogs // TODO: this code should go somewhere else, but probably not into QgsRasterFileWriter // clone pipe/provider is not really necessary, ready for threads @@ -6525,35 +6519,51 @@ void QgisApp::saveAsRasterFile() fileWriter.setPyramidsFormat( d.pyramidsFormat() ); fileWriter.setPyramidsConfigOptions( d.pyramidsConfigOptions() ); - QgsRasterFileWriter::WriterError err = fileWriter.writeRaster( pipe.get(), d.nColumns(), d.nRows(), d.outputRectangle(), d.outputCrs(), &pd ); - if ( err != QgsRasterFileWriter::NoError ) - { - QMessageBox::warning( this, tr( "Error" ), - tr( "Cannot write raster error code: %1" ).arg( err ), - QMessageBox::Ok ); + bool tileMode = d.tileMode(); + bool addToCanvas = d.addToCanvas(); + QPointer< QgsRasterLayer > rlWeakPointer( rasterLayer ); - } - else + QgsRasterFileWriterTask *writerTask = new QgsRasterFileWriterTask( fileWriter, pipe.release(), d.nColumns(), d.nRows(), + d.outputRectangle(), d.outputCrs() ); + + // when writer is successful: + + connect( writerTask, &QgsRasterFileWriterTask::writeComplete, this, [this, tileMode, addToCanvas, rlWeakPointer ]( const QString & newFilename ) { - QString fileName( d.outputFileName() ); - if ( d.tileMode() ) + QString fileName = newFilename; + if ( tileMode ) { QFileInfo outputInfo( fileName ); fileName = QStringLiteral( "%1/%2.vrt" ).arg( fileName, outputInfo.fileName() ); } - if ( d.addToCanvas() ) + if ( addToCanvas ) { addRasterLayers( QStringList( fileName ) ); } + if ( rlWeakPointer ) + emit layerSavedAs( rlWeakPointer, fileName ); - emit layerSavedAs( rasterLayer, fileName ); messageBar()->pushMessage( tr( "Saving done" ), tr( "Export to raster file has been completed" ), QgsMessageBar::INFO, messageTimeout() ); - } + } ); + + // when an error occurs: + connect( writerTask, &QgsRasterFileWriterTask::errorOccurred, this, [ = ]( int error ) + { + if ( error != QgsRasterFileWriter::WriteCanceled ) + { + QMessageBox::warning( this, tr( "Error" ), + tr( "Cannot write raster error code: %1" ).arg( error ), + QMessageBox::Ok ); + } + } ); + + QgsApplication::taskManager()->addTask( writerTask ); } + void QgisApp::saveAsFile() { QgsMapLayer *layer = activeLayer(); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt old mode 100644 new mode 100755 index 94fa62bf7ff..ecf8d58e31f --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -336,6 +336,7 @@ SET(QGIS_CORE_SRCS raster/qgsrasterblock.cpp raster/qgsrasterchecker.cpp raster/qgsrasterdataprovider.cpp + raster/qgsrasterfilewritertask.cpp raster/qgsrasteridentifyresult.cpp raster/qgsrasterinterface.cpp raster/qgsrasteriterator.cpp @@ -597,6 +598,7 @@ SET(QGIS_CORE_MOC_HDRS processing/qgsprocessingfeedback.h processing/qgsprocessingregistry.h + raster/qgsrasterfilewritertask.h raster/qgsrasterlayer.h raster/qgsrasterdataprovider.h diff --git a/src/core/qgsvectorfilewritertask.cpp b/src/core/qgsvectorfilewritertask.cpp index 0eaf0903916..bc69d65260e 100644 --- a/src/core/qgsvectorfilewritertask.cpp +++ b/src/core/qgsvectorfilewritertask.cpp @@ -19,7 +19,7 @@ QgsVectorFileWriterTask::QgsVectorFileWriterTask( QgsVectorLayer *layer, const QString &fileName, const QgsVectorFileWriter::SaveVectorOptions &options ) - : QgsTask( tr( "Saving %1 " ).arg( fileName ), QgsTask::CanCancel ) + : QgsTask( tr( "Saving %1" ).arg( fileName ), QgsTask::CanCancel ) , mLayer( layer ) , mDestFileName( fileName ) , mOptions( options ) diff --git a/src/core/qgsvectorfilewritertask.h b/src/core/qgsvectorfilewritertask.h index 68484862e98..ddaa4c0619f 100644 --- a/src/core/qgsvectorfilewritertask.h +++ b/src/core/qgsvectorfilewritertask.h @@ -30,6 +30,7 @@ * task. This can be used to save a vector layer out to a file without blocking the * QGIS interface. * \since QGIS 3.0 + * \see QgsRasterFileWriterTask */ class CORE_EXPORT QgsVectorFileWriterTask : public QgsTask { diff --git a/src/core/raster/qgsrasterfilewriter.cpp b/src/core/raster/qgsrasterfilewriter.cpp index 68dca70977d..670afd826e8 100644 --- a/src/core/raster/qgsrasterfilewriter.cpp +++ b/src/core/raster/qgsrasterfilewriter.cpp @@ -51,7 +51,6 @@ QgsRasterFileWriter::QgsRasterFileWriter( const QString &outputUrl ) , mMaxTileHeight( 500 ) , mBuildPyramidsFlag( QgsRaster::PyramidsFlagNo ) , mPyramidsFormat( QgsRaster::PyramidsGTiff ) - , mProgressDialog( nullptr ) , mPipe( nullptr ) , mInput( nullptr ) { @@ -67,7 +66,6 @@ QgsRasterFileWriter::QgsRasterFileWriter() , mMaxTileHeight( 500 ) , mBuildPyramidsFlag( QgsRaster::PyramidsFlagNo ) , mPyramidsFormat( QgsRaster::PyramidsGTiff ) - , mProgressDialog( nullptr ) , mPipe( nullptr ) , mInput( nullptr ) { @@ -75,7 +73,7 @@ QgsRasterFileWriter::QgsRasterFileWriter() } QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeRaster( const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent, - const QgsCoordinateReferenceSystem &crs, QProgressDialog *progressDialog ) + const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback ) { QgsDebugMsgLevel( "Entered", 4 ); @@ -114,7 +112,7 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeRaster( const QgsRast QgsDebugMsgLevel( QString( "srcInput = %1" ).arg( typeid( srcInput ).name() ), 4 ); #endif - mProgressDialog = progressDialog; + mFeedback = feedback; QgsRasterIterator iter( pipe->last() ); @@ -135,20 +133,18 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeRaster( const QgsRast if ( mMode == Image ) { - WriterError e = writeImageRaster( &iter, nCols, nRows, outputExtent, crs, progressDialog ); - mProgressDialog = nullptr; + WriterError e = writeImageRaster( &iter, nCols, nRows, outputExtent, crs, feedback ); return e; } else { - mProgressDialog = nullptr; - WriterError e = writeDataRaster( pipe, &iter, nCols, nRows, outputExtent, crs, progressDialog ); + WriterError e = writeDataRaster( pipe, &iter, nCols, nRows, outputExtent, crs, feedback ); return e; } } QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe *pipe, QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent, - const QgsCoordinateReferenceSystem &crs, QProgressDialog *progressDialog ) + const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback ) { QgsDebugMsgLevel( "Entered", 4 ); if ( !iter ) @@ -291,7 +287,7 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const Qgs // initOutput() returns 0 in tile mode! destProvider = initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ); - WriterError error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, progressDialog ); + WriterError error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, feedback ); if ( error == NoDataConflict ) { @@ -319,7 +315,7 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const Qgs // Try again destProvider = initOutput( nCols, nRows, crs, geoTransform, nBands, destDataType, destHasNoDataValueList, destNoDataValueList ); - error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, progressDialog ); + error = writeDataRaster( pipe, iter, nCols, nRows, outputExtent, crs, destDataType, destHasNoDataValueList, destNoDataValueList, destProvider, feedback ); } if ( destProvider ) @@ -328,17 +324,16 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const Qgs return error; } -QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( - const QgsRasterPipe *pipe, - QgsRasterIterator *iter, - int nCols, int nRows, - const QgsRectangle &outputExtent, - const QgsCoordinateReferenceSystem &crs, - Qgis::DataType destDataType, - const QList &destHasNoDataValueList, - const QList &destNoDataValueList, - QgsRasterDataProvider *destProvider, - QProgressDialog *progressDialog ) +QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( const QgsRasterPipe *pipe, + QgsRasterIterator *iter, + int nCols, int nRows, + const QgsRectangle &outputExtent, + const QgsCoordinateReferenceSystem &crs, + Qgis::DataType destDataType, + const QList &destHasNoDataValueList, + const QList &destNoDataValueList, + QgsRasterDataProvider *destProvider, + QgsRasterBlockFeedback *feedback ) { Q_UNUSED( pipe ); Q_UNUSED( destHasNoDataValueList ); @@ -369,14 +364,11 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( int nParts = 0; int fileIndex = 0; - if ( progressDialog ) + if ( feedback ) { int nPartsX = nCols / iter->maximumTileWidth() + 1; int nPartsY = nRows / iter->maximumTileHeight() + 1; nParts = nPartsX * nPartsY; - progressDialog->setMaximum( nParts ); - progressDialog->show(); - progressDialog->setLabelText( QObject::tr( "Reading raster part %1 of %2" ).arg( fileIndex + 1 ).arg( nParts ) ); } // hmm why is there a for(;;) here .. @@ -411,12 +403,10 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( // TODO: verify if NoDataConflict happened, to do that we need the whole pipe or nuller interface } - if ( progressDialog && fileIndex < ( nParts - 1 ) ) + if ( feedback && fileIndex < ( nParts - 1 ) ) { - progressDialog->setValue( fileIndex + 1 ); - progressDialog->setLabelText( QObject::tr( "Reading raster part %1 of %2" ).arg( fileIndex + 2 ).arg( nParts ) ); - QCoreApplication::processEvents( QEventLoop::AllEvents, 1000 ); - if ( progressDialog->wasCanceled() ) + feedback->setProgress( 100.0 * fileIndex / static_cast< double >( nParts ) ); + if ( feedback->isCanceled() ) { for ( int i = 0; i < nBands; ++i ) { @@ -479,11 +469,11 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeDataRaster( } QgsDebugMsgLevel( "Done", 4 ); - return NoError; + return ( feedback && feedback->isCanceled() ) ? WriteCanceled : NoError; } QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeImageRaster( QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent, - const QgsCoordinateReferenceSystem &crs, QProgressDialog *progressDialog ) + const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback ) { QgsDebugMsgLevel( "Entered", 4 ); if ( !iter ) @@ -520,17 +510,14 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeImageRaster( QgsRaste destProvider = initOutput( nCols, nRows, crs, geoTransform, 4, Qgis::Byte ); - iter->startRasterRead( 1, nCols, nRows, outputExtent ); + iter->startRasterRead( 1, nCols, nRows, outputExtent, feedback ); int nParts = 0; - if ( progressDialog ) + if ( feedback ) { int nPartsX = nCols / iter->maximumTileWidth() + 1; int nPartsY = nRows / iter->maximumTileHeight() + 1; nParts = nPartsX * nPartsY; - progressDialog->setMaximum( nParts ); - progressDialog->show(); - progressDialog->setLabelText( QObject::tr( "Reading raster part %1 of %2" ).arg( fileIndex + 1 ).arg( nParts ) ); } QgsRasterBlock *inputBlock = nullptr; @@ -541,12 +528,10 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeImageRaster( QgsRaste continue; } - if ( progressDialog && fileIndex < ( nParts - 1 ) ) + if ( feedback && fileIndex < ( nParts - 1 ) ) { - progressDialog->setValue( fileIndex + 1 ); - progressDialog->setLabelText( QObject::tr( "Reading raster part %1 of %2" ).arg( fileIndex + 2 ).arg( nParts ) ); - QCoreApplication::processEvents( QEventLoop::AllEvents, 1000 ); - if ( progressDialog->wasCanceled() ) + feedback->setProgress( 100.0 * fileIndex / static_cast< double >( nParts ) ); + if ( feedback->isCanceled() ) { delete inputBlock; break; @@ -626,9 +611,9 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeImageRaster( QgsRaste qgsFree( blueData ); qgsFree( alphaData ); - if ( progressDialog ) + if ( feedback ) { - progressDialog->setValue( progressDialog->maximum() ); + feedback->setProgress( 100.0 ); } if ( mTiledMode ) @@ -647,7 +632,7 @@ QgsRasterFileWriter::WriterError QgsRasterFileWriter::writeImageRaster( QgsRaste buildPyramids( mOutputUrl ); } } - return NoError; + return ( feedback && feedback->isCanceled() ) ? WriteCanceled : NoError; } void QgsRasterFileWriter::addToVRT( const QString &filename, int band, int xSize, int ySize, int xOffset, int yOffset ) diff --git a/src/core/raster/qgsrasterfilewriter.h b/src/core/raster/qgsrasterfilewriter.h index 3f609f0f01a..bf9678b6e1f 100644 --- a/src/core/raster/qgsrasterfilewriter.h +++ b/src/core/raster/qgsrasterfilewriter.h @@ -24,7 +24,7 @@ #include "qgsraster.h" -class QProgressDialog; +class QgsRasterBlockFeedback; class QgsRasterIterator; class QgsRasterPipe; class QgsRectangle; @@ -39,8 +39,8 @@ class CORE_EXPORT QgsRasterFileWriter public: enum Mode { - Raw = 0, // Raw data - Image = 1 // Rendered image + Raw = 0, //!< Raw data + Image = 1 //!< Rendered image }; enum WriterError { @@ -49,8 +49,8 @@ class CORE_EXPORT QgsRasterFileWriter DestProviderError = 2, CreateDatasourceError = 3, WriteError = 4, - // Internal error if a value used for 'no data' was found in input - NoDataConflict = 5 + NoDataConflict = 5, //!< Internal error if a value used for 'no data' was found in input + WriteCanceled = 6, //!< Writing was manually canceled }; QgsRasterFileWriter( const QString &outputUrl ); @@ -73,9 +73,16 @@ class CORE_EXPORT QgsRasterFileWriter \param nRows number of output rows (or -1 to automatically calculate row number to have square pixels) \param outputExtent extent to output \param crs crs to reproject to - \param p dialog to show progress in */ + \param feedback optional feedback object for progress reports + */ WriterError writeRaster( const QgsRasterPipe *pipe, int nCols, int nRows, const QgsRectangle &outputExtent, - const QgsCoordinateReferenceSystem &crs, QProgressDialog *p = nullptr ); + const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback = nullptr ); + + /** + * Returns the output URL for the raster. + * \since QGIS 3.0 + */ + QString outputUrl() const { return mOutputUrl; } void setOutputFormat( const QString &format ) { mOutputFormat = format; } QString outputFormat() const { return mOutputFormat; } @@ -113,7 +120,7 @@ class CORE_EXPORT QgsRasterFileWriter private: QgsRasterFileWriter(); //forbidden WriterError writeDataRaster( const QgsRasterPipe *pipe, QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent, - const QgsCoordinateReferenceSystem &crs, QProgressDialog *progressDialog = nullptr ); + const QgsCoordinateReferenceSystem &crs, QgsRasterBlockFeedback *feedback = nullptr ); // Helper method used by previous one WriterError writeDataRaster( const QgsRasterPipe *pipe, @@ -125,10 +132,11 @@ class CORE_EXPORT QgsRasterFileWriter const QList &destHasNoDataValueList, const QList &destNoDataValueList, QgsRasterDataProvider *destProvider, - QProgressDialog *progressDialog ); + QgsRasterBlockFeedback *feedback = nullptr ); WriterError writeImageRaster( QgsRasterIterator *iter, int nCols, int nRows, const QgsRectangle &outputExtent, - const QgsCoordinateReferenceSystem &crs, QProgressDialog *progressDialog = nullptr ); + const QgsCoordinateReferenceSystem &crs, + QgsRasterBlockFeedback *feedback = nullptr ); /** \brief Initialize vrt member variables * \param xSize width of vrt @@ -194,7 +202,7 @@ class CORE_EXPORT QgsRasterFileWriter QDomDocument mVRTDocument; QList mVRTBands; - QProgressDialog *mProgressDialog = nullptr; + QgsRasterBlockFeedback *mFeedback = nullptr; const QgsRasterPipe *mPipe = nullptr; const QgsRasterInterface *mInput = nullptr; diff --git a/src/core/raster/qgsrasterfilewritertask.cpp b/src/core/raster/qgsrasterfilewritertask.cpp new file mode 100644 index 00000000000..b40fc64d473 --- /dev/null +++ b/src/core/raster/qgsrasterfilewritertask.cpp @@ -0,0 +1,61 @@ +/*************************************************************************** + qgsrasterfilewritertask.cpp + --------------------------- + begin : Apr 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson 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. * + * * + ***************************************************************************/ + +#include "qgsrasterfilewritertask.h" +#include "qgsrasterinterface.h" + +QgsRasterFileWriterTask::QgsRasterFileWriterTask( const QgsRasterFileWriter &writer, QgsRasterPipe *pipe, + int columns, int rows, + const QgsRectangle &outputExtent, + const QgsCoordinateReferenceSystem &crs ) + : QgsTask( tr( "Saving %1" ).arg( writer.outputUrl() ), QgsTask::CanCancel ) + , mWriter( writer ) + , mRows( rows ) + , mColumns( columns ) + , mExtent( outputExtent ) + , mCrs( crs ) + , mPipe( pipe ) + , mFeedback( new QgsRasterBlockFeedback() ) +{} + +void QgsRasterFileWriterTask::cancel() +{ + mFeedback->cancel(); + QgsTask::cancel(); +} + +bool QgsRasterFileWriterTask::run() +{ + if ( !mPipe ) + return false; + + connect( mFeedback.get(), &QgsRasterBlockFeedback::progressChanged, this, &QgsRasterFileWriterTask::setProgress ); + + mError = mWriter.writeRaster( mPipe.get(), mColumns, mRows, mExtent, mCrs, mFeedback.get() ); + + return mError == QgsRasterFileWriter::NoError; +} + +void QgsRasterFileWriterTask::finished( bool result ) +{ + if ( result ) + emit writeComplete( mWriter.outputUrl() ); + else + emit errorOccurred( mError ); +} + + diff --git a/src/core/raster/qgsrasterfilewritertask.h b/src/core/raster/qgsrasterfilewritertask.h new file mode 100644 index 00000000000..82480ea48bb --- /dev/null +++ b/src/core/raster/qgsrasterfilewritertask.h @@ -0,0 +1,91 @@ +/*************************************************************************** + qgsrasterfilewritertask.h + ------------------------- + begin : April 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson 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 QGSRASTERFILEWRITERTASK_H +#define QGSRASTERFILEWRITERTASK_H + +#include "qgis_core.h" +#include "qgstaskmanager.h" +#include "qgsrasterfilewriter.h" +#include "qgsrasterinterface.h" +#include "qgsrasterpipe.h" + +/** + * \class QgsRasterFileWriterTask + * \ingroup core + * QgsTask task which performs a QgsRasterFileWriter layer saving operation as a background + * task. This can be used to save a raster layer out to a file without blocking the + * QGIS interface. + * \see QgsVectorFileWriterTask + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsRasterFileWriterTask : public QgsTask +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsRasterFileWriterTask. Takes a source \a writer, + * \a columns, \a rows, \a outputExtent and destination \a crs. + * Ownership of the \a pipe is transferred to the writer task, and will + * be deleted when the task completes. + */ + QgsRasterFileWriterTask( const QgsRasterFileWriter &writer, QgsRasterPipe *pipe SIP_TRANSFER, + int columns, int rows, + const QgsRectangle &outputExtent, + const QgsCoordinateReferenceSystem &crs ); + + virtual void cancel() override; + + signals: + + /** + * Emitted when writing the layer is successfully completed. The \a outputUrl + * parameter indicates the file path for the written file(s). + */ + void writeComplete( const QString &outputUrl ); + + /** + * Emitted when an error occurs which prevented the file being written (or if + * the task is canceled). The writing \a error will be reported. + */ + void errorOccurred( int error ); + + protected: + + virtual bool run() override; + virtual void finished( bool result ) override; + + private: + + QgsRasterFileWriter mWriter; + int mRows = 0; + int mColumns = 0; + QgsRectangle mExtent; + QgsCoordinateReferenceSystem mCrs; + std::unique_ptr< QgsRasterPipe > mPipe; + + QString mDestFileName; + + std::unique_ptr< QgsRasterBlockFeedback > mFeedback; + + QgsRasterFileWriter::WriterError mError = QgsRasterFileWriter::NoError; + +}; + +#endif //QGSRASTERFILEWRITERTASK_H diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt old mode 100644 new mode 100755 index 0bc2a3ae9d0..a06aa28424e --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -97,6 +97,7 @@ ADD_PYTHON_TEST(PyQgsPointDisplacementRenderer test_qgspointdisplacementrenderer ADD_PYTHON_TEST(PyQgsProjectionSelectionWidgets test_qgsprojectionselectionwidgets.py) ADD_PYTHON_TEST(PyQgsRangeWidgets test_qgsrangewidgets.py) ADD_PYTHON_TEST(PyQgsRasterFileWriter test_qgsrasterfilewriter.py) +ADD_PYTHON_TEST(PyQgsRasterFileWriterTask test_qgsrasterfilewritertask.py) ADD_PYTHON_TEST(PyQgsRasterLayer test_qgsrasterlayer.py) ADD_PYTHON_TEST(PyQgsRasterColorRampShader test_qgsrastercolorrampshader.py) ADD_PYTHON_TEST(PyQgsRectangle test_qgsrectangle.py) diff --git a/tests/src/python/test_qgsrasterfilewritertask.py b/tests/src/python/test_qgsrasterfilewritertask.py new file mode 100755 index 00000000000..86e094ad53e --- /dev/null +++ b/tests/src/python/test_qgsrasterfilewritertask.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsRasterFileWriterTask. + +.. note:: 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. +""" +__author__ = 'Nyall Dawson' +__date__ = '12/02/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA +import os + +from qgis.core import ( + QgsApplication, + QgsRasterLayer, + QgsRasterPipe, + QgsRasterFileWriter, + QgsRasterFileWriterTask +) +from qgis.PyQt.QtCore import QCoreApplication, QDir +from qgis.testing import start_app, unittest +from utilities import unitTestDataPath + +start_app() + + +def create_temp_filename(base_file): + return os.path.join(str(QDir.tempPath()), base_file) + + +class TestQgsRasterFileWriterTask(unittest.TestCase): + def setUp(self): + self.success = False + self.fail = False + + def onSuccess(self): + self.success = True + + def onFail(self): + self.fail = True + + def testSuccess(self): + """test successfully writing a layer""" + path = os.path.join(unitTestDataPath(), 'raster', 'with_color_table.tif') + raster_layer = QgsRasterLayer(path, "test") + self.assertTrue(raster_layer.isValid()) + + pipe = QgsRasterPipe() + self.assertTrue(pipe.set(raster_layer.dataProvider().clone())) + + tmp = create_temp_filename('success.tif') + writer = QgsRasterFileWriter(tmp) + + task = QgsRasterFileWriterTask(writer, pipe, 100, 100, raster_layer.extent(), raster_layer.crs()) + + task.writeComplete.connect(self.onSuccess) + task.errorOccurred.connect(self.onFail) + + QgsApplication.taskManager().addTask(task) + while not self.success and not self.fail: + QCoreApplication.processEvents() + + self.assertTrue(self.success) + self.assertFalse(self.fail) + self.assertTrue(os.path.exists(tmp)) + + def testLayerRemovalBeforeRun(self): + """test behavior when layer is removed before task begins""" + path = os.path.join(unitTestDataPath(), 'raster', 'with_color_table.tif') + raster_layer = QgsRasterLayer(path, "test") + self.assertTrue(raster_layer.isValid()) + + pipe = QgsRasterPipe() + self.assertTrue(pipe.set(raster_layer.dataProvider().clone())) + + tmp = create_temp_filename('remove_layer.tif') + writer = QgsRasterFileWriter(tmp) + + task = QgsRasterFileWriterTask(writer, pipe, 100, 100, raster_layer.extent(), raster_layer.crs()) + + task.writeComplete.connect(self.onSuccess) + task.errorOccurred.connect(self.onFail) + + # remove layer + raster_layer = None + + QgsApplication.taskManager().addTask(task) + while not self.success and not self.fail: + QCoreApplication.processEvents() + + # in this case will still get a positive result - since the pipe is cloned before the task + # begins the task is no longer dependent on the original layer + self.assertTrue(self.success) + self.assertFalse(self.fail) + self.assertTrue(os.path.exists(tmp)) + + +if __name__ == '__main__': + unittest.main()