QGIS API Documentation 3.99.0-Master (752b475928d)
Loading...
Searching...
No Matches
qgsalgorithmfillsinkswangliu.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmfillsinkswangliu.cpp
3 ---------------------
4 begin : April 2025
5 copyright : (C) 2025 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
20#include "qgsrasterfilewriter.h"
21
22#include <queue>
23
25
26QString QgsFillSinksWangLiuAlgorithm::name() const
27{
28 return QStringLiteral( "fillsinkswangliu" );
29}
30
31QString QgsFillSinksWangLiuAlgorithm::displayName() const
32{
33 return QObject::tr( "Fill sinks (Wang & Liu)" );
34}
35
36QStringList QgsFillSinksWangLiuAlgorithm::tags() const
37{
38 return QObject::tr( "fill,filter,slope,dsm,dtm,terrain,water,shed,basin,direction,flow" ).split( ',' );
39}
40
41QString QgsFillSinksWangLiuAlgorithm::group() const
42{
43 return QObject::tr( "Raster terrain analysis" );
44}
45
46QString QgsFillSinksWangLiuAlgorithm::groupId() const
47{
48 return QStringLiteral( "rasterterrainanalysis" );
49}
50
51QString QgsFillSinksWangLiuAlgorithm::shortHelpString() const
52{
53 return QObject::tr( "This algorithm uses a method proposed by Wang & Liu to identify and fill surface depressions in digital elevation models.\n\n"
54
55 "The method was enhanced to allow the creation of hydrologically sound elevation models, i.e. not only to fill the depression(s) "
56 "but also to preserve a downward slope along the flow path. If desired, this is accomplished by preserving a minimum slope "
57 "gradient (and thus elevation difference) between cells.\n\n"
58
59 "References: Wang, L. & H. Liu (2006): An efficient method for identifying and filling surface depressions in digital elevation models for hydrologic analysis and modelling. International Journal of Geographical Information Science, Vol. 20, No. 2: 193-213.\n\n"
60
61 "This algorithm is a port of the SAGA 'Fill Sinks (Wang & Liu)' tool." );
62}
63
64QString QgsFillSinksWangLiuAlgorithm::shortDescription() const
65{
66 return QObject::tr( "Identifies and fills surface depressions in digital elevation models using a method proposed by Wang & Liu." );
67}
68
69void QgsFillSinksWangLiuAlgorithm::initAlgorithm( const QVariantMap & )
70{
71 addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
72
73 addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ), QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
74
75 auto minSlopeParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "MIN_SLOPE" ), QObject::tr( "Minimum slope (degrees)" ), Qgis::ProcessingNumberParameterType::Double, 0.1, false, 0 );
76 minSlopeParam->setHelp( QObject::tr( "Minimum slope gradient to preserve from cell to cell. With a value of zero sinks are filled up to the spill elevation (which results in flat areas). Units are degrees." ) );
77 addParameter( minSlopeParam.release() );
78
79 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral( "CREATION_OPTIONS" ), QObject::tr( "Creation options" ), QVariant(), false, true );
80 createOptsParam->setMetadata( QVariantMap( { { QStringLiteral( "widget_wrapper" ), QVariantMap( { { QStringLiteral( "widget_type" ), QStringLiteral( "rasteroptions" ) } } ) } } ) );
81 createOptsParam->setFlags( createOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
82 addParameter( createOptsParam.release() );
83
84 auto outputFilledDem = std::make_unique<QgsProcessingParameterRasterDestination>( QStringLiteral( "OUTPUT_FILLED_DEM" ), QObject::tr( "Output layer (filled DEM)" ), QVariant(), true, true );
85 outputFilledDem->setHelp( QObject::tr( "Depression-free digital elevation model." ) );
86 addParameter( outputFilledDem.release() );
87
88 auto outputFlowDirections = std::make_unique<QgsProcessingParameterRasterDestination>( QStringLiteral( "OUTPUT_FLOW_DIRECTIONS" ), QObject::tr( "Output layer (flow directions)" ), QVariant(), true, false );
89 outputFlowDirections->setHelp( QObject::tr( "Computed flow directions, 0=N, 1=NE, 2=E, ... 7=NW." ) );
90 addParameter( outputFlowDirections.release() );
91
92 auto outputWatershedBasins = std::make_unique<QgsProcessingParameterRasterDestination>( QStringLiteral( "OUTPUT_WATERSHED_BASINS" ), QObject::tr( "Output layer (watershed basins)" ), QVariant(), true, false );
93 outputWatershedBasins->setHelp( QObject::tr( "Delineated watershed basin." ) );
94 addParameter( outputWatershedBasins.release() );
95}
96
97QgsFillSinksWangLiuAlgorithm *QgsFillSinksWangLiuAlgorithm::createInstance() const
98{
99 return new QgsFillSinksWangLiuAlgorithm();
100}
101
102bool QgsFillSinksWangLiuAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
103{
104 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT" ), context );
105 if ( !layer )
106 throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT" ) ) );
107
108 const int band = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
109
110 mBand = parameterAsInt( parameters, QStringLiteral( "BAND" ), context );
111 if ( mBand < 1 || mBand > layer->bandCount() )
112 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->bandCount() ) );
113
114 mInterface.reset( layer->dataProvider()->clone() );
115 mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
116 mLayerWidth = layer->width();
117 mLayerHeight = layer->height();
118 mExtent = layer->extent();
119 mCrs = layer->crs();
120 mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
121 mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();
122 mRasterDiagonal = std::sqrt( mRasterUnitsPerPixelX * mRasterUnitsPerPixelX + mRasterUnitsPerPixelY * mRasterUnitsPerPixelY );
123 mDataType = layer->dataProvider()->dataType( mBand );
124 mNoData = layer->dataProvider()->sourceNoDataValue( mBand );
125 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
126 return true;
127}
128
129static constexpr std::array< int, 8 > COL_DIRECTION_OFFSETS { 0, 1, 1, 1, 0, -1, -1, -1 };
130static constexpr std::array< int, 8 > ROW_DIRECTION_OFFSETS { -1, -1, 0, 1, 1, 1, 0, -1 };
131
132bool QgsFillSinksWangLiuAlgorithm::isInGrid( int row, int col ) const
133{
134 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
135}
136
137QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir( int row, int col, double z, const QgsRasterBlock *filled ) const
138{
139 Direction steepestDirection = Invalid;
140 double maxGradient = 0;
141 bool isNoData = false;
142
143 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
144 {
145 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
146 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
147
148 if ( isInGrid( neighborRow, neighborCol ) )
149 {
150 const double neighborZ = filled->valueAndNoData( neighborRow, neighborCol, isNoData );
151 if ( !isNoData && neighborZ < z )
152 {
153 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
154 if ( gradient >= maxGradient )
155 {
156 maxGradient = gradient;
157 steepestDirection = direction;
158 }
159 }
160 }
161 }
162
163 return steepestDirection;
164}
165
166struct CFillSinks_WL_Node
167{
168 int row = 0;
169 int col = 0;
170 double spill = 0;
171};
172
173class CompareGreater
174{
175 public:
176 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 ) const
177 {
178 return n1.spill > n2.spill;
179 }
180};
181
182typedef std::vector< CFillSinks_WL_Node > nodeVector;
183typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
184
185QVariantMap QgsFillSinksWangLiuAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
186{
187 const QString createOptions = parameterAsString( parameters, QStringLiteral( "CREATION_OPTIONS" ), context ).trimmed();
188
189 const QString filledDemOutputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT_FILLED_DEM" ), context );
190 const QString flowDirectionsOutputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT_FLOW_DIRECTIONS" ), context );
191 const QString watershedBasinsOutputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT_WATERSHED_BASINS" ), context );
192
193 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
194 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
195
196 if ( !filledDemOutputFile.isEmpty() )
197 {
198 const QString outputFormat = parameterAsOutputRasterFormat( parameters, QStringLiteral( "OUTPUT_FILLED_DEM" ), context );
199
200 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
201 filledDemWriter->setOutputProviderKey( QStringLiteral( "gdal" ) );
202 if ( !createOptions.isEmpty() )
203 {
204 filledDemWriter->setCreationOptions( createOptions.split( '|' ) );
205 }
206 filledDemWriter->setOutputFormat( outputFormat );
207
208 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
209
210 if ( !filledDemDestProvider )
211 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( filledDemOutputFile ) );
212 if ( !filledDemDestProvider->isValid() )
213 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( filledDemOutputFile, filledDemDestProvider->error().message( QgsErrorMessage::Text ) ) );
214
215 filledDemDestProvider->setNoDataValue( 1, mNoData );
216 filledDemDestProvider->setEditable( true );
217 }
218
219 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
220 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
221
222 if ( !flowDirectionsOutputFile.isEmpty() )
223 {
224 const QString outputFormat = parameterAsOutputRasterFormat( parameters, QStringLiteral( "OUTPUT_FLOW_DIRECTIONS" ), context );
225
226 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
227 flowDirectionsWriter->setOutputProviderKey( QStringLiteral( "gdal" ) );
228 flowDirectionsWriter->setOutputFormat( outputFormat );
229
230 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster( Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
231
232 if ( !flowDirectionsDestProvider )
233 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
234 if ( !flowDirectionsDestProvider->isValid() )
235 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( flowDirectionsOutputFile, flowDirectionsDestProvider->error().message( QgsErrorMessage::Text ) ) );
236
237 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
238 flowDirectionsDestProvider->setEditable( true );
239 }
240
241 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
242 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
243
244 if ( !watershedBasinsOutputFile.isEmpty() )
245 {
246 const QString outputFormat = parameterAsOutputRasterFormat( parameters, QStringLiteral( "OUTPUT_WATERSHED_BASINS" ), context );
247
248 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
249 watershedBasinsWriter->setOutputProviderKey( QStringLiteral( "gdal" ) );
250 watershedBasinsWriter->setOutputFormat( outputFormat );
251
252 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster( Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
253
254 if ( !watershedBasinsDestProvider )
255 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
256 if ( !watershedBasinsDestProvider->isValid() )
257 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( watershedBasinsOutputFile, watershedBasinsDestProvider->error().message( QgsErrorMessage::Text ) ) );
258
259 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
260 watershedBasinsDestProvider->setEditable( true );
261 }
262
263 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
264 if ( !sourceDemData )
265 {
266 throw QgsProcessingException( QObject::tr( "Could not read DEM raster" ) );
267 }
268
269 auto filledDemData = std::make_unique<QgsRasterBlock>( mDataType, mLayerWidth, mLayerHeight );
270 filledDemData->setNoDataValue( mNoData );
271 filledDemData->setIsNoData();
272
273 auto watershedData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
274 watershedData->setNoDataValue( -1 );
275 watershedData->setIsNoData();
276
277 auto outputFlowData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
278 outputFlowData->setNoDataValue( 255 );
279 outputFlowData->setIsNoData();
280
281 auto seedData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
282 seedData->fill( 0 );
283
284 double minSlope = parameterAsDouble( parameters, QStringLiteral( "MIN_SLOPE" ), context );
285 double mindiff[8];
286 bool preserve = false;
287 if ( minSlope > 0.0 )
288 {
289 minSlope = tan( minSlope * M_PI / 180.0 );
290 for ( int i = 0; i < 8; i++ )
291 mindiff[i] = minSlope * mDirectionalLengths[i];
292 preserve = true;
293 }
294
295 // fill priority queue with boundary, i.e. seed cells
296 CFillSinks_WL_Node tempNode;
297 PriorityQ theQueue;
298
299 long long id = 0;
300 double value = 0;
301 bool isNoData = false;
302 feedback->setProgressText( QObject::tr( "Seed boundary cells" ) );
303
304 std::size_t processed = 0;
305 const std::size_t totalCells = static_cast< std::size_t >( mLayerWidth ) * mLayerHeight;
306
307 for ( int row = 0; row < mLayerHeight; row++ )
308 {
309 if ( feedback->isCanceled() )
310 break;
311
312 for ( int col = 0; col < mLayerWidth; col++ )
313 {
314 value = sourceDemData->valueAndNoData( row, col, isNoData );
315 if ( !isNoData )
316 {
317 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
318 {
319 int iCol = col + COL_DIRECTION_OFFSETS[direction];
320 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
321 ;
322 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
323 {
324 const double z = value;
325 filledDemData->setValue( row, col, z );
326 seedData->setValue( row, col, 1.0 );
327 watershedData->setValue( row, col, static_cast< double >( id ) );
328 id += 1;
329
330 tempNode.row = row;
331 tempNode.col = col;
332 tempNode.spill = z;
333 theQueue.push( tempNode );
334 processed += 1;
335 break;
336 }
337 }
338 }
339 feedback->setProgress( static_cast< double >( processed ) / static_cast< double >( totalCells ) * 100 );
340 }
341 }
342
343 if ( feedback->isCanceled() )
344 return {};
345
346 // work through least cost path
347 feedback->setProgressText( QObject::tr( "Filling using least cost paths" ) );
348
349 while ( !theQueue.empty() )
350 {
351 PriorityQ::value_type tempNode = theQueue.top();
352
353 const int row = tempNode.row;
354 const int col = tempNode.col;
355 const double z = tempNode.spill;
356 theQueue.pop();
357
358 const long long id = static_cast< long long >( watershedData->value( row, col ) );
359
360 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
361 {
362 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
363 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
364 isNoData = false;
365 const bool iInGrid = isInGrid( iRow, iCol );
366 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
367 if ( iInGrid && !isNoData )
368 {
369 if ( filledDemData->isNoData( iRow, iCol ) )
370 {
371 if ( preserve )
372 {
373 iz = std::max( iz, z + mindiff[static_cast< int >( direction )] );
374 }
375 else if ( iz <= z )
376 {
377 iz = z;
378 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[static_cast< int >( direction )] );
379 }
380
381 tempNode.row = iRow;
382 tempNode.col = iCol;
383 tempNode.spill = iz;
384 theQueue.push( tempNode );
385
386 filledDemData->setValue( iRow, iCol, iz );
387 watershedData->setValue( iRow, iCol, id );
388 processed += 1;
389 }
390 else if ( seedData->value( iRow, iCol ) == 1 )
391 {
392 watershedData->setValue( iRow, iCol, id );
393 }
394 }
395 }
396
397 if ( outputFlowData->isNoData( row, col ) )
398 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
399
400 feedback->setProgress( static_cast< double >( processed ) / static_cast< double >( totalCells ) * 100 );
401 if ( feedback->isCanceled() )
402 break;
403 }
404
405 if ( feedback->isCanceled() )
406 return {};
407
408 QVariantMap outputs;
409
410 if ( filledDemDestProvider )
411 {
412 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
413 {
414 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
415 }
416 filledDemDestProvider->setEditable( false );
417 outputs.insert( QStringLiteral( "OUTPUT_FILLED_DEM" ), filledDemOutputFile );
418 }
419 if ( flowDirectionsDestProvider )
420 {
421 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
422 {
423 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
424 }
425 flowDirectionsDestProvider->setEditable( false );
426 outputs.insert( QStringLiteral( "OUTPUT_FLOW_DIRECTIONS" ), flowDirectionsOutputFile );
427 }
428 if ( watershedBasinsDestProvider )
429 {
430 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
431 {
432 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
433 }
434 watershedBasinsDestProvider->setEditable( false );
435 outputs.insert( QStringLiteral( "OUTPUT_WATERSHED_BASINS" ), watershedBasinsOutputFile );
436 }
437
438 return outputs;
439}
440
441
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:374
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:379
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3763
@ Double
Double/float values.
Definition qgis.h:3804
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:87
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.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
A raster band parameter for Processing algorithms.
A raster layer parameter for processing algorithms.
Raster data container.
double valueAndNoData(int row, int column, bool &isNoData) const
Reads a single value from the pixel at row and column, if type of block is numeric.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
int bandCount() const
Returns the number of bands in this layer.
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.