Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsalgorithmconstantraster.cpp
3 : : ---------------------
4 : : begin : November 2019
5 : : copyright : (C) 2019 by Alexander Bruy
6 : : email : alexander dot bruy at gmail dot com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include <limits>
19 : : #include "math.h"
20 : : #include "qgsalgorithmconstantraster.h"
21 : : #include "qgsrasterfilewriter.h"
22 : :
23 : : ///@cond PRIVATE
24 : :
25 : 0 : QString QgsConstantRasterAlgorithm::name() const
26 : : {
27 : 0 : return QStringLiteral( "createconstantrasterlayer" );
28 : : }
29 : :
30 : 0 : QString QgsConstantRasterAlgorithm::displayName() const
31 : : {
32 : 0 : return QObject::tr( "Create constant raster layer" );
33 : : }
34 : :
35 : 0 : QStringList QgsConstantRasterAlgorithm::tags() const
36 : : {
37 : 0 : return QObject::tr( "raster,create,constant" ).split( ',' );
38 : 0 : }
39 : :
40 : 0 : QString QgsConstantRasterAlgorithm::group() const
41 : : {
42 : 0 : return QObject::tr( "Raster creation" );
43 : : }
44 : :
45 : 0 : QString QgsConstantRasterAlgorithm::groupId() const
46 : : {
47 : 0 : return QStringLiteral( "rastercreation" );
48 : : }
49 : :
50 : 0 : QString QgsConstantRasterAlgorithm::shortHelpString() const
51 : : {
52 : 0 : return QObject::tr( "Generates raster layer for given extent and cell "
53 : : "size filled with the specified value.\n"
54 : : "Additionally an output data type can be specified. "
55 : : "The algorithm will abort if a value has been entered that "
56 : : "cannot be represented by the selected output raster data type." );
57 : : }
58 : :
59 : 0 : QgsConstantRasterAlgorithm *QgsConstantRasterAlgorithm::createInstance() const
60 : : {
61 : 0 : return new QgsConstantRasterAlgorithm();
62 : : }
63 : :
64 : 0 : void QgsConstantRasterAlgorithm::initAlgorithm( const QVariantMap & )
65 : : {
66 : 0 : addParameter( new QgsProcessingParameterExtent( QStringLiteral( "EXTENT" ), QObject::tr( "Desired extent" ) ) );
67 : 0 : addParameter( new QgsProcessingParameterCrs( QStringLiteral( "TARGET_CRS" ), QObject::tr( "Target CRS" ), QStringLiteral( "ProjectCrs" ) ) );
68 : 0 : addParameter( new QgsProcessingParameterNumber( QStringLiteral( "PIXEL_SIZE" ), QObject::tr( "Pixel size" ),
69 : 0 : QgsProcessingParameterNumber::Double, 0.00001, false, 0.01 ) );
70 : 0 : addParameter( new QgsProcessingParameterNumber( QStringLiteral( "NUMBER" ), QObject::tr( "Constant value" ),
71 : 0 : QgsProcessingParameterNumber::Double, 1, false ) );
72 : :
73 : 0 : QStringList rasterDataTypes; //currently supported raster data types that can be handled QgsRasterBlock::writeValue()
74 : 0 : rasterDataTypes << QStringLiteral( "Byte" )
75 : 0 : << QStringLiteral( "Integer16" )
76 : 0 : << QStringLiteral( "Unsigned Integer16" )
77 : 0 : << QStringLiteral( "Integer32" )
78 : 0 : << QStringLiteral( "Unsigned Integer32" )
79 : 0 : << QStringLiteral( "Float32" )
80 : 0 : << QStringLiteral( "Float64" );
81 : :
82 : : //QGIS3: parameter set to Float32 by default so that existing models/scripts don't break
83 : 0 : std::unique_ptr< QgsProcessingParameterDefinition > rasterTypeParameter = std::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "OUTPUT_TYPE" ), QObject::tr( "Output raster data type" ), rasterDataTypes, false, 5, false );
84 : 0 : rasterTypeParameter->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
85 : 0 : addParameter( rasterTypeParameter.release() );
86 : :
87 : 0 : addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Constant" ) ) );
88 : 0 : }
89 : :
90 : 0 : QVariantMap QgsConstantRasterAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
91 : : {
92 : 0 : QgsCoordinateReferenceSystem crs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context );
93 : 0 : QgsRectangle extent = parameterAsExtent( parameters, QStringLiteral( "EXTENT" ), context, crs );
94 : 0 : double pixelSize = parameterAsDouble( parameters, QStringLiteral( "PIXEL_SIZE" ), context );
95 : 0 : double value = parameterAsDouble( parameters, QStringLiteral( "NUMBER" ), context );
96 : 0 : int typeId = parameterAsInt( parameters, QStringLiteral( "OUTPUT_TYPE" ), context );
97 : :
98 : : //implement warning if input float has decimal places but is written to integer raster
99 : : double fractpart;
100 : : double intpart;
101 : 0 : fractpart = abs( std::modf( value, &intpart ) ); //@abs: negative values may be entered
102 : :
103 : 0 : Qgis::DataType rasterDataType = Qgis::Float32; //standard output type
104 : 0 : switch ( typeId )
105 : : {
106 : : case 0:
107 : 0 : rasterDataType = Qgis::Byte;
108 : 0 : if ( value < std::numeric_limits<quint8>::min() || value > std::numeric_limits<quint8>::max() )
109 : 0 : throw QgsProcessingException( QObject::tr( "Raster datasets of type %3 only accept positive values between %1 and %2" ).arg( std::numeric_limits<quint8>::min() ).arg( std::numeric_limits<quint8>::max() ).arg( QLatin1String( "Byte" ) ) );
110 : 0 : if ( fractpart > 0 )
111 : 0 : feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type %1. The decimals of the constant value will be omitted." ).arg( QLatin1String( "Byte" ) ) );
112 : 0 : break;
113 : : case 1:
114 : 0 : rasterDataType = Qgis::Int16;
115 : 0 : if ( value < std::numeric_limits<qint16>::min() || value > std::numeric_limits<qint16>::max() )
116 : 0 : throw QgsProcessingException( QObject::tr( "Raster datasets of type %3 only accept values between %1 and %2" ).arg( std::numeric_limits<qint16>::min() ).arg( std::numeric_limits<qint16>::max() ).arg( QLatin1String( "Integer16" ) ) );
117 : 0 : if ( fractpart > 0 )
118 : 0 : feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type %1. The decimals of the constant value will be omitted." ).arg( QLatin1String( "Integer16" ) ) );
119 : 0 : break;
120 : : case 2:
121 : 0 : rasterDataType = Qgis::UInt16;
122 : 0 : if ( value < std::numeric_limits<quint16>::min() || value > std::numeric_limits<quint16>::max() )
123 : 0 : throw QgsProcessingException( QObject::tr( "Raster datasets of type %3 only accept positive values between %1 and %2" ).arg( std::numeric_limits<quint16>::min() ).arg( std::numeric_limits<quint16>::max() ).arg( "Unsigned Integer16" ) );
124 : 0 : if ( fractpart > 0 )
125 : 0 : feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type %1. The decimals of the constant value will be omitted." ).arg( QLatin1String( "Unsigned Integer16" ) ) );
126 : 0 : break;
127 : : case 3:
128 : 0 : rasterDataType = Qgis::Int32;
129 : 0 : if ( value < std::numeric_limits<qint32>::min() || value > std::numeric_limits<qint32>::max() )
130 : 0 : throw QgsProcessingException( QObject::tr( "Raster datasets of type %3 only accept values between %1 and %2" ).arg( std::numeric_limits<qint32>::min() ).arg( std::numeric_limits<qint32>::max() ).arg( QLatin1String( "Integer32" ) ) );
131 : 0 : if ( fractpart > 0 )
132 : 0 : feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type %1. The decimals of the constant value will be omitted." ).arg( QLatin1String( "Integer32" ) ) );
133 : 0 : break;
134 : : case 4:
135 : 0 : rasterDataType = Qgis::UInt32;
136 : 0 : if ( value < std::numeric_limits<quint32>::min() || value > std::numeric_limits<quint32>::max() )
137 : 0 : throw QgsProcessingException( QObject::tr( "Raster datasets of type %3 only accept positive values between %1 and %2" ).arg( std::numeric_limits<quint32>::min() ).arg( std::numeric_limits<quint32>::max() ).arg( QLatin1String( "Unsigned Integer32" ) ) );
138 : 0 : if ( fractpart > 0 )
139 : 0 : feedback->reportError( QObject::tr( "The entered constant value has decimals but will be written to a raster dataset of type %1. The decimals of the constant value will be omitted." ).arg( QLatin1String( "Unsigned Integer32" ) ) );
140 : 0 : break;
141 : : case 5:
142 : 0 : rasterDataType = Qgis::Float32;
143 : 0 : break;
144 : : case 6:
145 : 0 : rasterDataType = Qgis::Float64;
146 : 0 : break;
147 : : default:
148 : 0 : break;
149 : : }
150 : 0 : const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
151 : 0 : QFileInfo fi( outputFile );
152 : 0 : const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
153 : :
154 : 0 : int rows = std::max( std::ceil( extent.height() / pixelSize ), 1.0 );
155 : 0 : int cols = std::max( std::ceil( extent.width() / pixelSize ), 1.0 );
156 : :
157 : : //build new raster extent based on number of columns and cellsize
158 : : //this prevents output cellsize being calculated too small
159 : 0 : QgsRectangle rasterExtent = QgsRectangle( extent.xMinimum(), extent.yMaximum() - ( rows * pixelSize ), extent.xMinimum() + ( cols * pixelSize ), extent.yMaximum() );
160 : :
161 : 0 : std::unique_ptr< QgsRasterFileWriter > writer = std::make_unique< QgsRasterFileWriter >( outputFile );
162 : 0 : writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
163 : 0 : writer->setOutputFormat( outputFormat );
164 : 0 : std::unique_ptr<QgsRasterDataProvider > provider( writer->createOneBandRaster( rasterDataType, cols, rows, rasterExtent, crs ) );
165 : 0 : if ( !provider )
166 : 0 : throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
167 : 0 : if ( !provider->isValid() )
168 : 0 : throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, provider->error().message( QgsErrorMessage::Text ) ) );
169 : :
170 : : //Thoughts on noData:
171 : : //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)
172 : : //We could also allow creating empty rasters by exposing a noData value parameter (usecases?).
173 : :
174 : : //prepare raw data depending on raster data type
175 : 0 : QgsRasterBlock block( rasterDataType, cols, 1 );
176 : 0 : switch ( typeId )
177 : : {
178 : : case 0:
179 : : {
180 : 0 : std::vector<quint8> byteRow( cols );
181 : 0 : std::fill( byteRow.begin(), byteRow.end(), value );
182 : 0 : block.setData( QByteArray::fromRawData( ( char * )&byteRow[0], QgsRasterBlock::typeSize( Qgis::Byte ) * cols ) );
183 : : break;
184 : 0 : }
185 : : case 1:
186 : : {
187 : 0 : std::vector<qint16> int16Row( cols );
188 : 0 : std::fill( int16Row.begin(), int16Row.end(), value );
189 : 0 : block.setData( QByteArray::fromRawData( ( char * )&int16Row[0], QgsRasterBlock::typeSize( Qgis::Int16 ) * cols ) );
190 : : break;
191 : 0 : }
192 : : case 2:
193 : : {
194 : 0 : std::vector<quint16> uInt16Row( cols );
195 : 0 : std::fill( uInt16Row.begin(), uInt16Row.end(), value );
196 : 0 : block.setData( QByteArray::fromRawData( ( char * )&uInt16Row[0], QgsRasterBlock::typeSize( Qgis::UInt16 ) * cols ) );
197 : : break;
198 : 0 : }
199 : : case 3:
200 : : {
201 : 0 : std::vector<qint32> int32Row( cols );
202 : 0 : std::fill( int32Row.begin(), int32Row.end(), value );
203 : 0 : block.setData( QByteArray::fromRawData( ( char * )&int32Row[0], QgsRasterBlock::typeSize( Qgis::Int32 ) * cols ) );
204 : : break;
205 : 0 : }
206 : : case 4:
207 : : {
208 : 0 : std::vector<quint32> uInt32Row( cols );
209 : 0 : std::fill( uInt32Row.begin(), uInt32Row.end(), value );
210 : 0 : block.setData( QByteArray::fromRawData( ( char * )&uInt32Row[0], QgsRasterBlock::typeSize( Qgis::UInt32 ) * cols ) );
211 : : break;
212 : 0 : }
213 : : case 5:
214 : : {
215 : 0 : std::vector<float> float32Row( cols );
216 : 0 : std::fill( float32Row.begin(), float32Row.end(), value );
217 : 0 : block.setData( QByteArray::fromRawData( ( char * )&float32Row[0], QgsRasterBlock::typeSize( Qgis::Float32 ) * cols ) );
218 : : break;
219 : 0 : }
220 : : case 6:
221 : : {
222 : 0 : std::vector<double> float64Row( cols );
223 : 0 : std::fill( float64Row.begin(), float64Row.end(), value );
224 : 0 : block.setData( QByteArray::fromRawData( ( char * )&float64Row[0], QgsRasterBlock::typeSize( Qgis::Float64 ) * cols ) );
225 : : break;
226 : 0 : }
227 : : default:
228 : : {
229 : 0 : std::vector<float> float32Row( cols );
230 : 0 : std::fill( float32Row.begin(), float32Row.end(), value );
231 : 0 : block.setData( QByteArray::fromRawData( ( char * )&float32Row[0], QgsRasterBlock::typeSize( Qgis::Float32 ) * cols ) );
232 : : break;
233 : 0 : }
234 : : }
235 : :
236 : 0 : double step = rows > 0 ? 100.0 / rows : 1;
237 : :
238 : 0 : for ( int i = 0; i < rows ; i++ )
239 : : {
240 : 0 : if ( feedback->isCanceled() )
241 : : {
242 : 0 : break;
243 : : }
244 : :
245 : 0 : provider->writeBlock( &block, 1, 0, i );
246 : 0 : feedback->setProgress( i * step );
247 : 0 : }
248 : :
249 : 0 : QVariantMap outputs;
250 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
251 : 0 : return outputs;
252 : 0 : }
253 : :
254 : : ///@endcond
|