24QString QgsFillSinksWangLiuAlgorithm::name()
const
26 return QStringLiteral(
"fillsinkswangliu" );
29QString QgsFillSinksWangLiuAlgorithm::displayName()
const
31 return QObject::tr(
"Fill sinks (Wang & Liu)" );
34QStringList QgsFillSinksWangLiuAlgorithm::tags()
const
36 return QObject::tr(
"fill,filter,slope,dsm,dtm,terrain,water,shed,basin,direction,flow" ).split(
',' );
39QString QgsFillSinksWangLiuAlgorithm::group()
const
41 return QObject::tr(
"Raster terrain analysis" );
44QString QgsFillSinksWangLiuAlgorithm::groupId()
const
46 return QStringLiteral(
"rasterterrainanalysis" );
49QString QgsFillSinksWangLiuAlgorithm::shortHelpString()
const
51 return QObject::tr(
"This algorithm uses a method proposed by Wang & Liu to identify and fill surface depressions in digital elevation models.\n\n"
53 "The method was enhanced to allow the creation of hydrologically sound elevation models, i.e. not only to fill the depression(s) "
54 "but also to preserve a downward slope along the flow path. If desired, this is accomplished by preserving a minimum slope "
55 "gradient (and thus elevation difference) between cells.\n\n"
57 "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"
59 "This algorithm is a port of the SAGA 'Fill Sinks (Wang & Liu)' tool." );
62QString QgsFillSinksWangLiuAlgorithm::shortDescription()
const
64 return QObject::tr(
"Identifies and fills surface depressions in digital elevation models using a method proposed by Wang & Liu." );
67void QgsFillSinksWangLiuAlgorithm::initAlgorithm(
const QVariantMap & )
71 addParameter(
new QgsProcessingParameterBand( QStringLiteral(
"BAND" ), QObject::tr(
"Band number" ), 1, QStringLiteral(
"INPUT" ) ) );
74 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." ) );
75 addParameter( minSlopeParam.release() );
77 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral(
"CREATION_OPTIONS" ), QObject::tr(
"Creation options" ), QVariant(),
false,
true );
78 createOptsParam->setMetadata( QVariantMap( { { QStringLiteral(
"widget_wrapper" ), QVariantMap( { { QStringLiteral(
"widget_type" ), QStringLiteral(
"rasteroptions" ) } } ) } } ) );
80 addParameter( createOptsParam.release() );
82 auto outputFilledDem = std::make_unique<QgsProcessingParameterRasterDestination>( QStringLiteral(
"OUTPUT_FILLED_DEM" ), QObject::tr(
"Output layer (filled DEM)" ), QVariant(),
true,
true );
83 outputFilledDem->setHelp( QObject::tr(
"Depression-free digital elevation model." ) );
84 addParameter( outputFilledDem.release() );
86 auto outputFlowDirections = std::make_unique<QgsProcessingParameterRasterDestination>( QStringLiteral(
"OUTPUT_FLOW_DIRECTIONS" ), QObject::tr(
"Output layer (flow directions)" ), QVariant(),
true,
false );
87 outputFlowDirections->setHelp( QObject::tr(
"Computed flow directions, 0=N, 1=NE, 2=E, ... 7=NW." ) );
88 addParameter( outputFlowDirections.release() );
90 auto outputWatershedBasins = std::make_unique<QgsProcessingParameterRasterDestination>( QStringLiteral(
"OUTPUT_WATERSHED_BASINS" ), QObject::tr(
"Output layer (watershed basins)" ), QVariant(),
true,
false );
91 outputWatershedBasins->setHelp( QObject::tr(
"Delineated watershed basin." ) );
92 addParameter( outputWatershedBasins.release() );
95QgsFillSinksWangLiuAlgorithm *QgsFillSinksWangLiuAlgorithm::createInstance()
const
97 return new QgsFillSinksWangLiuAlgorithm();
102 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral(
"INPUT" ), context );
106 const int band = parameterAsInt( parameters, QStringLiteral(
"BAND" ), context );
108 mBand = parameterAsInt( parameters, QStringLiteral(
"BAND" ), context );
109 if ( mBand < 1 || mBand > layer->
bandCount() )
110 throw QgsProcessingException( QObject::tr(
"Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->
bandCount() ) );
114 mLayerWidth = layer->
width();
115 mLayerHeight = layer->
height();
116 mExtent = layer->
extent();
120 mRasterDiagonal = std::sqrt( mRasterUnitsPerPixelX * mRasterUnitsPerPixelX + mRasterUnitsPerPixelY * mRasterUnitsPerPixelY );
123 mDirectionalLengths = { mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal, mRasterUnitsPerPixelY, mRasterDiagonal, mRasterUnitsPerPixelX, mRasterDiagonal };
127static constexpr std::array< int, 8 > COL_DIRECTION_OFFSETS { 0, 1, 1, 1, 0, -1, -1, -1 };
128static constexpr std::array< int, 8 > ROW_DIRECTION_OFFSETS { -1, -1, 0, 1, 1, 1, 0, -1 };
130bool QgsFillSinksWangLiuAlgorithm::isInGrid(
int row,
int col )
const
132 return col >= 0 && col < mLayerWidth && row >= 0 && row < mLayerHeight;
135QgsFillSinksWangLiuAlgorithm::Direction QgsFillSinksWangLiuAlgorithm::getDir(
int row,
int col,
double z,
const QgsRasterBlock *filled )
const
138 double maxGradient = 0;
139 bool isNoData =
false;
141 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
143 const int neighborCol = col + COL_DIRECTION_OFFSETS[direction];
144 const int neighborRow = row + ROW_DIRECTION_OFFSETS[direction];
146 if ( isInGrid( neighborRow, neighborCol ) )
148 const double neighborZ = filled->
valueAndNoData( neighborRow, neighborCol, isNoData );
149 if ( !isNoData && neighborZ < z )
151 const double gradient = ( z - neighborZ ) / mDirectionalLengths[direction];
152 if ( gradient >= maxGradient )
154 maxGradient = gradient;
155 steepestDirection = direction;
161 return steepestDirection;
164struct CFillSinks_WL_Node
174 bool operator()( CFillSinks_WL_Node n1, CFillSinks_WL_Node n2 )
const
176 return n1.spill > n2.spill;
180typedef std::vector< CFillSinks_WL_Node > nodeVector;
181typedef std::priority_queue< CFillSinks_WL_Node, nodeVector, CompareGreater > PriorityQ;
185 const QString createOptions = parameterAsString( parameters, QStringLiteral(
"CREATION_OPTIONS" ), context ).trimmed();
187 const QString filledDemOutputFile = parameterAsOutputLayer( parameters, QStringLiteral(
"OUTPUT_FILLED_DEM" ), context );
188 const QString flowDirectionsOutputFile = parameterAsOutputLayer( parameters, QStringLiteral(
"OUTPUT_FLOW_DIRECTIONS" ), context );
189 const QString watershedBasinsOutputFile = parameterAsOutputLayer( parameters, QStringLiteral(
"OUTPUT_WATERSHED_BASINS" ), context );
191 std::unique_ptr<QgsRasterFileWriter> filledDemWriter;
192 std::unique_ptr<QgsRasterDataProvider> filledDemDestProvider;
194 if ( !filledDemOutputFile.isEmpty() )
196 const QFileInfo fi( filledDemOutputFile );
199 filledDemWriter = std::make_unique<QgsRasterFileWriter>( filledDemOutputFile );
200 filledDemWriter->setOutputProviderKey( QStringLiteral(
"gdal" ) );
201 if ( !createOptions.isEmpty() )
203 filledDemWriter->setCreationOptions( createOptions.split(
'|' ) );
205 filledDemWriter->setOutputFormat( outputFormat );
207 filledDemDestProvider.reset( filledDemWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
209 if ( !filledDemDestProvider )
210 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( filledDemOutputFile ) );
211 if ( !filledDemDestProvider->isValid() )
214 filledDemDestProvider->setNoDataValue( 1, mNoData );
215 filledDemDestProvider->setEditable(
true );
218 std::unique_ptr<QgsRasterFileWriter> flowDirectionsWriter;
219 std::unique_ptr<QgsRasterDataProvider> flowDirectionsDestProvider;
221 if ( !flowDirectionsOutputFile.isEmpty() )
223 const QFileInfo fi( flowDirectionsOutputFile );
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 QFileInfo fi( watershedBasinsOutputFile );
249 watershedBasinsWriter = std::make_unique<QgsRasterFileWriter>( watershedBasinsOutputFile );
250 watershedBasinsWriter->setOutputProviderKey( QStringLiteral(
"gdal" ) );
251 watershedBasinsWriter->setOutputFormat( outputFormat );
253 watershedBasinsDestProvider.reset( watershedBasinsWriter->createOneBandRaster(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
255 if ( !watershedBasinsDestProvider )
256 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( watershedBasinsOutputFile ) );
257 if ( !watershedBasinsDestProvider->isValid() )
260 watershedBasinsDestProvider->setNoDataValue( 1, -1 );
261 watershedBasinsDestProvider->setEditable(
true );
264 std::unique_ptr< QgsRasterBlock > sourceDemData( mInterface->block( mBand, mExtent, mLayerWidth, mLayerHeight ) );
265 if ( !sourceDemData )
270 auto filledDemData = std::make_unique<QgsRasterBlock>( mDataType, mLayerWidth, mLayerHeight );
271 filledDemData->setNoDataValue( mNoData );
272 filledDemData->setIsNoData();
274 auto watershedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Int32, mLayerWidth, mLayerHeight );
275 watershedData->setNoDataValue( -1 );
276 watershedData->setIsNoData();
278 auto outputFlowData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
279 outputFlowData->setNoDataValue( 255 );
280 outputFlowData->setIsNoData();
282 auto seedData = std::make_unique<QgsRasterBlock>(
Qgis::DataType::Byte, mLayerWidth, mLayerHeight );
285 double minSlope = parameterAsDouble( parameters, QStringLiteral(
"MIN_SLOPE" ), context );
287 bool preserve =
false;
288 if ( minSlope > 0.0 )
290 minSlope = tan( minSlope * M_PI / 180.0 );
291 for (
int i = 0; i < 8; i++ )
292 mindiff[i] = minSlope * mDirectionalLengths[i];
297 CFillSinks_WL_Node tempNode;
302 bool isNoData =
false;
305 std::size_t processed = 0;
306 const std::size_t totalCells =
static_cast< std::size_t
>( mLayerWidth ) * mLayerHeight;
308 for (
int row = 0; row < mLayerHeight; row++ )
313 for (
int col = 0; col < mLayerWidth; col++ )
315 value = sourceDemData->valueAndNoData( row, col, isNoData );
318 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
320 int iCol = col + COL_DIRECTION_OFFSETS[direction];
321 int iRow = row + ROW_DIRECTION_OFFSETS[direction];
323 if ( !isInGrid( iRow, iCol ) || sourceDemData->isNoData( iRow, iCol ) )
325 const double z = value;
326 filledDemData->setValue( row, col, z );
327 seedData->setValue( row, col, 1.0 );
328 watershedData->setValue( row, col,
static_cast< double >(
id ) );
334 theQueue.push( tempNode );
340 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
348 feedback->
setProgressText( QObject::tr(
"Filling using least cost paths" ) );
350 while ( !theQueue.empty() )
352 PriorityQ::value_type tempNode = theQueue.top();
354 const int row = tempNode.row;
355 const int col = tempNode.col;
356 const double z = tempNode.spill;
359 const long long id =
static_cast< long long >( watershedData->value( row, col ) );
361 for (
Direction direction : { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest } )
363 const int iCol = col + COL_DIRECTION_OFFSETS[direction];
364 const int iRow = row + ROW_DIRECTION_OFFSETS[direction];
366 const bool iInGrid = isInGrid( iRow, iCol );
367 double iz = iInGrid ? sourceDemData->valueAndNoData( iRow, iCol, isNoData ) : 0;
368 if ( iInGrid && !isNoData )
370 if ( filledDemData->isNoData( iRow, iCol ) )
374 iz = std::max( iz, z + mindiff[
static_cast< int >( direction )] );
379 outputFlowData->setValue( iRow, iCol, INVERSE_DIRECTION[
static_cast< int >( direction )] );
385 theQueue.push( tempNode );
387 filledDemData->setValue( iRow, iCol, iz );
388 watershedData->setValue( iRow, iCol,
id );
391 else if ( seedData->value( iRow, iCol ) == 1 )
393 watershedData->setValue( iRow, iCol,
id );
398 if ( outputFlowData->isNoData( row, col ) )
399 outputFlowData->setValue( row, col, getDir( row, col, z, filledDemData.get() ) );
401 feedback->
setProgress(
static_cast< double >( processed ) /
static_cast< double >( totalCells ) * 100 );
411 if ( filledDemDestProvider )
413 if ( !filledDemDestProvider->writeBlock( filledDemData.get(), 1, 0, 0 ) )
415 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( filledDemDestProvider->error().summary() ) );
417 filledDemDestProvider->setEditable(
false );
418 outputs.insert( QStringLiteral(
"OUTPUT_FILLED_DEM" ), filledDemOutputFile );
420 if ( flowDirectionsDestProvider )
422 if ( !flowDirectionsDestProvider->writeBlock( outputFlowData.get(), 1, 0, 0 ) )
424 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( flowDirectionsDestProvider->error().summary() ) );
426 flowDirectionsDestProvider->setEditable(
false );
427 outputs.insert( QStringLiteral(
"OUTPUT_FLOW_DIRECTIONS" ), flowDirectionsOutputFile );
429 if ( watershedBasinsDestProvider )
431 if ( !watershedBasinsDestProvider->writeBlock( watershedData.get(), 1, 0, 0 ) )
433 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( watershedBasinsDestProvider->error().summary() ) );
435 watershedBasinsDestProvider->setEditable(
false );
436 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.
static QString driverForExtension(const QString &extension)
Returns the GDAL driver name for a specified file extension.
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.