QGIS API Documentation 4.3.0-Master (45633be667c)
Loading...
Searching...
No Matches
qgsalgorithmstrahlerorder.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmstrahlerorder.cpp
3 ---------------------
4 begin : September 2026
5 copyright : (C) 2026 by Nyall Dawson
6 email : nyall dot dawson 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
19
21#include "qgsrasterfilewriter.h"
22
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
28
29QStringList QgsStrahlerOrderAlgorithmBase::tags() const
30{
31 return QObject::tr( "dem,strahler,order,stream,channels,hydrology,network,catchment" ).split( ',' );
32}
33
34void QgsStrahlerOrderAlgorithmBase::addCommonParameters()
35{
36 addParameter( new QgsProcessingParameterNumber( u"THRESHOLD"_s, QObject::tr( "Minimum stream order threshold" ), Qgis::ProcessingNumberParameterType::Integer, 1, false, 1 ) );
37
38 auto outputNodataParam = std::make_unique<QgsProcessingParameterNumber>( u"NODATA"_s, QObject::tr( "Output NoData value" ), Qgis::ProcessingNumberParameterType::Integer, -9999 );
39 outputNodataParam->setHelp( QObject::tr( "The NODATA value to use in the output raster." ) );
40 outputNodataParam->setFlags( outputNodataParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
41 addParameter( outputNodataParam.release() );
42
43 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATION_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
44 creationOptsParam->setHelp( QObject::tr( "The raster creation options for the output raster. These options control things like colorimetry, compression, etc." ) );
45 creationOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
46 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
47 addParameter( creationOptsParam.release() );
48
49 auto outputParam = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT"_s, QObject::tr( "Strahler order" ) );
50 addParameter( outputParam.release() );
51}
52
53QVariantMap QgsStrahlerOrderAlgorithmBase::writeOutputRaster( QgsRasterBlock *outputBlock, const QString &outputFile, const QString &outputFormat, const QString &creationOptions )
54{
55 auto outputWriter = std::make_unique<QgsRasterFileWriter>( outputFile );
56 outputWriter->setOutputProviderKey( u"gdal"_s );
57 if ( !creationOptions.isEmpty() )
58 {
59 outputWriter->setCreationOptions( creationOptions.split( '|' ) );
60 }
61 outputWriter->setOutputFormat( outputFormat );
62
63 std::unique_ptr<QgsRasterDataProvider> destProvider( outputWriter->createOneBandRaster( Qgis::DataType::Int16, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
64 if ( !destProvider )
65 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
66 if ( !destProvider->isValid() )
67 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, destProvider->error().message( QgsErrorMessage::Text ) ) );
68
69 destProvider->setNoDataValue( 1, mOutputNoData );
70 destProvider->setEditable( true );
71
72 if ( !destProvider->writeBlock( outputBlock, 1 ) )
73 {
74 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destProvider->error().summary() ) );
75 }
76
77 destProvider->setEditable( false );
78
79 QVariantMap outputs;
80 outputs.insert( u"OUTPUT"_s, outputFile );
81 return outputs;
82}
83
84//
85// QgsStrahlerOrderFromDemAlgorithm
86//
87
88QString QgsStrahlerOrderFromDemAlgorithm::name() const
89{
90 return u"strahlerorderfromdem"_s;
91}
92
93QString QgsStrahlerOrderFromDemAlgorithm::displayName() const
94{
95 return QObject::tr( "Strahler order from DEM" );
96}
97
98QString QgsStrahlerOrderFromDemAlgorithm::shortDescription() const
99{
100 return QObject::tr( "Calculates Strahler stream order directly from an input DEM raster." );
101}
102
103QString QgsStrahlerOrderFromDemAlgorithm::shortHelpString() const
104{
105 return QObject::tr(
106 "This algorithm calculates Strahler stream order from an input elevation raster (DEM).\n\n"
107 "D8 flow directions are computed internally to traverse channel trees topographically.\n"
108 "Confluences of two stream channels of order N produce a downstream channel of order N + 1.\n"
109 "When the threshold is set to 1, raw stream orders (1, 2, 3...) are calculated. "
110 "Higher threshold values mask non-stream cells as NoData and offset stream orders.\n\n"
111 "This algorithm is a port of the Strahler stream order calculation from SAGA 'Channel Network and Drainage Basins' tool."
112 );
113}
114
115void QgsStrahlerOrderFromDemAlgorithm::initAlgorithm( const QVariantMap & )
116{
117 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT"_s, QObject::tr( "Elevation raster" ) ) );
118 addCommonParameters();
119}
120
121QgsProcessingAlgorithm *QgsStrahlerOrderFromDemAlgorithm::createInstance() const
122{
123 return new QgsStrahlerOrderFromDemAlgorithm();
124}
125
126bool QgsStrahlerOrderFromDemAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
127{
128 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u"INPUT"_s, context );
129 if ( !layer || !layer->dataProvider() )
130 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT"_s ) );
131
132 mDemInterface.reset( layer->dataProvider()->clone() );
133 mLayerWidth = layer->width();
134 mLayerHeight = layer->height();
135 mExtent = layer->extent();
136 mCrs = layer->crs();
137 mCellSizeX = layer->rasterUnitsPerPixelX();
138 mCellSizeY = layer->rasterUnitsPerPixelY();
139
140 return true;
141}
142
143QVariantMap QgsStrahlerOrderFromDemAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
144{
145 QGS_MARK_ALGORITHM_SOURCE
146
147 const int threshold = parameterAsInt( parameters, u"THRESHOLD"_s, context );
148 const QString creationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
149 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
150 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
151 mOutputNoData = parameterAsInt( parameters, u"NODATA"_s, context );
152
153 std::unique_ptr<QgsRasterBlock> demBlock( mDemInterface->block( 1, mExtent, mLayerWidth, mLayerHeight ) );
154 if ( !demBlock )
155 throw QgsProcessingException( QObject::tr( "Could not read input DEM block." ) );
156
157 const qgssize totalCells = static_cast<qgssize>( mLayerWidth ) * mLayerHeight;
158 QgsProcessingMultiStepFeedback multiStepFeedback( 2, feedback );
159
160 // pass 1: D8 flow direction calculation (matching SAGA CD8_Flow_Analysis::Get_Direction)
161 multiStepFeedback.setCurrentStep( 0 );
162 std::vector<int8_t> d8Directions( totalCells, -1 );
163 for ( int row = 0; row < mLayerHeight; ++row )
164 {
165 if ( multiStepFeedback.isCanceled() )
166 return {};
167
168 multiStepFeedback.setProgress( 100.0 * static_cast<double>( row ) / mLayerHeight );
169 const qgssize rowOffset = static_cast<qgssize>( row ) * mLayerWidth;
170 for ( int col = 0; col < mLayerWidth; ++col )
171 {
172 const int dir = QgsRasterAnalysisUtils::steepestGradientDirection( demBlock.get(), row, col, mCellSizeX, mCellSizeY, true, true );
173 d8Directions[rowOffset + col] = static_cast<int8_t>( dir );
174 }
175 }
176
177 // pass 2: Strahler stream order calculation
178 multiStepFeedback.setCurrentStep( 1 );
179 auto outputBlock = std::make_unique<QgsRasterBlock>( Qgis::DataType::Int16, mLayerWidth, mLayerHeight );
180 outputBlock->setNoDataValue( mOutputNoData );
181
182 int16_t *outOrder = reinterpret_cast<int16_t *>( outputBlock->bits() );
183 computeStrahlerOrder( demBlock.get(), d8Directions, mLayerWidth, mLayerHeight, threshold, outOrder, &multiStepFeedback, mOutputNoData );
184
185 if ( multiStepFeedback.isCanceled() )
186 return {};
187
188 return writeOutputRaster( outputBlock.get(), outputFile, outputFormat, creationOptions );
189}
190
191//
192// QgsStrahlerOrderFromFlowDirectionAlgorithm
193//
194
195QString QgsStrahlerOrderFromFlowDirectionAlgorithm::name() const
196{
197 return u"strahlerorderfromflowdirection"_s;
198}
199
200QString QgsStrahlerOrderFromFlowDirectionAlgorithm::displayName() const
201{
202 return QObject::tr( "Strahler order from DEM and flow direction" );
203}
204
205QString QgsStrahlerOrderFromFlowDirectionAlgorithm::shortDescription() const
206{
207 return QObject::tr( "Calculates Strahler stream order using an elevation raster (DEM) and a D8 flow direction raster." );
208}
209
210QString QgsStrahlerOrderFromFlowDirectionAlgorithm::shortHelpString() const
211{
212 return QObject::tr(
213 "This algorithm calculates Strahler stream order from an input elevation raster (DEM) and a pre-computed D8 flow direction raster.\n\n"
214 "Confluences of two stream channels of order N produce a downstream channel of order N + 1.\n"
215 "When the threshold is set to 1, raw stream orders (1, 2, 3...) are calculated. "
216 "Higher threshold values mask non-stream cells as NoData and offset stream orders.\n\n"
217 "This algorithm is a port of the Strahler stream order calculation from SAGA 'Channel Network and Drainage Basins' tool."
218 );
219}
220
221void QgsStrahlerOrderFromFlowDirectionAlgorithm::initAlgorithm( const QVariantMap & )
222{
223 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT"_s, QObject::tr( "Elevation raster" ) ) );
224 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT_FLOW_DIRECTION"_s, QObject::tr( "Flow direction raster" ) ) );
225 addCommonParameters();
226}
227
228QgsProcessingAlgorithm *QgsStrahlerOrderFromFlowDirectionAlgorithm::createInstance() const
229{
230 return new QgsStrahlerOrderFromFlowDirectionAlgorithm();
231}
232
233bool QgsStrahlerOrderFromFlowDirectionAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
234{
235 QgsRasterLayer *demLayer = parameterAsRasterLayer( parameters, u"INPUT"_s, context );
236 if ( !demLayer || !demLayer->dataProvider() )
237 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT"_s ) );
238
239 QgsRasterLayer *flowDirLayer = parameterAsRasterLayer( parameters, u"INPUT_FLOW_DIRECTION"_s, context );
240 if ( !flowDirLayer || !flowDirLayer->dataProvider() )
241 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT_FLOW_DIRECTION"_s ) );
242
243 mDemInterface.reset( demLayer->dataProvider()->clone() );
244 mFlowDirInterface.reset( flowDirLayer->dataProvider()->clone() );
245 mLayerWidth = demLayer->width();
246 mLayerHeight = demLayer->height();
247 mExtent = demLayer->extent();
248 mCrs = demLayer->crs();
249
250 return true;
251}
252
253QVariantMap QgsStrahlerOrderFromFlowDirectionAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
254{
255 QGS_MARK_ALGORITHM_SOURCE
256
257 const int threshold = parameterAsInt( parameters, u"THRESHOLD"_s, context );
258 const QString creationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
259 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
260 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
261 mOutputNoData = parameterAsInt( parameters, u"NODATA"_s, context );
262
263 std::unique_ptr<QgsRasterBlock> demBlock( mDemInterface->block( 1, mExtent, mLayerWidth, mLayerHeight ) );
264 if ( !demBlock )
265 throw QgsProcessingException( QObject::tr( "Could not read input DEM block." ) );
266
267 std::unique_ptr<QgsRasterBlock> flowDirBlock( mFlowDirInterface->block( 1, mExtent, mLayerWidth, mLayerHeight ) );
268 if ( !flowDirBlock )
269 throw QgsProcessingException( QObject::tr( "Could not read input flow direction block." ) );
270
271 const qgssize totalCells = static_cast<qgssize>( mLayerWidth ) * mLayerHeight;
272
273 std::vector<int8_t> d8Directions( totalCells, -1 );
274 for ( int row = 0; row < mLayerHeight; ++row )
275 {
276 if ( feedback->isCanceled() )
277 return {};
278
279 bool isNoData = false;
280 const qgssize rowOffset = static_cast<qgssize>( row ) * mLayerWidth;
281 for ( int col = 0; col < mLayerWidth; ++col )
282 {
283 const double dir = flowDirBlock->valueAndNoData( row, col, isNoData );
284 if ( !isNoData )
285 {
286 d8Directions[rowOffset + col] = static_cast<int8_t>( dir );
287 }
288 }
289 }
290
291 auto outputBlock = std::make_unique<QgsRasterBlock>( Qgis::DataType::Int16, mLayerWidth, mLayerHeight );
292 outputBlock->setNoDataValue( mOutputNoData );
293
294 int16_t *outOrder = reinterpret_cast<int16_t *>( outputBlock->bits() );
295 computeStrahlerOrder( demBlock.get(), d8Directions, mLayerWidth, mLayerHeight, threshold, outOrder, feedback, mOutputNoData );
296
297 if ( feedback->isCanceled() )
298 return {};
299
300 return writeOutputRaster( outputBlock.get(), outputFile, outputFormat, creationOptions );
301}
302
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:398
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:4006
@ Text
Plain text format.
Definition qgserror.h:40
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
virtual Q_INVOKABLE QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Abstract base class for processing algorithms.
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
Processing feedback object for multi-step operations.
A numeric parameter for processing algorithms.
A raster layer parameter for processing algorithms.
Raster data container.
char * bits(int row, int column)
Returns a pointer to block data.
void setNoDataValue(double noDataValue)
Sets cell value that will be considered as "no data".
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
double rasterUnitsPerPixelX() const
Returns the number of raster units per each raster pixel in X axis.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
int width() const
Returns the width of the (unclipped) raster.
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition qgis.h:8310