QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 QFileInfo fi( filledDemOutputFile );
199 const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
200
201 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
202 filledDemWriter->setOutputProviderKey( QStringLiteral( "gdal" ) );
203 if ( !createOptions.isEmpty() )
204 {
205 filledDemWriter->setCreationOptions( createOptions.split( '|' ) );
206 }
207 filledDemWriter->setOutputFormat( outputFormat );
208
209 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
210
211 if ( !filledDemDestProvider )
212 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( filledDemOutputFile ) );
213 if ( !filledDemDestProvider->isValid() )
214 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( filledDemOutputFile, filledDemDestProvider->error().message( QgsErrorMessage::Text ) ) );
215
216 filledDemDestProvider->setNoDataValue( 1, mNoData );
217 filledDemDestProvider->setEditable( true );
218 }
219
220 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
221 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
222
223 if ( !flowDirectionsOutputFile.isEmpty() )
224 {
225 const QFileInfo fi( flowDirectionsOutputFile );
226 const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
227
228 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
229 flowDirectionsWriter->setOutputProviderKey( QStringLiteral( "gdal" ) );
230 flowDirectionsWriter->setOutputFormat( outputFormat );
231
232 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster( Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
233
234 if ( !flowDirectionsDestProvider )
235 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
236 if ( !flowDirectionsDestProvider->isValid() )
237 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( flowDirectionsOutputFile, flowDirectionsDestProvider->error().message( QgsErrorMessage::Text ) ) );
238
239 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
240 flowDirectionsDestProvider->setEditable( true );
241 }
242
243 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
244 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
245
246 if ( !watershedBasinsOutputFile.isEmpty() )
247 {
248 const QFileInfo fi( watershedBasinsOutputFile );
249 const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
250
251 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
252 watershedBasinsWriter->setOutputProviderKey( QStringLiteral( "gdal" ) );
253 watershedBasinsWriter->setOutputFormat( outputFormat );
254
255 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster( Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
256
257 if ( !watershedBasinsDestProvider )
258 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
259 if ( !watershedBasinsDestProvider->isValid() )
260 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( watershedBasinsOutputFile, watershedBasinsDestProvider->error().message( QgsErrorMessage::Text ) ) );
261
262 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
263 watershedBasinsDestProvider->setEditable( true );
264 }
265
266 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
267 if ( !sourceDemData )
268 {
269 throw QgsProcessingException( QObject::tr( "Could not read DEM raster" ) );
270 }
271
272 auto filledDemData = std::make_unique<QgsRasterBlock>( mDataType, mLayerWidth, mLayerHeight );
273 filledDemData->setNoDataValue( mNoData );
274 filledDemData->setIsNoData();
275
276 auto watershedData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
277 watershedData->setNoDataValue( -1 );
278 watershedData->setIsNoData();
279
280 auto outputFlowData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
281 outputFlowData->setNoDataValue( 255 );
282 outputFlowData->setIsNoData();
283
284 auto seedData = std::make_unique<QgsRasterBlock>( Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
285 seedData->fill( 0 );
286
287 double minSlope = parameterAsDouble( parameters, QStringLiteral( "MIN_SLOPE" ), context );
288 double mindiff[8];
289 bool preserve = false;
290 if ( minSlope > 0.0 )
291 {
292 minSlope = tan( minSlope * M_PI / 180.0 );
293 for ( int i = 0; i < 8; i++ )
294 mindiff[i] = minSlope * mDirectionalLengths[i];
295 preserve = true;
296 }
297
298 // fill priority queue with boundary, i.e. seed cells
299 CFillSinks_WL_Node tempNode;
300 PriorityQ theQueue;
301
302 long long id = 0;
303 double value = 0;
304 bool isNoData = false;
305 feedback->setProgressText( QObject::tr( "Seed boundary cells" ) );
306
307 std::size_t processed = 0;
308 const std::size_t totalCells = static_cast< std::size_t >( mLayerWidth ) * mLayerHeight;
309
310 for ( int row = 0; row < mLayerHeight; row++ )
311 {
312 if ( feedback->isCanceled() )
313 break;
314
315 for ( int col = 0; col < mLayerWidth; col++ )
316 {
317 value = sourceDemData->valueAndNoData( row, col, isNoData );
318 if ( !isNoData )
319 {
320 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
321 {
322 int iCol = col + COL_DIRECTION_OFFSETS[direction];
323 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
324 ;
325 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
326 {
327 const double z = value;
328 filledDemData->setValue( row, col, z );
329 seedData->setValue( row, col, 1.0 );
330 watershedData->setValue( row, col, static_cast< double >( id ) );
331 id += 1;
332
333 tempNode.row = row;
334 tempNode.col = col;
335 tempNode.spill = z;
336 theQueue.push( tempNode );
337 processed += 1;
338 break;
339 }
340 }
341 }
342 feedback->setProgress( static_cast< double >( processed ) / static_cast< double >( totalCells ) * 100 );
343 }
344 }
345
346 if ( feedback->isCanceled() )
347 return {};
348
349 // work through least cost path
350 feedback->setProgressText( QObject::tr( "Filling using least cost paths" ) );
351
352 while ( !theQueue.empty() )
353 {
354 PriorityQ::value_type tempNode = theQueue.top();
355
356 const int row = tempNode.row;
357 const int col = tempNode.col;
358 const double z = tempNode.spill;
359 theQueue.pop();
360
361 const long long id = static_cast< long long >( watershedData->value( row, col ) );
362
363 for ( Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
364 {
365 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
366 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
367 isNoData = false;
368 const bool iInGrid = isInGrid( iRow, iCol );
369 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
370 if ( iInGrid && !isNoData )
371 {
372 if ( filledDemData->isNoData( iRow, iCol ) )
373 {
374 if ( preserve )
375 {
376 iz = std::max( iz, z + mindiff[static_cast< int >( direction )] );
377 }
378 else if ( iz <= z )
379 {
380 iz = z;
381 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[static_cast< int >( direction )] );
382 }
383
384 tempNode.row = iRow;
385 tempNode.col = iCol;
386 tempNode.spill = iz;
387 theQueue.push( tempNode );
388
389 filledDemData->setValue( iRow, iCol, iz );
390 watershedData->setValue( iRow, iCol, id );
391 processed += 1;
392 }
393 else if ( seedData->value( iRow, iCol ) == 1 )
394 {
395 watershedData->setValue( iRow, iCol, id );
396 }
397 }
398 }
399
400 if ( outputFlowData->isNoData( row, col ) )
401 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
402
403 feedback->setProgress( static_cast< double >( processed ) / static_cast< double >( totalCells ) * 100 );
404 if ( feedback->isCanceled() )
405 break;
406 }
407
408 if ( feedback->isCanceled() )
409 return {};
410
411 QVariantMap outputs;
412
413 if ( filledDemDestProvider )
414 {
415 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
416 {
417 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
418 }
419 filledDemDestProvider->setEditable( false );
420 outputs.insert( QStringLiteral( "OUTPUT_FILLED_DEM" ), filledDemOutputFile );
421 }
422 if ( flowDirectionsDestProvider )
423 {
424 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
425 {
426 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
427 }
428 flowDirectionsDestProvider->setEditable( false );
429 outputs.insert( QStringLiteral( "OUTPUT_FLOW_DIRECTIONS" ), flowDirectionsOutputFile );
430 }
431 if ( watershedBasinsDestProvider )
432 {
433 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
434 {
435 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
436 }
437 watershedBasinsDestProvider->setEditable( false );
438 outputs.insert( QStringLiteral( "OUTPUT_WATERSHED_BASINS" ), watershedBasinsOutputFile );
439 }
440
441 return outputs;
442}
443
444
@ 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.
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
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.