Skip to content

Commit

Permalink
[feature] allow creation of constant raster with different raster dat…
Browse files Browse the repository at this point in the history
…a types
  • Loading branch information
root676 authored and nyalldawson committed Apr 13, 2020
1 parent 22ade41 commit e2e4a99
Showing 1 changed file with 134 additions and 6 deletions.
140 changes: 134 additions & 6 deletions src/analysis/processing/qgsalgorithmconstantraster.cpp
Expand Up @@ -15,6 +15,8 @@
* *
***************************************************************************/

#include <limits>
#include "math.h"
#include "qgsalgorithmconstantraster.h"
#include "qgsrasterfilewriter.h"

Expand Down Expand Up @@ -65,6 +67,20 @@ void QgsConstantRasterAlgorithm::initAlgorithm( const QVariantMap & )
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "NUMBER" ), QObject::tr( "Constant value" ),
QgsProcessingParameterNumber::Double, 1, false ) );

QStringList rasterDataTypes = QStringList(); //currently supported raster data types that can be handled QgsRasterBlock::writeValue()
rasterDataTypes << QStringLiteral( "Byte" )
<< QStringLiteral( "Integer16" )
<< QStringLiteral( "Unsigned Integer16" )
<< QStringLiteral( "Integer32" )
<< QStringLiteral( "Unsigned Integer32" )
<< QStringLiteral( "Float32" )
<< QStringLiteral( "Float64" );

//QGIS3: parameter set to Float32 by default so that existing models/scripts don't break
std::unique_ptr< QgsProcessingParameterDefinition > rasterTypeParameter = qgis::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "OUTPUT_TYPE" ), QObject::tr( "Output raster data type" ), rasterDataTypes, false, 5, false );
rasterTypeParameter->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( rasterTypeParameter.release() );

addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Constant" ) ) );
}

Expand All @@ -74,7 +90,60 @@ QVariantMap QgsConstantRasterAlgorithm::processAlgorithm( const QVariantMap &par
QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, crs );
double pixelSize = parameterAsDouble( parameters, QStringLiteral( "PIXEL_SIZE" ), context );
double value = parameterAsDouble( parameters, QStringLiteral( "NUMBER" ), context );
int typeId = parameterAsInt( parameters, QStringLiteral( "OUTPUT_TYPE" ), context );

//implement warning if input float has decimal places but is written to integer raster
double fractpart;
double intpart;
fractpart = abs( std::modf( value, &intpart ) ); //@abs: negative values may be entered

Qgis::DataType rasterDataType = Qgis::Float32; //standard output type
switch ( typeId )
{
case ( 0 ):
rasterDataType = Qgis::Byte;
if ( value < std::numeric_limits<quint8>::min() || value > std::numeric_limits<quint8>::max() )
throw QgsProcessingException( QObject::tr( "Raster datasets of type Byte only accept positive values between %1 and %2" ).arg( std::numeric_limits<quint8>::min() ).arg( std::numeric_limits<quint8>::max() ) );
if ( fractpart > 0 )
feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type Byte. The decimals of the constant value will be omitted." ) );
break;
case ( 1 ):
rasterDataType = Qgis::Int16;
if ( value < std::numeric_limits<qint16>::min() || value > std::numeric_limits<qint16>::max() )
throw QgsProcessingException( QObject::tr( "Raster datasets of type Integer16 only accept values between %1 and %2" ).arg( std::numeric_limits<qint16>::min() ).arg( std::numeric_limits<qint16>::max() ) );
if ( fractpart > 0 )
feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type Integer16. The decimals of the constant value will be omitted." ) );
break;
case ( 2 ):
rasterDataType = Qgis::UInt16;
if ( value < std::numeric_limits<quint16>::min() || value > std::numeric_limits<quint16>::max() )
throw QgsProcessingException( QObject::tr( "Raster datasets of type Unsigned Integer16 only accept positive values between %1 and %2" ).arg( std::numeric_limits<quint16>::min() ).arg( std::numeric_limits<quint16>::max() ) );
if ( fractpart > 0 )
feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type Unsigned Integer16. The decimals of the constant value will be omitted." ) );
break;
case ( 3 ):
rasterDataType = Qgis::Int32;
if ( value < std::numeric_limits<qint32>::min() || value > std::numeric_limits<qint32>::max() )
throw QgsProcessingException( QObject::tr( "Raster datasets of type Integer32 only accept values between %1 and %2" ).arg( std::numeric_limits<qint32>::min() ).arg( std::numeric_limits<qint32>::max() ) );
if ( fractpart > 0 )
feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type Integer32. The decimals of the constant value will be omitted." ) );
break;
case ( 4 ):
rasterDataType = Qgis::UInt32;
if ( value < std::numeric_limits<quint32>::min() || value > std::numeric_limits<quint32>::max() )
throw QgsProcessingException( QObject::tr( "Raster datasets of type Unsigned Integer32 only accept positive values between %1 and %2" ).arg( std::numeric_limits<quint32>::min() ).arg( std::numeric_limits<quint32>::max() ) );
if ( fractpart > 0 )
feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type Unsigned Integer32. The decimals of the constant value will be omitted." ) );
break;
case ( 5 ):
rasterDataType = Qgis::Float32;
break;
case ( 6 ):
rasterDataType = Qgis::Float64;
break;
default:
break;
}
const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
QFileInfo fi( outputFile );
const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
Expand All @@ -89,18 +158,77 @@ QVariantMap QgsConstantRasterAlgorithm::processAlgorithm( const QVariantMap &par
std::unique_ptr< QgsRasterFileWriter > writer = qgis::make_unique< QgsRasterFileWriter >( outputFile );
writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
writer->setOutputFormat( outputFormat );
std::unique_ptr<QgsRasterDataProvider > provider( writer->createOneBandRaster( Qgis::Float32, cols, rows, rasterExtent, crs ) );
std::unique_ptr<QgsRasterDataProvider > provider( writer->createOneBandRaster( rasterDataType, cols, rows, rasterExtent, crs ) );
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 ) ) );

provider->setNoDataValue( 1, -9999 );
//Thoughts on noData:
//Setting a noData value is disabled so that the user is protected from accidentally creating an empty raster (eg. when value is set to -9999)
//We could also allow creating empty rasters by exposing a noData value parameter (usecases?).

std::vector<float> line( cols );
std::fill( line.begin(), line.end(), value );
QgsRasterBlock block( Qgis::Float32, cols, 1 );
block.setData( QByteArray::fromRawData( ( char * )&line[0], QgsRasterBlock::typeSize( Qgis::Float32 ) * cols ) );
//prepare raw data depending on raster data type
QgsRasterBlock block( rasterDataType, cols, 1 );
switch ( typeId )
{
case ( 0 ):
{
std::vector<quint8> byteRow( cols );
std::fill( byteRow.begin(), byteRow.end(), value );
block.setData( QByteArray::fromRawData( ( char * )&byteRow[0], QgsRasterBlock::typeSize( Qgis::Byte ) * cols ) );
break;
}
case ( 1 ):
{
std::vector<qint16> int16Row( cols );
std::fill( int16Row.begin(), int16Row.end(), value );
block.setData( QByteArray::fromRawData( ( char * )&int16Row[0], QgsRasterBlock::typeSize( Qgis::Int16 ) * cols ) );
break;
}
case ( 2 ):
{
std::vector<quint16> uInt16Row( cols );
std::fill( uInt16Row.begin(), uInt16Row.end(), value );
block.setData( QByteArray::fromRawData( ( char * )&uInt16Row[0], QgsRasterBlock::typeSize( Qgis::UInt16 ) * cols ) );
break;
}
case ( 3 ):
{
std::vector<qint32> int32Row( cols );
std::fill( int32Row.begin(), int32Row.end(), value );
block.setData( QByteArray::fromRawData( ( char * )&int32Row[0], QgsRasterBlock::typeSize( Qgis::Int32 ) * cols ) );
break;
}
case ( 4 ):
{
std::vector<quint32> uInt32Row( cols );
std::fill( uInt32Row.begin(), uInt32Row.end(), value );
block.setData( QByteArray::fromRawData( ( char * )&uInt32Row[0], QgsRasterBlock::typeSize( Qgis::UInt32 ) * cols ) );
break;
}
case ( 5 ):
{
std::vector<float> float32Row( cols );
std::fill( float32Row.begin(), float32Row.end(), value );
block.setData( QByteArray::fromRawData( ( char * )&float32Row[0], QgsRasterBlock::typeSize( Qgis::Float32 ) * cols ) );
break;
}
case ( 6 ):
{
std::vector<double> float64Row( cols );
std::fill( float64Row.begin(), float64Row.end(), value );
block.setData( QByteArray::fromRawData( ( char * )&float64Row[0], QgsRasterBlock::typeSize( Qgis::Float64 ) * cols ) );
break;
}
default:
{
std::vector<float> float32Row( cols );
std::fill( float32Row.begin(), float32Row.end(), value );
block.setData( QByteArray::fromRawData( ( char * )&float32Row[0], QgsRasterBlock::typeSize( Qgis::Float32 ) * cols ) );
break;
}
}

double step = rows > 0 ? 100.0 / rows : 1;

Expand Down

0 comments on commit e2e4a99

Please sign in to comment.