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