26QString QgsFillSinksWangLiuAlgorithm::name()
const
28 return QStringLiteral(
"fillsinkswangliu" );
31QString QgsFillSinksWangLiuAlgorithm::displayName()
const
33 return QObject::tr(
"Fill sinks (Wang & Liu)" );
36QStringList QgsFillSinksWangLiuAlgorithm::tags()
const
38 return QObject::tr(
"fill,filter,slope,dsm,dtm,terrain,water,shed,basin,direction,flow" ).split(
',' );
41QString QgsFillSinksWangLiuAlgorithm::group()
const
43 return QObject::tr(
"Raster terrain analysis" );
46QString QgsFillSinksWangLiuAlgorithm::groupId()
const
48 return QStringLiteral(
"rasterterrainanalysis" );
51QString QgsFillSinksWangLiuAlgorithm::shortHelpString()
const
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"
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"
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"
61 "This algorithm is a port of the SAGA 'Fill Sinks (Wang & Liu)' tool." );
64QString QgsFillSinksWangLiuAlgorithm::shortDescription()
const
66 return QObject::tr(
"Identifies and fills surface depressions in digital elevation models using a method proposed by Wang & Liu." );
69void QgsFillSinksWangLiuAlgorithm::initAlgorithm(
const QVariantMap & )
73 addParameter(
new QgsProcessingParameterBand( QStringLiteral(
"BAND" ), QObject::tr(
"Band number" ), 1, QStringLiteral(
"INPUT" ) ) );
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() );
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" ) } } ) } } ) );
82 addParameter( createOptsParam.release() );
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() );
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() );
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() );
97QgsFillSinksWangLiuAlgorithm *QgsFillSinksWangLiuAlgorithm::createInstance()
const
99 return new QgsFillSinksWangLiuAlgorithm();
104 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral(
"INPUT" ), context );
108 const int band = parameterAsInt( parameters, QStringLiteral(
"BAND" ), context );
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() ) );
116 mLayerWidth = layer->
width();
117 mLayerHeight = layer->
height();
118 mExtent = layer->
extent();
122 mRasterDiagonal = std::sqrt( mRasterUnitsPerPixelX * mRasterUnitsPerPixelX + mRasterUnitsPerPixelY * mRasterUnitsPerPixelY );
125 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
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 };
132bool QgsFillSinksWangLiuAlgorithm::isInGrid(
int row,
int col )
const
134 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
137QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir(
int row,
int col,
double z,
const QgsRasterBlock *filled )
const
140 double maxGradient = 0;
141 bool isNoData =
false;
143 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
145 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
146 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
148 if ( isInGrid( neighborRow, neighborCol ) )
150 const double neighborZ = filled->
valueAndNoData( neighborRow, neighborCol, isNoData );
151 if ( !isNoData && neighborZ < z )
153 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
154 if ( gradient >= maxGradient )
156 maxGradient = gradient;
157 steepestDirection = direction;
163 return steepestDirection;
166struct CFillSinks_WL_Node
176 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 )
const
178 return n1.spill > n2.spill;
182typedef std::vector< CFillSinks_WL_Node > nodeVector;
183typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
187 const QString createOptions = parameterAsString( parameters, QStringLiteral(
"CREATION_OPTIONS" ), context ).trimmed();
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 );
193 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
194 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
196 if ( !filledDemOutputFile.isEmpty() )
198 const QString outputFormat = parameterAsOutputRasterFormat( parameters, QStringLiteral(
"OUTPUT_FILLED_DEM" ), context );
200 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
201 filledDemWriter->setOutputProviderKey( QStringLiteral(
"gdal" ) );
202 if ( !createOptions.isEmpty() )
204 filledDemWriter->setCreationOptions( createOptions.split(
'|' ) );
206 filledDemWriter->setOutputFormat( outputFormat );
208 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
210 if ( !filledDemDestProvider )
211 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( filledDemOutputFile ) );
212 if ( !filledDemDestProvider->isValid() )
215 filledDemDestProvider->setNoDataValue( 1, mNoData );
216 filledDemDestProvider->setEditable(
true );
219 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
220 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
222 if ( !flowDirectionsOutputFile.isEmpty() )
224 const QString outputFormat = parameterAsOutputRasterFormat( parameters, QStringLiteral(
"OUTPUT_FLOW_DIRECTIONS" ), context );
226 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
227 flowDirectionsWriter->setOutputProviderKey( QStringLiteral(
"gdal" ) );
228 flowDirectionsWriter->setOutputFormat( outputFormat );
230 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
232 if ( !flowDirectionsDestProvider )
233 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
234 if ( !flowDirectionsDestProvider->isValid() )
237 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
238 flowDirectionsDestProvider->setEditable(
true );
241 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
242 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
244 if ( !watershedBasinsOutputFile.isEmpty() )
246 const QString outputFormat = parameterAsOutputRasterFormat( parameters, QStringLiteral(
"OUTPUT_WATERSHED_BASINS" ), context );
248 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
249 watershedBasinsWriter->setOutputProviderKey( QStringLiteral(
"gdal" ) );
250 watershedBasinsWriter->setOutputFormat( outputFormat );
252 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
254 if ( !watershedBasinsDestProvider )
255 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
256 if ( !watershedBasinsDestProvider->isValid() )
259 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
260 watershedBasinsDestProvider->setEditable(
true );
263 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
264 if ( !sourceDemData )
269 auto filledDemData = std::make_unique<QgsRasterBlock>( mDataType, mLayerWidth, mLayerHeight );
270 filledDemData->setNoDataValue( mNoData );
271 filledDemData->setIsNoData();
273 auto watershedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
274 watershedData->setNoDataValue( -1 );
275 watershedData->setIsNoData();
277 auto outputFlowData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
278 outputFlowData->setNoDataValue( 255 );
279 outputFlowData->setIsNoData();
281 auto seedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
284 double minSlope = parameterAsDouble( parameters, QStringLiteral(
"MIN_SLOPE" ), context );
286 bool preserve =
false;
287 if ( minSlope > 0.0 )
289 minSlope = tan( minSlope * M_PI / 180.0 );
290 for (
int i = 0; i < 8; i++ )
291 mindiff[i] = minSlope * mDirectionalLengths[i];
296 CFillSinks_WL_Node tempNode;
301 bool isNoData =
false;
304 std::size_t processed = 0;
305 const std::size_t totalCells =
static_cast< std::size_t
>( mLayerWidth ) * mLayerHeight;
307 for (
int row = 0; row < mLayerHeight; row++ )
312 for (
int col = 0; col < mLayerWidth; col++ )
314 value = sourceDemData->valueAndNoData( row, col, isNoData );
317 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
319 int iCol = col + COL_DIRECTION_OFFSETS[direction];
320 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
322 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
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 ) );
333 theQueue.push( tempNode );
339 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
347 feedback->
setProgressText( QObject::tr(
"Filling using least cost paths" ) );
349 while ( !theQueue.empty() )
351 PriorityQ::value_type tempNode = theQueue.top();
353 const int row = tempNode.row;
354 const int col = tempNode.col;
355 const double z = tempNode.spill;
358 const long long id =
static_cast< long long >( watershedData->value( row, col ) );
360 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
362 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
363 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
365 const bool iInGrid = isInGrid( iRow, iCol );
366 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
367 if ( iInGrid && !isNoData )
369 if ( filledDemData->isNoData( iRow, iCol ) )
373 iz = std::max( iz, z + mindiff[
static_cast< int >( direction )] );
378 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[
static_cast< int >( direction )] );
384 theQueue.push( tempNode );
386 filledDemData->setValue( iRow, iCol, iz );
387 watershedData->setValue( iRow, iCol,
id );
390 else if ( seedData->value( iRow, iCol ) == 1 )
392 watershedData->setValue( iRow, iCol,
id );
397 if ( outputFlowData->isNoData( row, col ) )
398 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
400 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
410 if ( filledDemDestProvider )
412 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
414 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
416 filledDemDestProvider->setEditable(
false );
417 outputs.insert( QStringLiteral(
"OUTPUT_FILLED_DEM" ), filledDemOutputFile );
419 if ( flowDirectionsDestProvider )
421 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
423 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
425 flowDirectionsDestProvider->setEditable(
false );
426 outputs.insert( QStringLiteral(
"OUTPUT_FLOW_DIRECTIONS" ), flowDirectionsOutputFile );
428 if ( watershedBasinsDestProvider )
430 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
432 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
434 watershedBasinsDestProvider->setEditable(
false );
435 outputs.insert( QStringLiteral(
"OUTPUT_WATERSHED_BASINS" ), 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.