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