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,
82QList<QgsProcessingAlgorithm::ExternalLink> QgsFillSinksWangLiuAlgorithm::externalLinks()
const
85 QgsProcessingAlgorithm::ExternalLink { QObject::tr(
"SAGA tool source code" ), u
"https://sourceforge.net/p/saga-gis/code/ci/72d9890b130fd446d7ffb7251c44b2e98c8e1e53/tree/saga-gis/src/tools/terrain_analysis/ta_preprocessor/FillSinks_WL.cpp"_s }
89QString QgsFillSinksWangLiuAlgorithm::shortDescription()
const
91 return QObject::tr(
"Identifies and fills surface depressions in digital elevation models using a method proposed by Wang & Liu." );
94void QgsFillSinksWangLiuAlgorithm::initAlgorithm(
const QVariantMap & )
101 minSlopeParam->setHelp(
102 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." )
104 addParameter( minSlopeParam.release() );
106 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( u
"CREATION_OPTIONS"_s, QObject::tr(
"Creation options" ), QVariant(),
false,
true );
107 createOptsParam->setMetadata( QVariantMap( { { u
"widget_wrapper"_s, QVariantMap( { { u
"widget_type"_s, u
"rasteroptions"_s } } ) } } ) );
109 addParameter( createOptsParam.release() );
111 auto outputFilledDem = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_FILLED_DEM"_s, QObject::tr(
"Output layer (filled DEM)" ), QVariant(),
true,
true );
112 outputFilledDem->setHelp( QObject::tr(
"Depression-free digital elevation model." ) );
113 addParameter( outputFilledDem.release() );
115 auto outputFlowDirections = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_FLOW_DIRECTIONS"_s, QObject::tr(
"Output layer (flow directions)" ), QVariant(),
true,
false );
116 outputFlowDirections->setHelp( QObject::tr(
"Computed flow directions, 0=N, 1=NE, 2=E, ... 7=NW." ) );
117 addParameter( outputFlowDirections.release() );
119 auto outputWatershedBasins = std::make_unique<QgsProcessingParameterRasterDestination>( u
"OUTPUT_WATERSHED_BASINS"_s, QObject::tr(
"Output layer (watershed basins)" ), QVariant(),
true,
false );
120 outputWatershedBasins->setHelp( QObject::tr(
"Delineated watershed basin." ) );
121 addParameter( outputWatershedBasins.release() );
124QgsFillSinksWangLiuAlgorithm *QgsFillSinksWangLiuAlgorithm::createInstance()
const
126 return new QgsFillSinksWangLiuAlgorithm();
131 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u
"INPUT"_s, context );
135 const int band = parameterAsInt( parameters, u
"BAND"_s, context );
137 mBand = parameterAsInt( parameters, u
"BAND"_s, context );
138 if ( mBand < 1 || mBand > layer->
bandCount() )
139 throw QgsProcessingException( QObject::tr(
"Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->
bandCount() ) );
143 mLayerWidth = layer->
width();
144 mLayerHeight = layer->
height();
145 mExtent = layer->
extent();
149 mRasterDiagonal = std::sqrt( mRasterUnitsPerPixelX * mRasterUnitsPerPixelX + mRasterUnitsPerPixelY * mRasterUnitsPerPixelY );
152 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
156static constexpr std::array< int, 8 > COL_DIRECTION_OFFSETS { 0, 1, 1, 1, 0, -1, -1, -1 };
157static constexpr std::array< int, 8 > ROW_DIRECTION_OFFSETS { -1, -1, 0, 1, 1, 1, 0, -1 };
159bool QgsFillSinksWangLiuAlgorithm::isInGrid(
int row,
int col )
const
161 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
164QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir(
int row,
int col,
double z,
const QgsRasterBlock *filled )
const
167 double maxGradient = 0;
168 bool isNoData =
false;
170 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
172 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
173 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
175 if ( isInGrid( neighborRow, neighborCol ) )
177 const double neighborZ = filled->
valueAndNoData( neighborRow, neighborCol, isNoData );
178 if ( !isNoData && neighborZ < z )
180 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
181 if ( gradient >= maxGradient )
183 maxGradient = gradient;
184 steepestDirection = direction;
190 return steepestDirection;
193struct CFillSinks_WL_Node
203 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 )
const {
return n1.spill > n2.spill; }
206typedef std::vector< CFillSinks_WL_Node > nodeVector;
207typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
211 QGS_MARK_ALGORITHM_SOURCE
213 const QString createOptions = parameterAsString( parameters, u
"CREATION_OPTIONS"_s, context ).trimmed();
215 const QString filledDemOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_FILLED_DEM"_s, context );
216 const QString flowDirectionsOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_FLOW_DIRECTIONS"_s, context );
217 const QString watershedBasinsOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_WATERSHED_BASINS"_s, context );
219 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
220 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
222 if ( !filledDemOutputFile.isEmpty() )
224 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_FILLED_DEM"_s, context );
226 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
227 filledDemWriter->setOutputProviderKey( u
"gdal"_s );
228 if ( !createOptions.isEmpty() )
230 filledDemWriter->setCreationOptions( createOptions.split(
'|' ) );
232 filledDemWriter->setOutputFormat( outputFormat );
234 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
236 if ( !filledDemDestProvider )
237 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( filledDemOutputFile ) );
238 if ( !filledDemDestProvider->isValid() )
241 filledDemDestProvider->setNoDataValue( 1, mNoData );
242 filledDemDestProvider->setEditable(
true );
245 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
246 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
248 if ( !flowDirectionsOutputFile.isEmpty() )
250 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_FLOW_DIRECTIONS"_s, context );
252 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
253 flowDirectionsWriter->setOutputProviderKey( u
"gdal"_s );
254 flowDirectionsWriter->setOutputFormat( outputFormat );
256 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
258 if ( !flowDirectionsDestProvider )
259 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
260 if ( !flowDirectionsDestProvider->isValid() )
263 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
264 flowDirectionsDestProvider->setEditable(
true );
267 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
268 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
270 if ( !watershedBasinsOutputFile.isEmpty() )
272 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_WATERSHED_BASINS"_s, context );
274 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
275 watershedBasinsWriter->setOutputProviderKey( u
"gdal"_s );
276 watershedBasinsWriter->setOutputFormat( outputFormat );
278 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
280 if ( !watershedBasinsDestProvider )
281 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
282 if ( !watershedBasinsDestProvider->isValid() )
285 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
286 watershedBasinsDestProvider->setEditable(
true );
289 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
290 if ( !sourceDemData )
295 auto filledDemData = std::make_unique<QgsRasterBlock>( mDataType, mLayerWidth, mLayerHeight );
296 filledDemData->setNoDataValue( mNoData );
297 filledDemData->setIsNoData();
299 auto watershedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
300 watershedData->setNoDataValue( -1 );
301 watershedData->setIsNoData();
303 auto outputFlowData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
304 outputFlowData->setNoDataValue( 255 );
305 outputFlowData->setIsNoData();
307 auto seedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
310 double minSlope = parameterAsDouble( parameters, u
"MIN_SLOPE"_s, context );
312 bool preserve =
false;
313 if ( minSlope > 0.0 )
315 minSlope = tan( minSlope * M_PI / 180.0 );
316 for (
int i = 0; i < 8; i++ )
317 mindiff[i] = minSlope * mDirectionalLengths[i];
322 CFillSinks_WL_Node tempNode;
327 bool isNoData =
false;
330 std::size_t processed = 0;
331 const std::size_t totalCells =
static_cast< std::size_t
>( mLayerWidth ) * mLayerHeight;
333 for (
int row = 0; row < mLayerHeight; row++ )
338 for (
int col = 0; col < mLayerWidth; col++ )
340 value = sourceDemData->valueAndNoData( row, col, isNoData );
343 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
345 int iCol = col + COL_DIRECTION_OFFSETS[direction];
346 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
348 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
350 const double z = value;
351 filledDemData->setValue( row, col, z );
352 seedData->setValue( row, col, 1.0 );
353 watershedData->setValue( row, col,
static_cast< double >(
id ) );
359 theQueue.push( tempNode );
365 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
373 feedback->
setProgressText( QObject::tr(
"Filling using least cost paths" ) );
375 while ( !theQueue.empty() )
377 PriorityQ::value_type tempNode = theQueue.top();
379 const int row = tempNode.row;
380 const int col = tempNode.col;
381 const double z = tempNode.spill;
384 const long long id =
static_cast< long long >( watershedData->value( row, col ) );
386 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
388 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
389 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
391 const bool iInGrid = isInGrid( iRow, iCol );
392 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
393 if ( iInGrid && !isNoData )
395 if ( filledDemData->isNoData( iRow, iCol ) )
399 iz = std::max( iz, z + mindiff[
static_cast< int >( direction )] );
404 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[
static_cast< int >( direction )] );
410 theQueue.push( tempNode );
412 filledDemData->setValue( iRow, iCol, iz );
413 watershedData->setValue( iRow, iCol,
id );
416 else if ( seedData->value( iRow, iCol ) == 1 )
418 watershedData->setValue( iRow, iCol,
id );
423 if ( outputFlowData->isNoData( row, col ) )
424 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
426 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
436 if ( filledDemDestProvider )
438 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
440 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
442 filledDemDestProvider->setEditable(
false );
443 outputs.insert( u
"OUTPUT_FILLED_DEM"_s, filledDemOutputFile );
445 if ( flowDirectionsDestProvider )
447 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
449 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
451 flowDirectionsDestProvider->setEditable(
false );
452 outputs.insert( u
"OUTPUT_FLOW_DIRECTIONS"_s, flowDirectionsOutputFile );
454 if ( watershedBasinsDestProvider )
456 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
458 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
460 watershedBasinsDestProvider->setEditable(
false );
461 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.
Encapsulates details of an external link describing an algorithm's behavior or source.