26using namespace Qt::StringLiterals;
30QString QgsFillSinksWangLiuAlgorithm::name()
const
32 return u
"fillsinkswangliu"_s;
35QString QgsFillSinksWangLiuAlgorithm::displayName()
const
37 return QObject::tr(
"Fill sinks (Wang & Liu)" );
40QStringList QgsFillSinksWangLiuAlgorithm::tags()
const
42 return QObject::tr(
"fill,filter,slope,dsm,dtm,terrain,water,shed,basin,direction,flow" ).split(
',' );
45QString QgsFillSinksWangLiuAlgorithm::group()
const
47 return QObject::tr(
"Raster terrain analysis" );
50QString QgsFillSinksWangLiuAlgorithm::groupId()
const
52 return u
"rasterterrainanalysis"_s;
55QString QgsFillSinksWangLiuAlgorithm::shortHelpString()
const
58 "This algorithm uses a method proposed by Wang & Liu to identify and fill surface depressions in digital elevation models.\n\n"
60 "The method was enhanced to allow the creation of hydrologically sound elevation models, i.e. not only to fill the depression(s) "
61 "but also to preserve a downward slope along the flow path. If desired, this is accomplished by preserving a minimum slope "
62 "gradient (and thus elevation difference) between cells.\n\n"
64 "This algorithm is a port of the SAGA 'Fill Sinks (Wang & Liu)' tool."
68QList<QgsAcademicReference> QgsFillSinksWangLiuAlgorithm::academicReferences()
const
71 { u
"Wang, L."_s, u
"Liu, H."_s },
73 u
"An efficient method for identifying and filling surface depressions in digital elevation models for hydrologic analysis and modelling."_s,
74 u
"International Journal of Geographical Information Science"_s,
82QString QgsFillSinksWangLiuAlgorithm::shortDescription()
const
84 return QObject::tr(
"Identifies and fills surface depressions in digital elevation models using a method proposed by Wang & Liu." );
87void QgsFillSinksWangLiuAlgorithm::initAlgorithm(
const QVariantMap & )
94 minSlopeParam->setHelp(
95 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." )
97 addParameter( minSlopeParam.release() );
99 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( u
"CREATION_OPTIONS"_s, QObject::tr(
"Creation options" ), QVariant(),
false,
true );
100 createOptsParam->setMetadata( QVariantMap( { { u
"widget_wrapper"_s, QVariantMap( { { u
"widget_type"_s, u
"rasteroptions"_s } } ) } } ) );
102 addParameter( createOptsParam.release() );
104 auto outputFilledDem = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_FILLED_DEM"_s, QObject::tr(
"Output layer (filled DEM)" ), QVariant(),
true,
true );
105 outputFilledDem->setHelp( QObject::tr(
"Depression-free digital elevation model." ) );
106 addParameter( outputFilledDem.release() );
108 auto outputFlowDirections = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_FLOW_DIRECTIONS"_s, QObject::tr(
"Output layer (flow directions)" ), QVariant(),
true,
false );
109 outputFlowDirections->setHelp( QObject::tr(
"Computed flow directions, 0=N, 1=NE, 2=E, ... 7=NW." ) );
110 addParameter( outputFlowDirections.release() );
112 auto outputWatershedBasins = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_WATERSHED_BASINS"_s, QObject::tr(
"Output layer (watershed basins)" ), QVariant(),
true,
false );
113 outputWatershedBasins->setHelp( QObject::tr(
"Delineated watershed basin." ) );
114 addParameter( outputWatershedBasins.release() );
117QgsFillSinksWangLiuAlgorithm *QgsFillSinksWangLiuAlgorithm::createInstance()
const
119 return new QgsFillSinksWangLiuAlgorithm();
124 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u
"INPUT"_s, context );
128 const int band = parameterAsInt( parameters, u
"BAND"_s, context );
130 mBand = parameterAsInt( parameters, u
"BAND"_s, context );
131 if ( mBand < 1 || mBand > layer->
bandCount() )
132 throw QgsProcessingException( QObject::tr(
"Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->
bandCount() ) );
136 mLayerWidth = layer->
width();
137 mLayerHeight = layer->
height();
138 mExtent = layer->
extent();
142 mRasterDiagonal = std::sqrt( mRasterUnitsPerPixelX * mRasterUnitsPerPixelX + mRasterUnitsPerPixelY * mRasterUnitsPerPixelY );
145 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
149static constexpr std::array< int, 8 > COL_DIRECTION_OFFSETS { 0, 1, 1, 1, 0, -1, -1, -1 };
150static constexpr std::array< int, 8 > ROW_DIRECTION_OFFSETS { -1, -1, 0, 1, 1, 1, 0, -1 };
152bool QgsFillSinksWangLiuAlgorithm::isInGrid(
int row,
int col )
const
154 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
157QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir(
int row,
int col,
double z,
const QgsRasterBlock *filled )
const
160 double maxGradient = 0;
161 bool isNoData =
false;
163 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
165 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
166 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
168 if ( isInGrid( neighborRow, neighborCol ) )
170 const double neighborZ = filled->
valueAndNoData( neighborRow, neighborCol, isNoData );
171 if ( !isNoData && neighborZ < z )
173 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
174 if ( gradient >= maxGradient )
176 maxGradient = gradient;
177 steepestDirection = direction;
183 return steepestDirection;
186struct CFillSinks_WL_Node
196 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 )
const {
return n1.spill > n2.spill; }
199typedef std::vector< CFillSinks_WL_Node > nodeVector;
200typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
204 QGS_MARK_ALGORITHM_SOURCE
206 const QString createOptions = parameterAsString( parameters, u
"CREATION_OPTIONS"_s, context ).trimmed();
208 const QString filledDemOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_FILLED_DEM"_s, context );
209 const QString flowDirectionsOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_FLOW_DIRECTIONS"_s, context );
210 const QString watershedBasinsOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_WATERSHED_BASINS"_s, context );
212 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
213 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
215 if ( !filledDemOutputFile.isEmpty() )
217 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_FILLED_DEM"_s, context );
219 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
220 filledDemWriter->setOutputProviderKey( u
"gdal"_s );
221 if ( !createOptions.isEmpty() )
223 filledDemWriter->setCreationOptions( createOptions.split(
'|' ) );
225 filledDemWriter->setOutputFormat( outputFormat );
227 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
229 if ( !filledDemDestProvider )
230 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( filledDemOutputFile ) );
231 if ( !filledDemDestProvider->isValid() )
234 filledDemDestProvider->setNoDataValue( 1, mNoData );
235 filledDemDestProvider->setEditable(
true );
238 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
239 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
241 if ( !flowDirectionsOutputFile.isEmpty() )
243 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_FLOW_DIRECTIONS"_s, context );
245 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
246 flowDirectionsWriter->setOutputProviderKey( u
"gdal"_s );
247 flowDirectionsWriter->setOutputFormat( outputFormat );
249 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
251 if ( !flowDirectionsDestProvider )
252 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
253 if ( !flowDirectionsDestProvider->isValid() )
256 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
257 flowDirectionsDestProvider->setEditable(
true );
260 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
261 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
263 if ( !watershedBasinsOutputFile.isEmpty() )
265 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_WATERSHED_BASINS"_s, context );
267 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
268 watershedBasinsWriter->setOutputProviderKey( u
"gdal"_s );
269 watershedBasinsWriter->setOutputFormat( outputFormat );
271 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
273 if ( !watershedBasinsDestProvider )
274 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
275 if ( !watershedBasinsDestProvider->isValid() )
278 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
279 watershedBasinsDestProvider->setEditable(
true );
282 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
283 if ( !sourceDemData )
288 auto filledDemData = std::make_unique<QgsRasterBlock>( mDataType, mLayerWidth, mLayerHeight );
289 filledDemData->setNoDataValue( mNoData );
290 filledDemData->setIsNoData();
292 auto watershedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
293 watershedData->setNoDataValue( -1 );
294 watershedData->setIsNoData();
296 auto outputFlowData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
297 outputFlowData->setNoDataValue( 255 );
298 outputFlowData->setIsNoData();
300 auto seedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
303 double minSlope = parameterAsDouble( parameters, u
"MIN_SLOPE"_s, context );
305 bool preserve =
false;
306 if ( minSlope > 0.0 )
308 minSlope = tan( minSlope * M_PI / 180.0 );
309 for (
int i = 0; i < 8; i++ )
310 mindiff[i] = minSlope * mDirectionalLengths[i];
315 CFillSinks_WL_Node tempNode;
320 bool isNoData =
false;
323 std::size_t processed = 0;
324 const std::size_t totalCells =
static_cast< std::size_t
>( mLayerWidth ) * mLayerHeight;
326 for (
int row = 0; row < mLayerHeight; row++ )
331 for (
int col = 0; col < mLayerWidth; col++ )
333 value = sourceDemData->valueAndNoData( row, col, isNoData );
336 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
338 int iCol = col + COL_DIRECTION_OFFSETS[direction];
339 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
341 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
343 const double z = value;
344 filledDemData->setValue( row, col, z );
345 seedData->setValue( row, col, 1.0 );
346 watershedData->setValue( row, col,
static_cast< double >(
id ) );
352 theQueue.push( tempNode );
358 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
366 feedback->
setProgressText( QObject::tr(
"Filling using least cost paths" ) );
368 while ( !theQueue.empty() )
370 PriorityQ::value_type tempNode = theQueue.top();
372 const int row = tempNode.row;
373 const int col = tempNode.col;
374 const double z = tempNode.spill;
377 const long long id =
static_cast< long long >( watershedData->value( row, col ) );
379 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
381 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
382 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
384 const bool iInGrid = isInGrid( iRow, iCol );
385 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
386 if ( iInGrid && !isNoData )
388 if ( filledDemData->isNoData( iRow, iCol ) )
392 iz = std::max( iz, z + mindiff[
static_cast< int >( direction )] );
397 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[
static_cast< int >( direction )] );
403 theQueue.push( tempNode );
405 filledDemData->setValue( iRow, iCol, iz );
406 watershedData->setValue( iRow, iCol,
id );
409 else if ( seedData->value( iRow, iCol ) == 1 )
411 watershedData->setValue( iRow, iCol,
id );
416 if ( outputFlowData->isNoData( row, col ) )
417 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
419 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
429 if ( filledDemDestProvider )
431 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
433 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
435 filledDemDestProvider->setEditable(
false );
436 outputs.insert( u
"OUTPUT_FILLED_DEM"_s, filledDemOutputFile );
438 if ( flowDirectionsDestProvider )
440 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
442 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
444 flowDirectionsDestProvider->setEditable(
false );
445 outputs.insert( u
"OUTPUT_FLOW_DIRECTIONS"_s, flowDirectionsOutputFile );
447 if ( watershedBasinsDestProvider )
449 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
451 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
453 watershedBasinsDestProvider->setEditable(
false );
454 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.
Encapsulates an academic reference and formats it according to style guidelines.
static QgsAcademicReference createJournalArticle(const QStringList &authors, int year, const QString &title, const QString &journal, const QString &volume=QString(), const QString &issue=QString(), const QString &pages=QString())
Creates a journal article reference.
bool isCanceled() const
Tells whether the operation has been canceled already.
void setProgress(double progress)
Sets the current progress for the feedback object.
virtual Q_INVOKABLE 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.