QGIS API Documentation 4.3.0-Master (45633be667c)
Loading...
Searching...
No Matches
qgsalgorithmupslopearea.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmupslopearea.cpp
3 ---------------------
4 begin : September 2026
5 copyright : (C) 2026 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
22#include "qgsrasterfilewriter.h"
24#include "qgsvariantutils.h"
25
26#include <QString>
27
28using namespace Qt::StringLiterals;
29
31
32// SAGA 8-neighbor directions: 0: N, 1: NE, 2: E, 3: SE, 4: S, 5: SW, 6: W, 7: NW
33// SAGA Cartesian directions (SAGA_X points East, SAGA_Y points North):
34static constexpr std::array<int, 8> SAGA_X { 0, 1, 1, 1, 0, -1, -1, -1 };
35static constexpr std::array<int, 8> SAGA_Y { 1, 1, 0, -1, -1, -1, 0, 1 };
36
37QString QgsUpslopeAreaAlgorithmBase::group() const
38{
39 return QObject::tr( "Raster terrain analysis" );
40}
41
42QString QgsUpslopeAreaAlgorithmBase::groupId() const
43{
44 return u"rasterterrainanalysis"_s;
45}
46
47QStringList QgsUpslopeAreaAlgorithmBase::tags() const
48{
49 return QObject::tr( "upslope,area,flow,accumulation,hydrology,catchment,watershed,target" ).split( ',' );
50}
51
52QList<QgsAcademicReference> QgsUpslopeAreaAlgorithmBase::academicReferences() const
53{
54 const QgsAcademicReference freemanReference = QgsAcademicReference::
55 createJournalArticle( { u"Freeman, G. T."_s }, 1991, u"Calculating catchment area with divergent flow based on a regular grid"_s, u"Computers and Geosciences"_s, u"17"_s, QString(), u"413-422"_s );
56
57 const QgsAcademicReference ocallaghanReference = QgsAcademicReference::
58 createJournalArticle( { u"O'Callaghan, J. F."_s, u"Mark, D. M."_s }, 1984, u"The extraction of drainage networks from digital elevation data"_s, u"Computer Vision, Graphics and Image Processing"_s, u"28"_s, QString(), u"323-344"_s );
59
61 { u"Qin, C. Z."_s, u"Zhu, A. X."_s, u"Pei, T."_s, u"Li, B. L."_s, u"Scholten, T."_s, u"Behrens, T."_s, u"Zhou, C. H."_s },
62 2011,
63 u"An approach to computing topographic wetness index based on maximum downslope gradient"_s,
64 u"Precision Agriculture"_s,
65 u"12"_s,
66 u"1"_s,
67 u"32-43"_s
68 );
69
71 { u"Quinn, P. F."_s, u"Beven, K. J."_s, u"Chevallier, P."_s, u"Planchon, O."_s },
72 1991,
73 u"The prediction of hillslope flow paths for distributed hydrological modelling using digital terrain models"_s,
74 u"Hydrological Processes"_s,
75 u"5"_s,
76 QString(),
77 u"59-79"_s
78 );
79
80 const QgsAcademicReference seibertReference = QgsAcademicReference::
81 createJournalArticle( { u"Seibert, J."_s, u"McGlynn, B."_s }, 2007, u"A new triangular multiple flow direction algorithm for computing upslope areas from gridded digital elevation models"_s, u"Water Resources Research"_s, u"43"_s, QString(), u"W04501"_s );
82
83 const QgsAcademicReference tarbotonReference = QgsAcademicReference::
84 createJournalArticle( { u"Tarboton, D. G."_s }, 1997, u"A new method for the determination of flow directions and upslope areas in grid digital elevation models"_s, u"Water Resources Research"_s, u"33"_s, u"2"_s, u"309-319"_s );
85
86 return { freemanReference, ocallaghanReference, qinReference, quinnReference, seibertReference, tarbotonReference };
87}
88
89QList<QgsProcessingAlgorithm::ExternalLink> QgsUpslopeAreaAlgorithmBase::externalLinks() const
90{
91 return {
92 QgsProcessingAlgorithm::ExternalLink { QObject::tr( "SAGA tool source code" ), u"https://sourceforge.net/p/saga-gis/code/ci/f24c137792a79906840f455ce36b1122b83e2f45/tree/saga-gis/src/tools/terrain_analysis/ta_hydrology/Flow_AreaUpslope.cpp"_s }
93 };
94}
95
96void QgsUpslopeAreaAlgorithmBase::addCommonParameters()
97{
98 auto demParam = std::make_unique<QgsProcessingParameterRasterLayer>( u"ELEVATION"_s, QObject::tr( "Elevation" ) );
99 demParam->setHelp( QObject::tr( "Input digital elevation model (DEM) raster layer." ) );
100 addParameter( demParam.release() );
101
102 auto routeParam = std::make_unique<QgsProcessingParameterRasterLayer>( u"SINK_ROUTES"_s, QObject::tr( "Sink routes" ), QVariant(), true );
103 routeParam->setHelp( QObject::tr( "Optional raster layer specifying explicit flow routes through sinks/depressions." ) );
104 addParameter( routeParam.release() );
105
106 const QStringList methods
107 = { QObject::tr( "Deterministic 8" ), QObject::tr( "Deterministic Infinity" ), QObject::tr( "Multiple Flow Direction" ), QObject::tr( "Multiple Triangular Flow Direction" ), QObject::tr( "Multiple Maximum Downslope Gradient Based Flow Direction" ) };
108 auto methodParam = std::make_unique<QgsProcessingParameterEnum>( u"METHOD"_s, QObject::tr( "Method" ), methods, false, 2 );
109 methodParam->setHelp( QObject::tr( "Flow routing algorithm used to determine flow distribution to downslope cells." ) );
110 addParameter( methodParam.release() );
111
112 auto convergeParam = std::make_unique<QgsProcessingParameterNumber>( u"CONVERGE"_s, QObject::tr( "Convergence" ), Qgis::ProcessingNumberParameterType::Double, 1.1, false, 0.001 );
113 convergeParam->setHelp( QObject::tr( "Convergence factor for Multiple Flow Direction algorithms." ) );
114 addParameter( convergeParam.release() );
115
116 auto contourParam = std::make_unique<QgsProcessingParameterBoolean>( u"MFD_CONTOUR"_s, QObject::tr( "Use contour length weighting" ), false );
117 contourParam->setHelp(
118 QObject::tr( "Include pseudo contour length weighting factor in multiple flow routing. Reduces flow to diagonal neighbour cells by a factor of 0.71 (see Quinn et al. 1991 for details)." )
119 );
120 addParameter( contourParam.release() );
121
122 auto outputNodataParam = std::make_unique<QgsProcessingParameterNumber>( u"NODATA"_s, QObject::tr( "Output NoData value" ), Qgis::ProcessingNumberParameterType::Integer, -9999 );
123 outputNodataParam->setHelp( QObject::tr( "The NODATA value to use in the output raster." ) );
124 outputNodataParam->setFlags( outputNodataParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
125 addParameter( outputNodataParam.release() );
126
127 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATION_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
128 creationOptsParam->setHelp( QObject::tr( "The raster creation options for the output raster. These options control things like colorimetry, compression, etc." ) );
129 creationOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
130 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
131 addParameter( creationOptsParam.release() );
132
133 auto outputParam = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT"_s, QObject::tr( "Upslope area" ) );
134 addParameter( outputParam.release() );
135}
136
137bool QgsUpslopeAreaAlgorithmBase::prepareBase( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
138{
139 QgsRasterLayer *demLayer = parameterAsRasterLayer( parameters, u"ELEVATION"_s, context );
140 if ( !demLayer || !demLayer->dataProvider() )
141 throw QgsProcessingException( invalidRasterError( parameters, u"ELEVATION"_s ) );
142
143 mDemProvider.reset( demLayer->dataProvider()->clone() );
144 mDemCrs = demLayer->crs();
145 mExtent = demLayer->extent();
146 mCols = demLayer->width();
147 mRows = demLayer->height();
148 mCellSizeX = demLayer->rasterUnitsPerPixelX();
149 mCellSizeY = demLayer->rasterUnitsPerPixelY();
150
151 if ( demLayer->dataProvider()->sourceHasNoDataValue( 1 ) )
152 mDemNoData = demLayer->dataProvider()->sourceNoDataValue( 1 );
153
154 QgsRasterLayer *routeLayer = parameterAsRasterLayer( parameters, u"SINK_ROUTES"_s, context );
155 if ( routeLayer && routeLayer->dataProvider() )
156 {
157 mRouteProvider.reset( routeLayer->dataProvider()->clone() );
158 if ( routeLayer->dataProvider()->sourceHasNoDataValue( 1 ) )
159 mRouteNoData = routeLayer->dataProvider()->sourceNoDataValue( 1 );
160 }
161 else if ( !QgsVariantUtils::isNull( parameters.value( u"SINK_ROUTES"_s ) ) )
162 {
163 throw QgsProcessingException( invalidRasterError( parameters, u"SINK_ROUTES"_s ) );
164 }
165
166 mMethod = static_cast<Method>( parameterAsInt( parameters, u"METHOD"_s, context ) );
167 mConvergence = parameterAsDouble( parameters, u"CONVERGE"_s, context );
168 mMfdContour = parameterAsBool( parameters, u"MFD_CONTOUR"_s, context );
169
170 mCreationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
171 mOutputNoData = parameterAsDouble( parameters, u"NODATA"_s, context );
172 return true;
173}
174
175bool QgsUpslopeAreaAlgorithmBase::calculateUpslopeArea( const std::vector<QgsPointXY> &targetPoints, QgsProcessingContext &, QgsProcessingFeedback *feedback )
176{
177 const qgssize nCells = static_cast<qgssize>( mCols ) * mRows;
178
179 std::unique_ptr<QgsRasterBlock> demBlock( mDemProvider->block( 1, mExtent, mCols, mRows ) );
180 if ( !demBlock )
181 throw QgsProcessingException( QObject::tr( "Could not read DEM raster block." ) );
182
183 // load (optional) sink routes
184 if ( mRouteProvider )
185 {
186 mRouteData.resize( nCells, -1.0 );
187 std::unique_ptr<QgsRasterBlock> routeBlock( mRouteProvider->block( 1, mExtent, mCols, mRows ) );
188 if ( routeBlock )
189 {
190 for ( int r = 0; r < mRows; ++r )
191 {
192 for ( int c = 0; c < mCols; ++c )
193 {
194 mRouteData[static_cast<qgssize>( r ) * mCols + c] = routeBlock->value( r, c );
195 }
196 }
197 }
198 }
199
200 mFlowData.assign( nCells, 0.0 );
201
202 // map input target points to raster cell coordinates and set initial target flow to 100.0
203 // (matches SAGA's CFlow_AreaUpslope::Add_Target)
204 bool hasValidTarget = false;
205 int column = 0;
206 int row = 0;
207 for ( const QgsPointXY &pt : targetPoints )
208 {
209 QgsRasterAnalysisUtils::mapToPixel( pt.x(), pt.y(), mExtent, mCellSizeX, mCellSizeY, column, row );
210 if ( column >= 0 && column < mCols && row >= 0 && row < mRows )
211 {
212 mFlowData[static_cast<qgssize>( row ) * mCols + column] = 100.0;
213 hasValidTarget = true;
214 }
215 }
216
217 if ( !hasValidTarget )
218 {
219 feedback->reportError( QObject::tr( "All target point(s) lie outside the DEM extent." ) );
220 return false;
221 }
222
223 // process cells in ascending topological order (lowest to highest elevation)
224 // (matching SAGA's CFlow_AreaUpslope::Get_Area)
225 const QgsSortedRasterBlockIndex sortedIndex( demBlock.get() );
226 const qgssize count = sortedIndex.sortedCount();
227 for ( qgssize i = 0; i < count; ++i )
228 {
229 if ( feedback->isCanceled() )
230 return false;
231 feedback->setProgress( 100.0 * static_cast<double>( i ) / count );
232 sortedIndex.sortedColumnRow( i, column, row, Qt::AscendingOrder );
233
234 // calculate cell flow if not already set by target initialization
235
236 if ( mFlowData[static_cast<qgssize>( row ) * mCols + column] <= 0.0 )
237 {
238 computeCellValue( demBlock.get(), column, row, mCols, mRows, mCellSizeX, mCellSizeY, mMethod, mConvergence, mMfdContour );
239 }
240 }
241
242 if ( feedback->isCanceled() )
243 return false;
244
245 auto writer = std::make_unique<QgsRasterFileWriter>( mOutputPath );
246 writer->setOutputProviderKey( u"gdal"_s );
247 if ( !mCreationOptions.isEmpty() )
248 {
249 writer->setCreationOptions( mCreationOptions.split( '|' ) );
250 }
251 writer->setOutputFormat( mOutputFormat );
252
253 std::unique_ptr<QgsRasterDataProvider> provider( writer->createOneBandRaster( Qgis::DataType::Float32, mCols, mRows, mExtent, mDemCrs ) );
254 if ( !provider )
255 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( mOutputPath ) );
256 if ( !provider->isValid() )
257 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( mOutputPath, provider->error().message( QgsErrorMessage::Text ) ) );
258
259 provider->setNoDataValue( 1, mOutputNoData );
260 provider->setEditable( true );
261
262 QgsRasterBlock outputBlock( Qgis::DataType::Float32, mCols, mRows );
263 for ( int r = 0; r < mRows; ++r )
264 {
265 for ( int c = 0; c < mCols; ++c )
266 {
267 outputBlock.setValue( r, c, static_cast<float>( mFlowData[static_cast<qgssize>( r ) * mCols + c] ) );
268 }
269 }
270
271 if ( !provider->writeBlock( &outputBlock, 1 ) )
272 {
273 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( provider->error().summary() ) );
274 }
275
276 provider->setEditable( false );
277 return true;
278}
279
280void QgsUpslopeAreaAlgorithmBase::computeCellValue( const QgsRasterBlock *demBlock, int col, int row, int cols, int rows, double cellSizeX, double cellSizeY, Method method, double converge, bool contour )
281{
282 const qgssize idx = static_cast<qgssize>( row ) * cols + col;
283
284 // check explicit sink route if available (see SAGA's CFlow_AreaUpslope::Set_Value)
285 if ( !mRouteData.empty() )
286 {
287 const int routeDir = static_cast<int>( mRouteData[idx] );
288 if ( routeDir >= 0 && routeDir < 8 )
289 {
290 int neighborCol = 0;
291 int neighborRow = 0;
292 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( routeDir, row, col, neighborRow, neighborCol, row, cols ) )
293 {
294 const double routedFlow = mFlowData[static_cast<qgssize>( neighborRow ) * cols + neighborCol];
295 if ( routedFlow > 0.0 )
296 {
297 mFlowData[idx] = routedFlow;
298 }
299 }
300 return;
301 }
302 }
303
304 switch ( method )
305 {
306 case Method::D8:
307 computeD8( demBlock, col, row, cols, rows, cellSizeX, cellSizeY );
308 break;
309 case Method::DInf:
310 computeDInf( demBlock, col, row, cols, rows, cellSizeX, cellSizeY );
311 break;
312 case Method::MFD:
313 computeMFD( demBlock, col, row, cols, rows, cellSizeX, cellSizeY, converge, contour );
314 break;
315 case Method::MDInf:
316 computeMDInf( demBlock, col, row, cols, rows, cellSizeX, cellSizeY, converge );
317 break;
318 case Method::MMDGFD:
319 computeMMDGFD( demBlock, col, row, cols, rows, cellSizeX, cellSizeY, contour );
320 break;
321 }
322}
323
324void QgsUpslopeAreaAlgorithmBase::computeD8( const QgsRasterBlock *demBlock, int col, int row, int cols, int rows, double cellSizeX, double cellSizeY )
325{
326 const int steepestDir = QgsRasterAnalysisUtils::steepestGradientDirection( demBlock, row, col, cellSizeX, cellSizeY );
327
328 if ( steepestDir >= 0 )
329 {
330 int neighborCol = 0;
331 int neighborRow = 0;
332 QgsRasterAnalysisUtils::neighborCellCoordinates( steepestDir, row, col, neighborRow, neighborCol, rows, cols );
333 const double neighborFlow = mFlowData[static_cast<qgssize>( neighborRow ) * cols + neighborCol];
334 if ( neighborFlow > 0.0 )
335 {
336 mFlowData[static_cast<qgssize>( row ) * cols + col] = neighborFlow;
337 }
338 }
339}
340
341void QgsUpslopeAreaAlgorithmBase::computeDInf( const QgsRasterBlock *demBlock, int col, int row, int cols, int rows, double cellSizeX, double cellSizeY )
342{
343 bool isNoData = false;
344 const double z = demBlock->valueAndNoData( row, col, isNoData );
345 if ( isNoData )
346 {
347 // follow SAGA -- fallback to D8
348 computeD8( demBlock, col, row, cols, rows, cellSizeX, cellSizeY );
349 return;
350 }
351
352 // Following SAGA's CSG_Grid::Get_Gradient (which follows Zevenbergen & Thorne 1986)
353 double dz[4] = { 0.0, 0.0, 0.0, 0.0 };
354 const std::array<int, 4> dirs { 0, 2, 4, 6 };
355 for ( int i = 0; i < 4; ++i )
356 {
357 const int iDir = dirs[i];
358 const int oppositeDir = ( iDir + 4 ) % 8;
359
360 int neighborCol = 0;
361 int neighborRow = 0;
362 int oppositeNeighborCol = 0;
363 int oppositeNeighborRow = 0;
364 bool neighborIsNoData = true;
365 double neighborZ = 0;
366 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( iDir, row, col, neighborRow, neighborCol, rows, cols ) )
367 {
368 neighborZ = demBlock->valueAndNoData( neighborRow, neighborCol, neighborIsNoData );
369 }
370 bool oppositeIsNoData = true;
371 double oppositeNeighborZ = 0;
372 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( oppositeDir, row, col, oppositeNeighborRow, oppositeNeighborCol, rows, cols ) )
373 {
374 oppositeNeighborZ = demBlock->valueAndNoData( oppositeNeighborRow, oppositeNeighborCol, oppositeIsNoData );
375 }
376
377 if ( !neighborIsNoData )
378 {
379 dz[i] = neighborZ - z;
380 }
381 else if ( !oppositeIsNoData )
382 {
383 dz[i] = z - oppositeNeighborZ;
384 }
385 else
386 {
387 dz[i] = 0.0;
388 }
389 }
390
391 const double G = ( dz[0] - dz[2] ) / ( 2.0 * cellSizeY );
392 const double H = ( dz[1] - dz[3] ) / ( 2.0 * cellSizeX );
393
394 double aspect = -1.0;
395 if ( G != 0.0 )
396 {
397 aspect = M_PI + std::atan2( H, G );
398 if ( aspect < 0.0 )
399 aspect += 2.0 * M_PI;
400 if ( aspect >= 2.0 * M_PI )
401 aspect -= 2.0 * M_PI;
402 }
403 else if ( H > 0.0 )
404 {
405 aspect = 1.5 * M_PI;
406 }
407 else if ( H < 0.0 )
408 {
409 aspect = 0.5 * M_PI;
410 }
411
412 if ( aspect >= 0.0 )
413 {
414 const int i = static_cast<int>( aspect / ( M_PI / 4.0 ) ) % 8;
415 const int j = ( i + 1 ) % 8;
416
417 int iCol = 0;
418 int iRow = 0;
419 int jCol = 0;
420 int jRow = 0;
421 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( i, row, col, iRow, iCol, rows, cols ) && QgsRasterAnalysisUtils::neighborCellCoordinates( j, row, col, jRow, jCol, rows, cols ) )
422 {
423 bool iIsNoData = false;
424 const double zi = demBlock->valueAndNoData( iRow, iCol, iIsNoData );
425 bool jIsNoData = false;
426 const double zj = demBlock->valueAndNoData( jRow, jCol, jIsNoData );
427 if ( !iIsNoData && !jIsNoData )
428 {
429 // both sector neighbors must be lower in elevation
430 if ( zi < z && zj < z )
431 {
432 const double aspectFraction = std::fmod( aspect, M_PI / 4.0 ) / ( M_PI / 4.0 );
433 const double flowI = mFlowData[static_cast<qgssize>( iRow ) * cols + iCol];
434 const double flowJ = mFlowData[static_cast<qgssize>( jRow ) * cols + jCol];
435
436 const double accumulatedFlow = flowI * ( 1.0 - aspectFraction ) + flowJ * aspectFraction;
437 if ( accumulatedFlow > 0.0 )
438 {
439 mFlowData[static_cast<qgssize>( row ) * cols + col] = accumulatedFlow;
440 }
441 return;
442 }
443 }
444 }
445 }
446
447 computeD8( demBlock, col, row, cols, rows, cellSizeX, cellSizeY );
448}
449
450void QgsUpslopeAreaAlgorithmBase::computeMFD( const QgsRasterBlock *demBlock, int col, int row, int cols, int rows, double cellSizeX, double cellSizeY, double converge, bool contour )
451{
452 const double z = demBlock->value( row, col );
453 double dz[8];
454 double dzSum = 0.0;
455
456 bool isNodata = false;
457 for ( int dir = 0; dir < 8; ++dir )
458 {
459 dz[dir] = 0.0;
460 int neighborCol = 0;
461 int neighborRow = 0;
462 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( dir, row, col, neighborRow, neighborCol, rows, cols ) )
463 {
464 const double nZ = demBlock->valueAndNoData( neighborRow, neighborCol, isNodata );
465 if ( !isNodata )
466 {
467 const double diff = z - nZ;
468 if ( diff > 0.0 )
469 {
470 const double length = QgsRasterAnalysisUtils::neighborCellDistance( dir, cellSizeX, cellSizeY );
471 const double weight = std::pow( diff / length, converge ) * ( ( contour && ( dir % 2 ) ) ? ( M_SQRT1_2 ) : 1.0 );
472 dz[dir] = weight;
473 dzSum += weight;
474 }
475 }
476 }
477 }
478
479 if ( dzSum > 0.0 )
480 {
481 double flow = 0.0;
482 for ( int dir = 0; dir < 8; ++dir )
483 {
484 if ( dz[dir] > 0.0 )
485 {
486 int nCol = 0;
487 int nRow = 0;
488 QgsRasterAnalysisUtils::neighborCellCoordinates( dir, row, col, nRow, nCol, rows, cols );
489 const double nFlow = mFlowData[static_cast<qgssize>( nRow ) * cols + nCol];
490 if ( nFlow > 0.0 )
491 {
492 flow += ( dz[dir] / dzSum ) * nFlow;
493 }
494 }
495 }
496
497 if ( flow > 0.0 )
498 {
499 mFlowData[static_cast<qgssize>( row ) * cols + col] = flow;
500 }
501 }
502}
503
504void QgsUpslopeAreaAlgorithmBase::computeMMDGFD( const QgsRasterBlock *demBlock, int col, int row, int cols, int rows, double cellSizeX, double cellSizeY, bool contour )
505{
506 const double z = demBlock->value( row, col );
507 double dz[8];
508 double dzMax = 0.0;
509
510 bool isNodata = false;
511 for ( int dir = 0; dir < 8; ++dir )
512 {
513 dz[dir] = 0.0;
514 int neighborCol = 0;
515 int neighborRow = 0;
516 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( dir, row, col, neighborRow, neighborCol, rows, cols ) )
517 {
518 const double nZ = demBlock->valueAndNoData( neighborRow, neighborCol, isNodata );
519 if ( !isNodata )
520 {
521 const double diff = z - nZ;
522 if ( diff > 0.0 )
523 {
524 dz[dir] = diff / QgsRasterAnalysisUtils::neighborCellDistance( dir, cellSizeX, cellSizeY );
525 if ( dzMax < dz[dir] )
526 {
527 dzMax = dz[dir];
528 }
529 }
530 }
531 }
532 }
533
534 if ( dzMax > 0.0 )
535 {
536 const double exponent = ( dzMax < 1.0 ) ? ( 8.9 * dzMax + 1.1 ) : 10.0;
537 double dzSum = 0.0;
538
539 for ( int i = 0; i < 8; ++i )
540 {
541 if ( dz[i] > 0.0 )
542 {
543 dz[i] = std::pow( dz[i], exponent ) * ( ( contour && ( i % 2 ) ) ? M_SQRT1_2 : 1.0 );
544 dzSum += dz[i];
545 }
546 }
547
548 if ( dzSum > 0.0 )
549 {
550 double flow = 0.0;
551 for ( int i = 0; i < 8; ++i )
552 {
553 if ( dz[i] > 0.0 )
554 {
555 int neighborCol = 0;
556 int neighborRow = 0;
557 QgsRasterAnalysisUtils::neighborCellCoordinates( i, row, col, neighborRow, neighborCol, rows, cols );
558 const double nFlow = mFlowData[static_cast<qgssize>( neighborRow ) * cols + neighborCol];
559 if ( nFlow > 0.0 )
560 {
561 flow += ( dz[i] / dzSum ) * nFlow;
562 }
563 }
564 }
565
566 if ( flow > 0.0 )
567 {
568 mFlowData[static_cast<qgssize>( row ) * cols + col] = flow;
569 }
570 }
571 }
572}
573
574void QgsUpslopeAreaAlgorithmBase::computeMDInf( const QgsRasterBlock *demBlock, int col, int row, int cols, int rows, double cellSizeX, double cellSizeY, double converge )
575{
576 const double z = demBlock->value( row, col );
577 bool bInGrid[8];
578 double dz[8];
579 double sFacet[8];
580 double rFacet[8];
581
582 bool isNodata = false;
583 for ( int i = 0; i < 8; ++i )
584 {
585 bInGrid[i] = false;
586 dz[i] = 0;
587 sFacet[i] = -999;
588 rFacet[i] = -999;
589 int neighborCol = 0;
590 int neighborRow = 0;
591 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( i, row, col, neighborRow, neighborCol, rows, cols ) )
592 {
593 const double nZ = demBlock->valueAndNoData( neighborRow, neighborCol, isNodata );
594 if ( !isNodata )
595 {
596 bInGrid[i] = true;
597 dz[i] = z - nZ;
598 }
599 }
600 }
601
602 for ( int i = 0; i < 8; ++i )
603 {
604 double hs = -999.0;
605 double hr = -999.0;
606
607 if ( bInGrid[i] )
608 {
609 const int j = ( i < 7 ) ? i + 1 : 0;
610
611 if ( bInGrid[j] )
612 {
613 const double nx = ( dz[j] * SAGA_Y[i] - dz[i] * SAGA_Y[j] ) * cellSizeY;
614 const double ny = ( dz[i] * SAGA_X[j] - dz[j] * SAGA_X[i] ) * cellSizeX;
615 const double nz = ( SAGA_X[i] * SAGA_Y[j] - SAGA_X[j] * SAGA_Y[i] ) * ( cellSizeX * cellSizeY );
616
617 const double nNorm = std::sqrt( nx * nx + ny * ny + nz * nz );
618
619 if ( nx == 0.0 )
620 {
621 hr = ( ny >= 0.0 ) ? 0.0 : M_PI;
622 }
623 else if ( nx < 0.0 )
624 {
625 hr = ( 1.5 * M_PI ) - std::atan( ny / nx );
626 }
627 else
628 {
629 hr = ( 0.5 * M_PI ) - std::atan( ny / nx );
630 }
631
632 const double cosAngle = std::clamp( nz / nNorm, -1.0, 1.0 );
633 hs = -std::tan( std::acos( cosAngle ) );
634
635 if ( hr < i * ( M_PI / 4.0 ) || hr > ( i + 1 ) * ( M_PI / 4.0 ) )
636 {
637 if ( dz[i] > dz[j] )
638 {
639 hr = i * ( M_PI / 4.0 );
640 hs = dz[i] / QgsRasterAnalysisUtils::neighborCellDistance( i, cellSizeX, cellSizeY );
641 }
642 else
643 {
644 hr = j * ( M_PI / 4.0 );
645 hs = dz[j] / QgsRasterAnalysisUtils::neighborCellDistance( j, cellSizeX, cellSizeY );
646 }
647 }
648 }
649 else if ( dz[i] > 0.0 )
650 {
651 hr = i * ( M_PI / 4.0 );
652 hs = dz[i] / QgsRasterAnalysisUtils::neighborCellDistance( i, cellSizeX, cellSizeY );
653 }
654
655 sFacet[i] = hs;
656 rFacet[i] = hr;
657 }
658 }
659
660 double dzSum = 0.0;
661 double valley[8];
662 double portion[8];
663 for ( int i = 0; i < 8; ++i )
664 {
665 valley[i] = 0;
666 portion[i] = 0;
667 int j = ( i < 7 ) ? i + 1 : 0;
668
669 if ( sFacet[i] > 0.0 )
670 {
671 if ( rFacet[i] > i * ( M_PI / 4.0 ) && rFacet[i] < ( i + 1 ) * ( M_PI / 4.0 ) )
672 {
673 valley[i] = sFacet[i];
674 }
675 else if ( rFacet[i] == rFacet[j] )
676 {
677 valley[i] = sFacet[i];
678 }
679 else if ( sFacet[j] == -999.0 && rFacet[i] == ( i + 1 ) * ( M_PI / 4.0 ) )
680 {
681 valley[i] = sFacet[i];
682 }
683 else
684 {
685 const int k = ( i > 0 ) ? i - 1 : 7;
686 if ( sFacet[k] == -999.0 && rFacet[i] == i * ( M_PI / 4.0 ) )
687 {
688 valley[i] = sFacet[i];
689 }
690 }
691
692 valley[i] = std::pow( valley[i], converge );
693 dzSum += valley[i];
694 }
695 portion[i] = 0.0;
696 }
697
698 if ( dzSum > 0.0 )
699 {
700 for ( int i = 0; i < 8; ++i )
701 {
702 const int j = ( i < 7 ) ? i + 1 : 0;
703
704 if ( i >= 7 && rFacet[i] == 0.0 )
705 {
706 rFacet[i] = 2.0 * M_PI;
707 }
708
709 if ( valley[i] > 0.0 )
710 {
711 valley[i] /= dzSum;
712 portion[i] += valley[i] * ( ( i + 1 ) * ( M_PI / 4.0 ) - rFacet[i] ) / ( M_PI / 4.0 );
713 portion[j] += valley[i] * ( rFacet[i] - i * ( M_PI / 4.0 ) ) / ( M_PI / 4.0 );
714 }
715 }
716
717 double flow = 0.0;
718 for ( int i = 0; i < 8; ++i )
719 {
720 if ( portion[i] > 0.0 )
721 {
722 int nCol = 0, nRow = 0;
723 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( i, row, col, nRow, nCol, rows, cols ) )
724 {
725 const double nFlow = mFlowData[static_cast<qgssize>( nRow ) * cols + nCol];
726 if ( nFlow > 0.0 )
727 {
728 flow += nFlow * portion[i];
729 }
730 }
731 }
732 }
733
734 if ( flow > 0.0 )
735 {
736 mFlowData[static_cast<qgssize>( row ) * cols + col] = flow;
737 }
738 }
739}
740
741
742//
743// QgsUpslopeAreaPointAlgorithm
744//
745
746QgsUpslopeAreaPointAlgorithm::QgsUpslopeAreaPointAlgorithm() = default;
747
748QString QgsUpslopeAreaPointAlgorithm::name() const
749{
750 return u"upslopeareafrompoint"_s;
751}
752
753QString QgsUpslopeAreaPointAlgorithm::displayName() const
754{
755 return QObject::tr( "Upslope area (from point)" );
756}
757
758QString QgsUpslopeAreaPointAlgorithm::shortDescription() const
759{
760 return QObject::tr( "Calculates the upslope contributing area for a specified target point coordinate." );
761}
762
763QString QgsUpslopeAreaPointAlgorithm::shortHelpString() const
764{
765 return QObject::tr(
766 "This algorithm calculates the upslope contributing area (catchment) for a single target coordinate point on a Digital Elevation Model (DEM).\n\n"
767 "Each output raster cell value represents the percentage (0% to 100%) of surface flow originating at that cell that drains to or passes through the target point.\n\n"
768 "Supported flow routing methods are:\n\n"
769 "• Deterministic 8: Single-flow direction algorithm, routing 100% of flow to the steepest downslope neighbor (O'Callaghan & Mark 1984).\n"
770 "• Deterministic Infinity: Continuous single-facet flow direction algorithm, routing flow along triangular facets using a 3×3 finite-difference aspect calculation (Tarboton 1997).\n"
771 "• Multiple Flow Direction: Divergent flow distribution to all lower-elevation neighbors, weighted by slope and a configurable convergence exponent (Freeman 1991, Quinn et al. 1991).\n"
772 "• Multiple Triangular Flow Direction: Advanced divergent routing utilizing 3D vector normal cross-products across triangular facets to distribute flow smoothly across complex terrain (Seibert & "
773 "McGlynn 2007).\n"
774 "• Multiple Maximum Downslope Gradient: Adaptive MFD variant scaling exponent weights dynamically based on the local maximum gradient (Qin et al. 2011).\n\n"
775 "An optional sink routes raster layer can be provided to explicitly override topographic flow and direct water through karst features, culverts, or artificial depressions.\n\n"
776 "This algorithm is a port of the SAGA 'Upslope Area' tool."
777 );
778}
779
780QgsProcessingAlgorithm *QgsUpslopeAreaPointAlgorithm::createInstance() const
781{
782 return new QgsUpslopeAreaPointAlgorithm();
783}
784
785void QgsUpslopeAreaPointAlgorithm::initAlgorithm( const QVariantMap & )
786{
787 addCommonParameters();
788
789 auto pointParam = std::make_unique<QgsProcessingParameterPoint>( u"TARGET_PT"_s, QObject::tr( "Target point" ) );
790 pointParam->setHelp( QObject::tr( "World coordinate point defining the target cell." ) );
791 addParameter( pointParam.release() );
792}
793
794bool QgsUpslopeAreaPointAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
795{
796 return prepareBase( parameters, context, feedback );
797}
798
799QVariantMap QgsUpslopeAreaPointAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
800{
801 QGS_MARK_ALGORITHM_SOURCE
802
803 const QgsPointXY targetPt = parameterAsPoint( parameters, u"TARGET_PT"_s, context, mDemCrs );
804
805 mOutputPath = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
806 mOutputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
807
808 calculateUpslopeArea( { targetPt }, context, feedback );
809
810 QVariantMap outputs;
811 outputs.insert( u"OUTPUT"_s, mOutputPath );
812 return outputs;
813}
814
815
816//
817// QgsUpslopeAreaLayerAlgorithm
818//
819
820QgsUpslopeAreaLayerAlgorithm::QgsUpslopeAreaLayerAlgorithm() = default;
821
822QString QgsUpslopeAreaLayerAlgorithm::name() const
823{
824 return u"upslopeareafromlayer"_s;
825}
826
827QString QgsUpslopeAreaLayerAlgorithm::displayName() const
828{
829 return QObject::tr( "Upslope area (from layer)" );
830}
831
832QString QgsUpslopeAreaLayerAlgorithm::shortDescription() const
833{
834 return QObject::tr( "Calculates the combined upslope contributing area for target points in a vector layer." );
835}
836
837QString QgsUpslopeAreaLayerAlgorithm::shortHelpString() const
838{
839 return QObject::tr( "This algorithm calculates the combined upslope contributing catchment area for target points provided in an input vector point layer." );
840
841 return QObject::tr(
842 "This algorithm calculates the combined upslope contributing area (catchments) for all target point locations provided in an input vector layer.\n\n"
843 "Each output raster cell value represents the percentage (0% to 100%) of surface flow originating at that cell that reaches at least one of the target points in the input vector layer.\n\n"
844 "Supported flow routing methods are:\n\n"
845 "• Deterministic 8: Single-flow direction algorithm, routing 100% of flow to the steepest downslope neighbor (O'Callaghan & Mark 1984).\n"
846 "• Deterministic Infinity: Continuous single-facet flow direction algorithm, routing flow along triangular facets using a 3×3 finite-difference aspect calculation (Tarboton 1997).\n"
847 "• Multiple Flow Direction: Divergent flow distribution to all lower-elevation neighbors, weighted by slope and a configurable convergence exponent (Freeman 1991, Quinn et al. 1991).\n"
848 "• Multiple Triangular Flow Direction: Advanced divergent routing utilizing 3D vector normal cross-products across triangular facets to distribute flow smoothly across complex terrain (Seibert & "
849 "McGlynn 2007).\n"
850 "• Multiple Maximum Downslope Gradient: Adaptive MFD variant scaling exponent weights dynamically based on the local maximum gradient (Qin et al. 2011).\n\n"
851 "An optional sink routes raster layer can be provided to explicitly override topographic flow and direct water through karst features, culverts, or artificial depressions.\n\n"
852 "This algorithm is a port of the SAGA 'Upslope Area' tool."
853 );
854}
855
856QgsProcessingAlgorithm *QgsUpslopeAreaLayerAlgorithm::createInstance() const
857{
858 return new QgsUpslopeAreaLayerAlgorithm();
859}
860
861void QgsUpslopeAreaLayerAlgorithm::initAlgorithm( const QVariantMap & )
862{
863 addCommonParameters();
864
865 auto layerParam
866 = std::make_unique<QgsProcessingParameterFeatureSource>( u"TARGET_LAYER"_s, QObject::tr( "Target point layer" ), QList<int> { static_cast<int>( Qgis::ProcessingSourceType::VectorPoint ) } );
867 layerParam->setHelp( QObject::tr( "Vector point layer containing target locations." ) );
868 addParameter( layerParam.release() );
869}
870
871bool QgsUpslopeAreaLayerAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
872{
873 return prepareBase( parameters, context, feedback );
874}
875
876QVariantMap QgsUpslopeAreaLayerAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
877{
878 QGS_MARK_ALGORITHM_SOURCE
879
880 std::unique_ptr<QgsFeatureSource> targetSource( parameterAsSource( parameters, u"TARGET_LAYER"_s, context ) );
881 if ( !targetSource )
882 throw QgsProcessingException( invalidSourceError( parameters, u"TARGET_LAYER"_s ) );
883
884 std::vector<QgsPointXY> targetPoints;
885 QgsFeature f;
886 QgsFeatureIterator fit = targetSource->getFeatures( QgsFeatureRequest().setNoAttributes().setDestinationCrs( mDemCrs, context.transformContext() ) );
887 while ( fit.nextFeature( f ) )
888 {
889 if ( f.hasGeometry() )
890 {
891 const QgsPointXY pt = f.geometry().asPoint();
892 targetPoints.push_back( pt );
893 }
894 }
895
896 if ( targetPoints.empty() )
897 {
898 throw QgsProcessingException( QObject::tr( "Input target point layer contains no valid point geometries." ) );
899 }
900
901 mOutputPath = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
902 mOutputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
903
904 calculateUpslopeArea( targetPoints, context, feedback );
905
906 QVariantMap outputs;
907 outputs.insert( u"OUTPUT"_s, mOutputPath );
908 return outputs;
909}
910
@ VectorPoint
Vector point layers.
Definition qgis.h:3752
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:401
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:4006
@ Double
Double/float values.
Definition qgis.h:4047
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.
@ Text
Plain text format.
Definition qgserror.h:40
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsGeometry geometry
Definition qgsfeature.h:66
bool hasGeometry() const
Returns true if the feature has an associated geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
virtual Q_INVOKABLE QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Represents a 2D point.
Definition qgspointxy.h:62
Abstract base class for processing algorithms.
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
Raster data container.
double value(int row, int column) const
Read a single value if type of block is numeric.
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.
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.
Creates a flat index over a QgsRasterBlock, sorted by cell values.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition qgis.h:8310