Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[feature] add native Round raster algorithm
- Loading branch information
1 parent
f792127
commit b9b7c4d
Showing
24 changed files
with
658 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
images/themes/default/algorithms/mAlgorithmRoundRastervalues.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
262 changes: 262 additions & 0 deletions
262
src/analysis/processing/qgsalgorithmroundrastervalues.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
/*************************************************************************** | ||
qgsalgorithmroundrastervalues.cpp | ||
--------------------- | ||
begin : April 2020 | ||
copyright : (C) 2020 by Clemens Raffler | ||
email : clemens dot raffler 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 "qgsalgorithmroundrastervalues.h" | ||
#include "qgsrasterfilewriter.h" | ||
|
||
///@cond PRIVATE | ||
|
||
QString QgsRoundRasterValuesAlgorithm::name() const | ||
{ | ||
return QStringLiteral( "roundrastervalues" ); | ||
} | ||
|
||
QString QgsRoundRasterValuesAlgorithm::displayName() const | ||
{ | ||
return QObject::tr( "Round raster" ); | ||
} | ||
|
||
QStringList QgsRoundRasterValuesAlgorithm::tags() const | ||
{ | ||
return QObject::tr( "data,cells,round,truncate" ).split( ',' ); | ||
} | ||
|
||
QString QgsRoundRasterValuesAlgorithm::group() const | ||
{ | ||
return QObject::tr( "Raster tools" ); | ||
} | ||
|
||
QString QgsRoundRasterValuesAlgorithm::groupId() const | ||
{ | ||
return QStringLiteral( "rastertools" ); | ||
} | ||
|
||
void QgsRoundRasterValuesAlgorithm::initAlgorithm( const QVariantMap & ) | ||
{ | ||
addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QStringLiteral( "Input raster" ) ) ); | ||
addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ), QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) ); | ||
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "ROUNDING_DIRECTION" ), QObject::tr( "Rouding direction" ), QStringList() << QObject::tr( "Round up" ) << QObject::tr( "Round to nearest" ) << QObject::tr( "Round down" ), false, 1 ) ); | ||
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DECIMAL_PLACES" ), QObject::tr( "Number of decimals places (use negative values to round cell values to a multiple of a base n, see advanced parameters)" ), QgsProcessingParameterNumber::Integer, 2 ) ); | ||
addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output raster" ) ) ); | ||
std::unique_ptr< QgsProcessingParameterDefinition > baseParameter = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "BASE_N" ), QObject::tr( "Base n (provides the base rounding raster values near/up/down to multiples of n when Decimal parameter is negative)" ), QgsProcessingParameterNumber::Integer, 10, true, 1 ); | ||
baseParameter->setFlags( QgsProcessingParameterDefinition::FlagAdvanced ); | ||
addParameter( baseParameter.release() ); | ||
} | ||
|
||
QString QgsRoundRasterValuesAlgorithm::shortHelpString() const | ||
{ | ||
return QObject::tr( "This algorithm rounds the cell values of a rasterdataset according to the specified number of decimals.\n " | ||
"Alternatively, a negative number of decimal places may be used to round values to powers of a base n " | ||
"(specified in the advanced parameter Base n). For example, with a Base value n of 10 and Decimal places of -1 " | ||
"the algorithm rounds cell values to multiples of 10, -2 rounds to multiples of 100, and so on. Arbitrary base values " | ||
"may be chosen, the algorithm applies the same multiplicative principle.Rounding cell values to multiples of " | ||
"a base n may be used to generalize raster layers.\n" | ||
"The algorithm preserves the data type of the input raster. Therefore byte/integer rasters can only be rounded " | ||
"to multiples of a base n, otherwise a warning is raised and the raster gets copied as byte/integer raster" ); | ||
} | ||
|
||
QgsRoundRasterValuesAlgorithm *QgsRoundRasterValuesAlgorithm::createInstance() const | ||
{ | ||
return new QgsRoundRasterValuesAlgorithm(); | ||
} | ||
|
||
bool QgsRoundRasterValuesAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) | ||
{ | ||
Q_UNUSED( feedback ); | ||
mInputRaster = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context ); | ||
mDecimalPrecision = parameterAsInt( parameters, QStringLiteral( "DECIMAL_PLACES" ), context ); | ||
mBaseN = parameterAsInt( parameters, QStringLiteral( "BASE_N" ), context ); | ||
mMultipleOfBaseN = pow( mBaseN, abs( mDecimalPrecision ) ); | ||
|
||
if ( !mInputRaster ) | ||
throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) ); | ||
|
||
mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context ); | ||
if ( mBand < 1 || mBand > mInputRaster->bandCount() ) | ||
throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( mInputRaster->bandCount() ) ); | ||
|
||
mRoundingDirection = parameterAsEnum( parameters, QStringLiteral( "ROUNDING_DIRECTION" ), context ); | ||
|
||
mInterface.reset( mInputRaster->dataProvider()->clone() ); | ||
mDataType = mInterface->dataType( mBand ); | ||
|
||
switch ( mDataType ) | ||
{ | ||
case Qgis::Byte: | ||
case Qgis::Int16: | ||
case Qgis::UInt16: | ||
case Qgis::Int32: | ||
case Qgis::UInt32: | ||
mIsInteger = true; | ||
if ( mDecimalPrecision > -1 ) | ||
feedback->reportError( QObject::tr( "Input raster is of byte or integer type. The cell values cannot be rounded and will be output using the same data type." ), false ); | ||
break; | ||
default: | ||
mIsInteger = false; | ||
break; | ||
} | ||
|
||
mInputNoDataValue = mInputRaster->dataProvider()->sourceNoDataValue( mBand ); | ||
mExtent = mInputRaster->extent(); | ||
mLayerWidth = mInputRaster->width(); | ||
mLayerHeight = mInputRaster->height(); | ||
mCrs = mInputRaster->crs(); | ||
mNbCellsXProvider = mInterface->xSize(); | ||
mNbCellsYProvider = mInterface->ySize(); | ||
return true; | ||
} | ||
|
||
QVariantMap QgsRoundRasterValuesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) | ||
{ | ||
//prepare output dataset | ||
const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context ); | ||
QFileInfo fi( outputFile ); | ||
const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() ); | ||
std::unique_ptr< QgsRasterFileWriter > writer = qgis::make_unique< QgsRasterFileWriter >( outputFile ); | ||
writer->setOutputProviderKey( QStringLiteral( "gdal" ) ); | ||
writer->setOutputFormat( outputFormat ); | ||
std::unique_ptr<QgsRasterDataProvider > provider( writer->createOneBandRaster( mInterface->dataType( mBand ), mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) ); | ||
if ( !provider ) | ||
throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) ); | ||
if ( !provider->isValid() ) | ||
throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) ); | ||
|
||
//prepare output provider | ||
QgsRasterDataProvider *destinationRasterProvider; | ||
destinationRasterProvider = provider.get(); | ||
destinationRasterProvider->setEditable( true ); | ||
destinationRasterProvider->setNoDataValue( 1, mInputNoDataValue ); | ||
|
||
int maxWidth = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH; | ||
int maxHeight = QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT; | ||
int nbBlocksWidth = static_cast< int >( std::ceil( 1.0 * mLayerWidth / maxWidth ) ); | ||
int nbBlocksHeight = static_cast< int >( std::ceil( 1.0 * mLayerHeight / maxHeight ) ); | ||
int nbBlocks = nbBlocksWidth * nbBlocksHeight; | ||
|
||
QgsRasterIterator iter( mInterface.get() ); | ||
iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent ); | ||
int iterLeft = 0; | ||
int iterTop = 0; | ||
int iterCols = 0; | ||
int iterRows = 0; | ||
std::unique_ptr< QgsRasterBlock > analysisRasterBlock; | ||
while ( iter.readNextRasterPart( mBand, iterCols, iterRows, analysisRasterBlock, iterLeft, iterTop ) ) | ||
{ | ||
if ( feedback ) | ||
feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks ); | ||
if ( mIsInteger && mDecimalPrecision > -1 ) | ||
{ | ||
//nothing to round, just write raster block | ||
analysisRasterBlock->setNoDataValue( mInputNoDataValue ); | ||
destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop ); | ||
} | ||
else | ||
{ | ||
for ( int row = 0; row < iterRows; row++ ) | ||
{ | ||
if ( feedback && feedback->isCanceled() ) | ||
break; | ||
for ( int column = 0; column < iterCols; column++ ) | ||
{ | ||
if ( analysisRasterBlock->isNoData( row, column ) ) | ||
{ | ||
analysisRasterBlock->setValue( row, column, mInputNoDataValue ); | ||
} | ||
else | ||
{ | ||
double val = analysisRasterBlock->value( row, column ); | ||
double roundedVal = mInputNoDataValue; | ||
if ( mRoundingDirection == 0 && mDecimalPrecision < 0 ) | ||
{ | ||
roundedVal = roundUpBaseN( val, mMultipleOfBaseN ); | ||
} | ||
else if ( mRoundingDirection == 0 && mDecimalPrecision > -1 ) | ||
{ | ||
roundedVal = roundUp( val, mDecimalPrecision ); | ||
} | ||
else if ( mRoundingDirection == 1 && mDecimalPrecision < 0 ) | ||
{ | ||
roundedVal = roundNearestBaseN( val, mMultipleOfBaseN ); | ||
} | ||
else if ( mRoundingDirection == 1 && mDecimalPrecision > -1 ) | ||
{ | ||
roundedVal = roundNearest( val, mDecimalPrecision ); | ||
} | ||
else if ( mRoundingDirection == 2 && mDecimalPrecision < 0 ) | ||
{ | ||
roundedVal = roundDownBaseN( val, mMultipleOfBaseN ); | ||
} | ||
else | ||
{ | ||
roundedVal = roundDown( val, mDecimalPrecision ); | ||
} | ||
//intergers get automatically cast to double when reading and back to int when writing | ||
analysisRasterBlock->setValue( row, column, roundedVal ); | ||
} | ||
} | ||
} | ||
destinationRasterProvider->writeBlock( analysisRasterBlock.get(), mBand, iterLeft, iterTop ); | ||
} | ||
} | ||
destinationRasterProvider->setEditable( false ); | ||
|
||
QVariantMap outputs; | ||
outputs.insert( QStringLiteral( "OUTPUT" ), outputFile ); | ||
return outputs; | ||
} | ||
|
||
double QgsRoundRasterValuesAlgorithm::roundNearest( double &value, int &decimals ) | ||
{ | ||
double m = ( value < 0.0 ) ? -1.0 : 1.0; | ||
double scaleFactor = std::pow( 10.0, decimals ); | ||
return ( std::round( value * m * scaleFactor ) / scaleFactor ) * m; | ||
} | ||
|
||
double QgsRoundRasterValuesAlgorithm::roundUp( double &value, int &decimals ) | ||
{ | ||
double m = ( value < 0.0 ) ? -1.0 : 1.0; | ||
double scaleFactor = std::pow( 10.0, decimals ); | ||
return ( std::ceil( value * m * scaleFactor ) / scaleFactor ) * m; | ||
} | ||
|
||
double QgsRoundRasterValuesAlgorithm::roundDown( double &value, int &decimals ) | ||
{ | ||
double m = ( value < 0.0 ) ? -1.0 : 1.0; | ||
double scaleFactor = std::pow( 10.0, decimals ); | ||
return ( std::floor( value * m * scaleFactor ) / scaleFactor ) * m; | ||
} | ||
|
||
|
||
double QgsRoundRasterValuesAlgorithm::roundNearestBaseN( double &value, int &multipleOfBaseN ) | ||
{ | ||
return static_cast<double>( multipleOfBaseN * round( value / multipleOfBaseN ) ); | ||
} | ||
|
||
double QgsRoundRasterValuesAlgorithm::roundUpBaseN( double &value, int &multipleOfBaseN ) | ||
{ | ||
return static_cast<double>( multipleOfBaseN * ceil( value / multipleOfBaseN ) ); | ||
} | ||
|
||
double QgsRoundRasterValuesAlgorithm::roundDownBaseN( double &value, int &multipleOfBaseN ) | ||
{ | ||
return static_cast<double>( multipleOfBaseN * floor( value / multipleOfBaseN ) ); | ||
} | ||
|
||
|
||
|
||
|
||
///@endcond |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/*************************************************************************** | ||
qgsalgorithmroundrastervalues.h | ||
--------------------- | ||
begin : April 2020 | ||
copyright : (C) 2020 by Clemens Raffler | ||
email : clemens dot raffler 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 QGSALGORITHMROUNDRASTERVALUES_H | ||
#define QGSALGORITHMROUNDRASTERVALUES_H | ||
|
||
#define SIP_NO_FILE | ||
|
||
#include "qgis_sip.h" | ||
#include "qgsprocessingalgorithm.h" | ||
#include "qgsapplication.h" | ||
|
||
///@cond PRIVATE | ||
|
||
/** | ||
* Round raster values algorithm: | ||
* This algorithm rounds the Values of floating point raster datasets | ||
* based on a predefined precision value. | ||
*/ | ||
class QgsRoundRasterValuesAlgorithm : public QgsProcessingAlgorithm | ||
{ | ||
public: | ||
|
||
QgsRoundRasterValuesAlgorithm() = default; | ||
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; | ||
QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmRoundRastervalues.svg" ) ); } | ||
QString svgIconPath() const override { return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmRoundRastervalues.svg" ) ); } | ||
QString name() const override; | ||
QString displayName() const override; | ||
QStringList tags() const override; | ||
QString group() const override; | ||
QString groupId() const override; | ||
QString shortHelpString() const override; | ||
QgsRoundRasterValuesAlgorithm *createInstance() const override SIP_FACTORY; | ||
|
||
protected: | ||
bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; | ||
QVariantMap processAlgorithm( const QVariantMap ¶meters, | ||
QgsProcessingContext &context, | ||
QgsProcessingFeedback *feedback ) override; | ||
|
||
private: | ||
double roundNearest( double &value, int &decimals ); | ||
double roundUp( double &value, int &decimals ); | ||
double roundDown( double &value, int &decimals ); | ||
double roundNearestBaseN( double &value, int &multipleOfBaseN ); | ||
double roundUpBaseN( double &value, int &multipleOfBaseN ); | ||
double roundDownBaseN( double &value, int &multipleOfBaseN ); | ||
|
||
QgsRasterLayer *mInputRaster; | ||
int mDecimalPrecision = 2; | ||
int mBaseN = 10; | ||
int mMultipleOfBaseN; | ||
int mBand; | ||
int mRoundingDirection; | ||
std::unique_ptr< QgsRasterInterface > mInterface; | ||
Qgis::DataType mDataType; | ||
bool mIsInteger; | ||
QgsRectangle mExtent; | ||
QgsCoordinateReferenceSystem mCrs; | ||
int mLayerWidth; | ||
int mLayerHeight; | ||
int mNbCellsXProvider = 0; | ||
int mNbCellsYProvider = 0; | ||
double mInputNoDataValue; | ||
}; | ||
|
||
///@endcond PRIVATE | ||
|
||
#endif // QGSALGORITHMROUNDRASTERVALUES_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.