Skip to content

Commit

Permalink
[feature] add native Round raster algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
root676 authored and nyalldawson committed Apr 12, 2020
1 parent f792127 commit b9b7c4d
Show file tree
Hide file tree
Showing 24 changed files with 658 additions and 0 deletions.
1 change: 1 addition & 0 deletions images/images.qrc
Expand Up @@ -116,6 +116,7 @@
<file>themes/default/algorithms/mAlgorithmRandomPointsWithinPolygon.svg</file>
<file>themes/default/algorithms/mAlgorithmRandomPointsWithinExtent.svg</file>
<file>themes/default/algorithms/mAlgorithmRegularPoints.svg</file>
<file>themes/default/algorithms/mAlgorithmRoundRastervalues.svg</file>
<file>themes/default/algorithms/mAlgorithmSelectLocation.svg</file>
<file>themes/default/algorithms/mAlgorithmSelectRandom.svg</file>
<file>themes/default/algorithms/mAlgorithmSimplify.svg</file>
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -130,6 +130,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmrepairshapefile.cpp
processing/qgsalgorithmreverselinedirection.cpp
processing/qgsalgorithmrotate.cpp
processing/qgsalgorithmroundrastervalues.cpp
processing/qgsalgorithmruggedness.cpp
processing/qgsalgorithmsavelog.cpp
processing/qgsalgorithmsaveselectedfeatures.cpp
Expand Down
262 changes: 262 additions & 0 deletions src/analysis/processing/qgsalgorithmroundrastervalues.cpp
@@ -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 &parameters, 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 &parameters, 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
85 changes: 85 additions & 0 deletions src/analysis/processing/qgsalgorithmroundrastervalues.h
@@ -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 &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QVariantMap processAlgorithm( const QVariantMap &parameters,
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
2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -124,6 +124,7 @@
#include "qgsalgorithmrepairshapefile.h"
#include "qgsalgorithmreverselinedirection.h"
#include "qgsalgorithmrotate.h"
#include "qgsalgorithmroundrastervalues.h"
#include "qgsalgorithmruggedness.h"
#include "qgsalgorithmsavelog.h"
#include "qgsalgorithmsaveselectedfeatures.h"
Expand Down Expand Up @@ -331,6 +332,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsRepairShapefileAlgorithm() );
addAlgorithm( new QgsReverseLineDirectionAlgorithm() );
addAlgorithm( new QgsRotateFeaturesAlgorithm() );
addAlgorithm( new QgsRoundRasterValuesAlgorithm() );
addAlgorithm( new QgsRuggednessAlgorithm() );
addAlgorithm( new QgsSaveLogToFileAlgorithm() );
addAlgorithm( new QgsSaveSelectedFeatures() );
Expand Down

0 comments on commit b9b7c4d

Please sign in to comment.