25using namespace Qt::StringLiterals;
29QString QgsFillSinksWangLiuAlgorithm::name()
const
31 return u
"fillsinkswangliu"_s;
34QString QgsFillSinksWangLiuAlgorithm::displayName()
const
36 return QObject::tr(
"Fill sinks (Wang & Liu)" );
39QStringList QgsFillSinksWangLiuAlgorithm::tags()
const
41 return QObject::tr(
"fill,filter,slope,dsm,dtm,terrain,water,shed,basin,direction,flow" ).split(
',' );
44QString QgsFillSinksWangLiuAlgorithm::group()
const
46 return QObject::tr(
"Raster terrain analysis" );
49QString QgsFillSinksWangLiuAlgorithm::groupId()
const
51 return u
"rasterterrainanalysis"_s;
54QString QgsFillSinksWangLiuAlgorithm::shortHelpString()
const
56 return QObject::tr(
"This algorithm uses a method proposed by Wang & Liu to identify and fill surface depressions in digital elevation models.\n\n"
58 "The method was enhanced to allow the creation of hydrologically sound elevation models, i.e. not only to fill the depression(s) "
59 "but also to preserve a downward slope along the flow path. If desired, this is accomplished by preserving a minimum slope "
60 "gradient (and thus elevation difference) between cells.\n\n"
62 "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"
64 "This algorithm is a port of the SAGA 'Fill Sinks (Wang & Liu)' tool." );
67QString QgsFillSinksWangLiuAlgorithm::shortDescription()
const
69 return QObject::tr(
"Identifies and fills surface depressions in digital elevation models using a method proposed by Wang & Liu." );
72void QgsFillSinksWangLiuAlgorithm::initAlgorithm(
const QVariantMap & )
79 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." ) );
80 addParameter( minSlopeParam.release() );
82 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( u
"CREATION_OPTIONS"_s, QObject::tr(
"Creation options" ), QVariant(),
false,
true );
83 createOptsParam->setMetadata( QVariantMap( { { u
"widget_wrapper"_s, QVariantMap( { { u
"widget_type"_s, u
"rasteroptions"_s } } ) } } ) );
85 addParameter( createOptsParam.release() );
87 auto outputFilledDem = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_FILLED_DEM"_s, QObject::tr(
"Output layer (filled DEM)" ), QVariant(),
true,
true );
88 outputFilledDem->setHelp( QObject::tr(
"Depression-free digital elevation model." ) );
89 addParameter( outputFilledDem.release() );
91 auto outputFlowDirections = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_FLOW_DIRECTIONS"_s, QObject::tr(
"Output layer (flow directions)" ), QVariant(),
true,
false );
92 outputFlowDirections->setHelp( QObject::tr(
"Computed flow directions, 0=N, 1=NE, 2=E, ... 7=NW." ) );
93 addParameter( outputFlowDirections.release() );
95 auto outputWatershedBasins = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_WATERSHED_BASINS"_s, QObject::tr(
"Output layer (watershed basins)" ), QVariant(),
true,
false );
96 outputWatershedBasins->setHelp( QObject::tr(
"Delineated watershed basin." ) );
97 addParameter( outputWatershedBasins.release() );
100QgsFillSinksWangLiuAlgorithm *QgsFillSinksWangLiuAlgorithm::createInstance()
const
102 return new QgsFillSinksWangLiuAlgorithm();
107 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u
"INPUT"_s, context );
111 const int band = parameterAsInt( parameters, u
"BAND"_s, context );
113 mBand = parameterAsInt( parameters, u
"BAND"_s, context );
114 if ( mBand < 1 || mBand > layer->
bandCount() )
115 throw QgsProcessingException( QObject::tr(
"Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->
bandCount() ) );
119 mLayerWidth = layer->
width();
120 mLayerHeight = layer->
height();
121 mExtent = layer->
extent();
125 mRasterDiagonal = std::sqrt( mRasterUnitsPerPixelX * mRasterUnitsPerPixelX + mRasterUnitsPerPixelY * mRasterUnitsPerPixelY );
128 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
132static constexpr std::array< int, 8 > COL_DIRECTION_OFFSETS { 0, 1, 1, 1, 0, -1, -1, -1 };
133static constexpr std::array< int, 8 > ROW_DIRECTION_OFFSETS { -1, -1, 0, 1, 1, 1, 0, -1 };
135bool QgsFillSinksWangLiuAlgorithm::isInGrid(
int row,
int col )
const
137 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
140QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir(
int row,
int col,
double z,
const QgsRasterBlock *filled )
const
143 double maxGradient = 0;
144 bool isNoData =
false;
146 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
148 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
149 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
151 if ( isInGrid( neighborRow, neighborCol ) )
153 const double neighborZ = filled->
valueAndNoData( neighborRow, neighborCol, isNoData );
154 if ( !isNoData && neighborZ < z )
156 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
157 if ( gradient >= maxGradient )
159 maxGradient = gradient;
160 steepestDirection = direction;
166 return steepestDirection;
169struct CFillSinks_WL_Node
179 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 )
const
181 return n1.spill > n2.spill;
185typedef std::vector< CFillSinks_WL_Node > nodeVector;
186typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
190 const QString createOptions = parameterAsString( parameters, u
"CREATION_OPTIONS"_s, context ).trimmed();
192 const QString filledDemOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_FILLED_DEM"_s, context );
193 const QString flowDirectionsOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_FLOW_DIRECTIONS"_s, context );
194 const QString watershedBasinsOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_WATERSHED_BASINS"_s, context );
196 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
197 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
199 if ( !filledDemOutputFile.isEmpty() )
201 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_FILLED_DEM"_s, context );
203 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
204 filledDemWriter->setOutputProviderKey( u
"gdal"_s );
205 if ( !createOptions.isEmpty() )
207 filledDemWriter->setCreationOptions( createOptions.split(
'|' ) );
209 filledDemWriter->setOutputFormat( outputFormat );
211 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
213 if ( !filledDemDestProvider )
214 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( filledDemOutputFile ) );
215 if ( !filledDemDestProvider->isValid() )
218 filledDemDestProvider->setNoDataValue( 1, mNoData );
219 filledDemDestProvider->setEditable(
true );
222 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
223 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
225 if ( !flowDirectionsOutputFile.isEmpty() )
227 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_FLOW_DIRECTIONS"_s, context );
229 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
230 flowDirectionsWriter->setOutputProviderKey( u
"gdal"_s );
231 flowDirectionsWriter->setOutputFormat( outputFormat );
233 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
235 if ( !flowDirectionsDestProvider )
236 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
237 if ( !flowDirectionsDestProvider->isValid() )
240 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
241 flowDirectionsDestProvider->setEditable(
true );
244 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
245 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
247 if ( !watershedBasinsOutputFile.isEmpty() )
249 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_WATERSHED_BASINS"_s, context );
251 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
252 watershedBasinsWriter->setOutputProviderKey( u
"gdal"_s );
253 watershedBasinsWriter->setOutputFormat( outputFormat );
255 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
257 if ( !watershedBasinsDestProvider )
258 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
259 if ( !watershedBasinsDestProvider->isValid() )
262 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
263 watershedBasinsDestProvider->setEditable(
true );
266 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
267 if ( !sourceDemData )
272 auto filledDemData = std::make_unique<QgsRasterBlock>( mDataType, mLayerWidth, mLayerHeight );
273 filledDemData->setNoDataValue( mNoData );
274 filledDemData->setIsNoData();
276 auto watershedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
277 watershedData->setNoDataValue( -1 );
278 watershedData->setIsNoData();
280 auto outputFlowData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
281 outputFlowData->setNoDataValue( 255 );
282 outputFlowData->setIsNoData();
284 auto seedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
287 double minSlope = parameterAsDouble( parameters, u
"MIN_SLOPE"_s, context );
289 bool preserve =
false;
290 if ( minSlope > 0.0 )
292 minSlope = tan( minSlope * M_PI / 180.0 );
293 for (
int i = 0; i < 8; i++ )
294 mindiff[i] = minSlope * mDirectionalLengths[i];
299 CFillSinks_WL_Node tempNode;
304 bool isNoData =
false;
307 std::size_t processed = 0;
308 const std::size_t totalCells =
static_cast< std::size_t
>( mLayerWidth ) * mLayerHeight;
310 for (
int row = 0; row < mLayerHeight; row++ )
315 for (
int col = 0; col < mLayerWidth; col++ )
317 value = sourceDemData->valueAndNoData( row, col, isNoData );
320 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
322 int iCol = col + COL_DIRECTION_OFFSETS[direction];
323 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
325 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
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 ) );
336 theQueue.push( tempNode );
342 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
350 feedback->
setProgressText( QObject::tr(
"Filling using least cost paths" ) );
352 while ( !theQueue.empty() )
354 PriorityQ::value_type tempNode = theQueue.top();
356 const int row = tempNode.row;
357 const int col = tempNode.col;
358 const double z = tempNode.spill;
361 const long long id =
static_cast< long long >( watershedData->value( row, col ) );
363 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
365 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
366 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
368 const bool iInGrid = isInGrid( iRow, iCol );
369 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
370 if ( iInGrid && !isNoData )
372 if ( filledDemData->isNoData( iRow, iCol ) )
376 iz = std::max( iz, z + mindiff[
static_cast< int >( direction )] );
381 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[
static_cast< int >( direction )] );
387 theQueue.push( tempNode );
389 filledDemData->setValue( iRow, iCol, iz );
390 watershedData->setValue( iRow, iCol,
id );
393 else if ( seedData->value( iRow, iCol ) == 1 )
395 watershedData->setValue( iRow, iCol,
id );
400 if ( outputFlowData->isNoData( row, col ) )
401 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
403 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
413 if ( filledDemDestProvider )
415 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
417 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
419 filledDemDestProvider->setEditable(
false );
420 outputs.insert( u
"OUTPUT_FILLED_DEM"_s, filledDemOutputFile );
422 if ( flowDirectionsDestProvider )
424 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
426 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
428 flowDirectionsDestProvider->setEditable(
false );
429 outputs.insert( u
"OUTPUT_FLOW_DIRECTIONS"_s, flowDirectionsOutputFile );
431 if ( watershedBasinsDestProvider )
433 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
435 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
437 watershedBasinsDestProvider->setEditable(
false );
438 outputs.insert( u
"OUTPUT_WATERSHED_BASINS"_s, watershedBasinsOutputFile );
@ Byte
Eight bit unsigned integer (quint8).
@ Int32
Thirty two bit signed integer (qint32).
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
@ Double
Double/float values.
bool isCanceled() const
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
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.
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.