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 );
151 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
155static constexpr std::array< int, 8 > COL_DIRECTION_OFFSETS { 0, 1, 1, 1, 0, -1, -1, -1 };
156static constexpr std::array< int, 8 > ROW_DIRECTION_OFFSETS { -1, -1, 0, 1, 1, 1, 0, -1 };
158bool QgsFillSinksWangLiuAlgorithm::isInGrid(
int row,
int col )
const
160 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
163QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir(
int row,
int col,
double z,
const QgsRasterBlock *filled )
const
166 double maxGradient = 0;
167 bool isNoData =
false;
169 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
171 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
172 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
174 if ( isInGrid( neighborRow, neighborCol ) )
176 const double neighborZ = filled->
valueAndNoData( neighborRow, neighborCol, isNoData );
177 if ( !isNoData && neighborZ < z )
179 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
180 if ( gradient >= maxGradient )
182 maxGradient = gradient;
183 steepestDirection = direction;
189 return steepestDirection;
192struct CFillSinks_WL_Node
202 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 )
const {
return n1.spill > n2.spill; }
205typedef std::vector< CFillSinks_WL_Node > nodeVector;
206typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
210 QGS_MARK_ALGORITHM_SOURCE
212 const QString createOptions = parameterAsString( parameters, u
"CREATION_OPTIONS"_s, context ).trimmed();
214 const QString filledDemOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_FILLED_DEM"_s, context );
215 const QString flowDirectionsOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_FLOW_DIRECTIONS"_s, context );
216 const QString watershedBasinsOutputFile = parameterAsOutputLayer( parameters, u
"OUTPUT_WATERSHED_BASINS"_s, context );
218 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
219 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
221 if ( !filledDemOutputFile.isEmpty() )
223 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_FILLED_DEM"_s, context );
225 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
226 filledDemWriter->setOutputProviderKey( u
"gdal"_s );
227 if ( !createOptions.isEmpty() )
229 filledDemWriter->setCreationOptions( createOptions.split(
'|' ) );
231 filledDemWriter->setOutputFormat( outputFormat );
233 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster(
Qgis::DataType::Float32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
235 if ( !filledDemDestProvider )
236 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( filledDemOutputFile ) );
237 if ( !filledDemDestProvider->isValid() )
240 filledDemDestProvider->setNoDataValue( 1, mNoData );
241 filledDemDestProvider->setEditable(
true );
244 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
245 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
247 if ( !flowDirectionsOutputFile.isEmpty() )
249 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_FLOW_DIRECTIONS"_s, context );
251 flowDirectionsWriter = std::make_unique<QgsRasterFileWriter>( flowDirectionsOutputFile );
252 flowDirectionsWriter->setOutputProviderKey( u
"gdal"_s );
253 flowDirectionsWriter->setOutputFormat( outputFormat );
255 flowDirectionsDestProvider.reset( flowDirectionsWriter->createOneBandRaster(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
257 if ( !flowDirectionsDestProvider )
258 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( flowDirectionsOutputFile ) );
259 if ( !flowDirectionsDestProvider->isValid() )
262 flowDirectionsDestProvider->setNoDataValue( 1, 255 );
263 flowDirectionsDestProvider->setEditable(
true );
266 std::unique_ptr<QgsRasterFileWriter> watershedBasinsWriter;
267 std::unique_ptr<QgsRasterDataProvider> watershedBasinsDestProvider;
269 if ( !watershedBasinsOutputFile.isEmpty() )
271 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u
"OUTPUT_WATERSHED_BASINS"_s, context );
273 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
274 watershedBasinsWriter->setOutputProviderKey( u
"gdal"_s );
275 watershedBasinsWriter->setOutputFormat( outputFormat );
277 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
279 if ( !watershedBasinsDestProvider )
280 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
281 if ( !watershedBasinsDestProvider->isValid() )
284 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
285 watershedBasinsDestProvider->setEditable(
true );
288 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
289 if ( !sourceDemData )
295 filledDemData->setNoDataValue( mNoData );
296 filledDemData->setIsNoData();
298 auto watershedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
299 watershedData->setNoDataValue( -1 );
300 watershedData->setIsNoData();
302 auto outputFlowData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
303 outputFlowData->setNoDataValue( 255 );
304 outputFlowData->setIsNoData();
306 auto seedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
309 double minSlope = parameterAsDouble( parameters, u
"MIN_SLOPE"_s, context );
311 bool preserve =
false;
312 if ( minSlope > 0.0 )
314 minSlope = tan( minSlope * M_PI / 180.0 );
315 for (
int i = 0; i < 8; i++ )
316 mindiff[i] = minSlope * mDirectionalLengths[i];
321 CFillSinks_WL_Node tempNode;
326 bool isNoData =
false;
329 std::size_t processed = 0;
330 const std::size_t totalCells =
static_cast< std::size_t
>( mLayerWidth ) * mLayerHeight;
332 for (
int row = 0; row < mLayerHeight; row++ )
337 for (
int col = 0; col < mLayerWidth; col++ )
339 value = sourceDemData->valueAndNoData( row, col, isNoData );
342 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
344 int iCol = col + COL_DIRECTION_OFFSETS[direction];
345 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
347 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
349 const double z = value;
350 filledDemData->setValue( row, col, z );
351 seedData->setValue( row, col, 1.0 );
352 watershedData->setValue( row, col,
static_cast< double >(
id ) );
358 theQueue.push( tempNode );
364 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
372 feedback->
setProgressText( QObject::tr(
"Filling using least cost paths" ) );
374 while ( !theQueue.empty() )
376 PriorityQ::value_type tempNode = theQueue.top();
378 const int row = tempNode.row;
379 const int col = tempNode.col;
380 const double z = tempNode.spill;
383 const long long id =
static_cast< long long >( watershedData->value( row, col ) );
385 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
387 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
388 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
390 const bool iInGrid = isInGrid( iRow, iCol );
391 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
392 if ( iInGrid && !isNoData )
394 if ( filledDemData->isNoData( iRow, iCol ) )
398 iz = std::max( iz, z + mindiff[
static_cast< int >( direction )] );
403 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[
static_cast< int >( direction )] );
409 theQueue.push( tempNode );
411 filledDemData->setValue( iRow, iCol, iz );
412 watershedData->setValue( iRow, iCol,
id );
415 else if ( seedData->value( iRow, iCol ) == 1 )
417 watershedData->setValue( iRow, iCol,
id );
422 if ( outputFlowData->isNoData( row, col ) )
423 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
425 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
435 if ( filledDemDestProvider )
437 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
439 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
441 filledDemDestProvider->setEditable(
false );
442 outputs.insert( u
"OUTPUT_FILLED_DEM"_s, filledDemOutputFile );
444 if ( flowDirectionsDestProvider )
446 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
448 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
450 flowDirectionsDestProvider->setEditable(
false );
451 outputs.insert( u
"OUTPUT_FLOW_DIRECTIONS"_s, flowDirectionsOutputFile );
453 if ( watershedBasinsDestProvider )
455 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
457 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
459 watershedBasinsDestProvider->setEditable(
false );
460 outputs.insert( u
"OUTPUT_WATERSHED_BASINS"_s, watershedBasinsOutputFile );
@ Float32
Thirty two bit floating point (float).
@ 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.
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.