QGIS API Documentation 4.3.0-Master (45633be667c)
Loading...
Searching...
No Matches
qgsalgorithmflowconnectivity.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmflowconnectivity.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"
23
24#include <QString>
25
26using namespace Qt::StringLiterals;
27
29
30QString QgsFlowConnectivityD8Algorithm::name() const
31{
32 return u"flowconnectivity"_s;
33}
34
35QString QgsFlowConnectivityD8Algorithm::displayName() const
36{
37 return QObject::tr( "Flow connectivity" );
38}
39
40QStringList QgsFlowConnectivityD8Algorithm::tags() const
41{
42 return QObject::tr( "dem,flow,connectivity,d8,confluence,topology,hydrology,drainage" ).split( ',' );
43}
44
45QString QgsFlowConnectivityD8Algorithm::group() const
46{
47 return QObject::tr( "Raster terrain analysis" );
48}
49
50QString QgsFlowConnectivityD8Algorithm::groupId() const
51{
52 return u"rasterterrainanalysis"_s;
53}
54
55QString QgsFlowConnectivityD8Algorithm::shortDescription() const
56{
57 return QObject::tr( "Calculates the number of adjacent cells flowing directly into each grid cell using D8 flow routing." );
58}
59
60QString QgsFlowConnectivityD8Algorithm::shortHelpString() const
61{
62 return QObject::tr(
63 "This algorithm calculates deterministic 8 (D8) flow connectivity for each cell in an input elevation raster (DEM).\n\n"
64 "Output cell values represent the number of immediate 8-neighbor adjacent cells (0 to 8) whose D8 steepest downslope flow direction points directly into the cell:\n"
65 "• 0 = Ridge, crest, or spring cell receiving no incoming surface flow.\n"
66 "• 1 = Channel segment cell receiving flow from a single upstream neighbor.\n"
67 "• 2+ = Stream junction or confluence cell receiving flow from multiple converging upstream paths.\n\n"
68 "This algorithm is a port of the flow connectivity calculation from SAGA 'Channel Network and Drainage Basins' tool."
69 );
70}
71
72QList<QgsAcademicReference> QgsFlowConnectivityD8Algorithm::academicReferences() const
73{
74 const QgsAcademicReference ocallaghanReference = QgsAcademicReference::
75 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 );
76 return { ocallaghanReference };
77}
78
79QList<QgsProcessingAlgorithm::ExternalLink> QgsFlowConnectivityD8Algorithm::externalLinks() const
80{
81 return {
82 QgsProcessingAlgorithm::ExternalLink { QObject::tr( "SAGA tool source code" ), u"https://sourceforge.net/p/saga-gis/code/ci/33d1062b7120c696c9dd258378c48d86dc33560c/tree/saga-gis/src/tools/terrain_analysis/ta_channels/D8_Flow_Analysis.cpp"_s }
83 };
84}
85
86void QgsFlowConnectivityD8Algorithm::initAlgorithm( const QVariantMap & )
87{
88 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
89
90 auto outputNodataParam = std::make_unique<QgsProcessingParameterNumber>( u"NODATA"_s, QObject::tr( "Output NoData value" ), Qgis::ProcessingNumberParameterType::Integer, -9999 );
91 outputNodataParam->setHelp( QObject::tr( "The NODATA value to use in the output raster." ) );
92 outputNodataParam->setFlags( outputNodataParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
93 addParameter( outputNodataParam.release() );
94
95 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATION_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
96 creationOptsParam->setHelp( QObject::tr( "The raster creation options for the output raster. These options control things like colorimetry, compression, etc." ) );
97 creationOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
98 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
99 addParameter( creationOptsParam.release() );
100
101 auto outputParam = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT"_s, QObject::tr( "Flow direction" ) );
102 addParameter( outputParam.release() );
103}
104
105QgsProcessingAlgorithm *QgsFlowConnectivityD8Algorithm::createInstance() const
106{
107 return new QgsFlowConnectivityD8Algorithm();
108}
109
110bool QgsFlowConnectivityD8Algorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
111{
112 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u"INPUT"_s, context );
113 if ( !layer || !layer->dataProvider() )
114 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT"_s ) );
115
116 mInterface.reset( layer->dataProvider()->clone() );
117 mLayerWidth = layer->width();
118 mLayerHeight = layer->height();
119 mExtent = layer->extent();
120 mCrs = layer->crs();
121 mCellSizeX = layer->rasterUnitsPerPixelX();
122 mCellSizeY = layer->rasterUnitsPerPixelY();
123
124 return true;
125}
126
127QVariantMap QgsFlowConnectivityD8Algorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
128{
129 QGS_MARK_ALGORITHM_SOURCE
130
131 const QString creationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
132 const QString outputPath = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
133 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
134 const int outputNoData = parameterAsInt( parameters, u"NODATA"_s, context );
135
136 std::unique_ptr<QgsRasterBlock> inputBlock( mInterface->block( 1, mExtent, mLayerWidth, mLayerHeight ) );
137 if ( !inputBlock )
138 throw QgsProcessingException( QObject::tr( "Could not read input raster block." ) );
139
140 const qgssize totalCells = static_cast<qgssize>( mLayerWidth ) * mLayerHeight;
141
142 QgsProcessingMultiStepFeedback multiStepFeedback( 2, feedback );
143 multiStepFeedback.setCurrentStep( 0 );
144
145 std::vector<int8_t> d8Directions( totalCells, -1 );
146
147 // process D8 steepest gradient direction for every grid cell (matching SAGA CD8_Flow_Analysis::Get_Direction)
148 for ( int row = 0; row < mLayerHeight; ++row )
149 {
150 if ( feedback->isCanceled() )
151 return {};
152
153 multiStepFeedback.setProgress( 100.0 * static_cast<double>( row ) / mLayerHeight );
154
155 const qgssize rowOffset = static_cast<qgssize>( row ) * mLayerWidth;
156 for ( int col = 0; col < mLayerWidth; ++col )
157 {
158 const int dir = QgsRasterAnalysisUtils::steepestGradientDirection( inputBlock.get(), row, col, mCellSizeX, mCellSizeY, true, true );
159 d8Directions[rowOffset + col] = static_cast<int8_t>( dir );
160 }
161 }
162
163 auto outputBlock = std::make_unique<QgsRasterBlock>( Qgis::DataType::Int16, mLayerWidth, mLayerHeight );
164 outputBlock->setNoDataValue( outputNoData );
165
166 // pass 2: calculate incoming flow connectivity (matching SAGA CD8_Flow_Analysis::Get_Direction connectivity pass)
167 multiStepFeedback.setCurrentStep( 1 );
168 int16_t *outData = reinterpret_cast<int16_t *>( outputBlock->bits() );
169
170 for ( int row = 0; row < mLayerHeight; ++row )
171 {
172 if ( feedback->isCanceled() )
173 return {};
174
175 multiStepFeedback.setProgress( static_cast<double>( row ) / mLayerHeight );
176
177 const qgssize rowOffset = static_cast<qgssize>( row ) * mLayerWidth;
178 for ( int col = 0; col < mLayerWidth; ++col )
179 {
180 if ( inputBlock->isNoData( row, col ) )
181 {
182 outData[rowOffset + col] = outputNoData;
183 continue;
184 }
185
186 int incomingCount = 0;
187 for ( int dir = 0; dir < 8; ++dir )
188 {
189 const int oppositeDir = ( dir + 4 ) % 8;
190 int neighborCol = 0;
191 int neighborRow = 0;
192 if ( QgsRasterAnalysisUtils::neighborCellCoordinates( oppositeDir, row, col, neighborRow, neighborCol, mLayerHeight, mLayerWidth ) )
193 {
194 const qgssize neighborIdx = static_cast<qgssize>( neighborRow ) * mLayerWidth + neighborCol;
195 if ( d8Directions[neighborIdx] == dir )
196 {
197 incomingCount++;
198 }
199 }
200 }
201
202 outData[rowOffset + col] = static_cast<int16_t>( incomingCount );
203 }
204 }
205
206 auto outputWriter = std::make_unique<QgsRasterFileWriter>( outputPath );
207 outputWriter->setOutputProviderKey( u"gdal"_s );
208 if ( !creationOptions.isEmpty() )
209 {
210 outputWriter->setCreationOptions( creationOptions.split( '|' ) );
211 }
212 outputWriter->setOutputFormat( outputFormat );
213
214 std::unique_ptr<QgsRasterDataProvider> destProvider( outputWriter->createOneBandRaster( Qgis::DataType::Int16, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
215 if ( !destProvider )
216 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputPath ) );
217 if ( !destProvider->isValid() )
218 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputPath, destProvider->error().message( QgsErrorMessage::Text ) ) );
219
220 destProvider->setNoDataValue( 1, outputNoData );
221 destProvider->setEditable( true );
222
223 if ( !destProvider->writeBlock( outputBlock.get(), 1 ) )
224 {
225 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destProvider->error().summary() ) );
226 }
227
228 destProvider->setEditable( false );
229
230 QVariantMap outputs;
231 outputs.insert( u"OUTPUT"_s, outputPath );
232 return outputs;
233}
234
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:398
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:4006
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
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
virtual Q_INVOKABLE QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Abstract base class for processing algorithms.
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.
Processing feedback object for multi-step operations.
A raster layer parameter for processing algorithms.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
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.
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