24QString QgsRasterDtmSlopeBasedFilterAlgorithm::name()
const
26 return QStringLiteral(
"dtmslopebasedfilter" );
29QString QgsRasterDtmSlopeBasedFilterAlgorithm::displayName()
const
31 return QObject::tr(
"DTM filter (slope-based)" );
34QStringList QgsRasterDtmSlopeBasedFilterAlgorithm::tags()
const
36 return QObject::tr(
"dem,filter,slope,dsm,dtm,terrain" ).split(
',' );
39QString QgsRasterDtmSlopeBasedFilterAlgorithm::group()
const
41 return QObject::tr(
"Raster terrain analysis" );
44QString QgsRasterDtmSlopeBasedFilterAlgorithm::groupId()
const
46 return QStringLiteral(
"rasterterrainanalysis" );
49QString QgsRasterDtmSlopeBasedFilterAlgorithm::shortHelpString()
const
51 return QObject::tr(
"This algorithm can be used to filter a digital elevation model in order to classify its cells into ground and object (non-ground) cells.\n\n"
52 "The tool uses concepts as described by Vosselman (2000) and is based on the assumption that a large height difference between two nearby "
53 "cells is unlikely to be caused by a steep slope in the terrain. The probability that the higher cell might be non-ground increases when "
54 "the distance between the two cells decreases. Therefore the filter defines a maximum height difference (<i>dz_max</i>) between two cells as a "
55 "function of the distance (<i>d</i>) between the cells (<i>dz_max( d ) = d</i>).\n\n"
56 "A cell is classified as terrain if there is no cell within the kernel radius to which the height difference is larger than the allowed "
57 "maximum height difference at the distance between these two cells.\n\n"
58 "The approximate terrain slope (<i>s</i>) parameter is used to modify the filter function to match the overall slope in the study "
59 "area (<i>dz_max( d ) = d * s</i>).\n\n"
60 "A 5 % confidence interval (<i>ci = 1.65 * sqrt( 2 * stddev )</i>) may be used to modify the filter function even further by either "
61 "relaxing (<i>dz_max( d ) = d * s + ci</i>) or amplifying (<i>dz_max( d ) = d * s - ci</i>) the filter criterium.\n\n"
62 "References: Vosselman, G. (2000): Slope based filtering of laser altimetry data. IAPRS, Vol. XXXIII, Part B3, Amsterdam, The Netherlands, 935-942\n\n"
63 "This algorithm is a port of the SAGA 'DTM Filter (slope-based)' tool." );
66void QgsRasterDtmSlopeBasedFilterAlgorithm::initAlgorithm(
const QVariantMap & )
70 addParameter(
new QgsProcessingParameterBand( QStringLiteral(
"BAND" ), QObject::tr(
"Band number" ), 1, QStringLiteral(
"INPUT" ) ) );
73 radiusParam->setHelp( QObject::tr(
"The radius of the filter kernel (in pixels). Must be large enough to reach ground cells next to non-ground objects." ) );
74 addParameter( radiusParam.release() );
76 auto terrainSlopeParam = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral(
"TERRAIN_SLOPE" ), QObject::tr(
"Terrain slope (%, pixel size/vertical units)" ),
Qgis::ProcessingNumberParameterType::Double, 30,
false, 0, 1000 );
77 terrainSlopeParam->setHelp( QObject::tr(
"The approximate terrain slope in %. The terrain slope must be adjusted to account for the ratio of height units vs raster pixel dimensions. Used to relax the filter criterium in steeper terrain." ) );
78 addParameter( terrainSlopeParam.release() );
80 auto filterModificationParam = std::make_unique<QgsProcessingParameterEnum>( QStringLiteral(
"FILTER_MODIFICATION" ), QObject::tr(
"Filter modification" ), QStringList { QObject::tr(
"None" ), QObject::tr(
"Relax filter" ), QObject::tr(
"Amplify" ) },
false, 0 );
81 filterModificationParam->setHelp( QObject::tr(
"Choose whether to apply the filter kernel without modification or to use a confidence interval to relax or amplify the height criterium." ) );
82 addParameter( filterModificationParam.release() );
85 stDevParam->setHelp( QObject::tr(
"The standard deviation used to calculate a 5% confidence interval applied to the height threshold." ) );
86 addParameter( stDevParam.release() );
90 auto createOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral(
"CREATE_OPTIONS" ), QObject::tr(
"Creation options" ), QVariant(),
false,
true );
91 createOptsParam->setMetadata( QVariantMap( { { QStringLiteral(
"widget_wrapper" ), QVariantMap( { { QStringLiteral(
"widget_type" ), QStringLiteral(
"rasteroptions" ) } } ) } } ) );
93 addParameter( createOptsParam.release() );
95 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( QStringLiteral(
"CREATION_OPTIONS" ), QObject::tr(
"Creation options" ), QVariant(),
false,
true );
96 creationOptsParam->setMetadata( QVariantMap( { { QStringLiteral(
"widget_wrapper" ), QVariantMap( { { QStringLiteral(
"widget_type" ), QStringLiteral(
"rasteroptions" ) } } ) } } ) );
98 addParameter( creationOptsParam.release() );
100 auto outputLayerGroundParam = std::make_unique<QgsProcessingParameterRasterDestination>( QStringLiteral(
"OUTPUT_GROUND" ), QObject::tr(
"Output layer (ground)" ), QVariant(),
true,
true );
101 outputLayerGroundParam->setHelp( QObject::tr(
"The filtered DEM containing only cells classified as ground." ) );
102 addParameter( outputLayerGroundParam.release() );
104 auto outputLayerNonGroundParam = std::make_unique<QgsProcessingParameterRasterDestination>( QStringLiteral(
"OUTPUT_NONGROUND" ), QObject::tr(
"Output layer (non-ground objects)" ), QVariant(),
true,
false );
105 outputLayerNonGroundParam->setHelp( QObject::tr(
"The non-ground objects removed by the filter." ) );
106 addParameter( outputLayerNonGroundParam.release() );
109QgsRasterDtmSlopeBasedFilterAlgorithm *QgsRasterDtmSlopeBasedFilterAlgorithm::createInstance()
const
111 return new QgsRasterDtmSlopeBasedFilterAlgorithm();
116 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral(
"INPUT" ), context );
120 const int band = parameterAsInt( parameters, QStringLiteral(
"BAND" ), context );
122 mBand = parameterAsInt( parameters, QStringLiteral(
"BAND" ), context );
123 if ( mBand < 1 || mBand > layer->
bandCount() )
124 throw QgsProcessingException( QObject::tr(
"Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->
bandCount() ) );
128 mLayerWidth = layer->
width();
129 mLayerHeight = layer->
height();
130 mExtent = layer->
extent();
141 QString creationOptions = parameterAsString( parameters, QStringLiteral(
"CREATION_OPTIONS" ), context ).trimmed();
143 const QString optionsString = parameterAsString( parameters, QStringLiteral(
"CREATE_OPTIONS" ), context );
144 if ( !optionsString.isEmpty() )
145 creationOptions = optionsString;
147 const QString groundOutputFile = parameterAsOutputLayer( parameters, QStringLiteral(
"OUTPUT_GROUND" ), context );
148 std::unique_ptr<QgsRasterFileWriter> groundWriter;
149 std::unique_ptr<QgsRasterDataProvider> groundDestProvider;
151 if ( !groundOutputFile.isEmpty() )
153 const QFileInfo fi( groundOutputFile );
156 groundWriter = std::make_unique<QgsRasterFileWriter>( groundOutputFile );
157 groundWriter->setOutputProviderKey( QStringLiteral(
"gdal" ) );
158 if ( !creationOptions.isEmpty() )
160 groundWriter->setCreationOptions( creationOptions.split(
'|' ) );
162 groundWriter->setOutputFormat( outputFormat );
164 groundDestProvider.reset( groundWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
166 if ( !groundDestProvider )
167 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( groundOutputFile ) );
168 if ( !groundDestProvider->isValid() )
171 groundDestProvider->setNoDataValue( 1, mNoData );
172 groundDestProvider->setEditable(
true );
175 const QString nonGroundOutputFile = parameterAsOutputLayer( parameters, QStringLiteral(
"OUTPUT_NONGROUND" ), context );
176 std::unique_ptr<QgsRasterFileWriter> nonGroundWriter;
177 std::unique_ptr<QgsRasterDataProvider> nonGroundDestProvider;
179 if ( !nonGroundOutputFile.isEmpty() )
181 const QFileInfo fi( groundOutputFile );
184 nonGroundWriter = std::make_unique<QgsRasterFileWriter>( nonGroundOutputFile );
185 nonGroundWriter->setOutputProviderKey( QStringLiteral(
"gdal" ) );
186 if ( !creationOptions.isEmpty() )
188 nonGroundWriter->setCreationOptions( creationOptions.split(
'|' ) );
190 nonGroundWriter->setOutputFormat( outputFormat );
192 nonGroundDestProvider.reset( nonGroundWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
194 if ( !nonGroundDestProvider )
195 throw QgsProcessingException( QObject::tr(
"Could not create raster output: %1" ).arg( nonGroundOutputFile ) );
196 if ( !nonGroundDestProvider->isValid() )
199 nonGroundDestProvider->setNoDataValue( 1, mNoData );
200 nonGroundDestProvider->setEditable(
true );
205 const int numBlocksX =
static_cast<int>( std::ceil( 1.0 * mLayerWidth / blockWidth ) );
206 const int numBlocksY =
static_cast<int>( std::ceil( 1.0 * mLayerHeight / blockHeight ) );
207 const int numBlocks = numBlocksX * numBlocksY;
209 const int radius = parameterAsInt( parameters, QStringLiteral(
"RADIUS" ), context );
211 const double terrainSlopePercent = parameterAsDouble( parameters, QStringLiteral(
"TERRAIN_SLOPE" ), context ) / 100;
212 const int filterModification = parameterAsEnum( parameters, QStringLiteral(
"FILTER_MODIFICATION" ), context );
213 const double standardDeviation = parameterAsDouble( parameters, QStringLiteral(
"STANDARD_DEVIATION" ), context );
216 QVector<double> kernel;
217 kernel.reserve( ( radius * 2 ) * ( radius * 2 ) );
219 for (
int y = -radius; y <= radius; y++ )
221 for (
int x = -radius; x <= radius; x++ )
223 const double distance = std::sqrt( x * x + y * y );
224 if ( distance < radius )
227 kernel.push_back( x );
228 kernel.push_back( y );
229 switch ( filterModification )
232 kernel.push_back( distance * terrainSlopePercent );
236 kernel.push_back( distance * terrainSlopePercent + 1.65 * std::sqrt( 2 * standardDeviation ) );
241 const double dz = distance * terrainSlopePercent - 1.65 * std::sqrt( 2 * standardDeviation );
242 kernel.push_back( dz > 0 ? dz : 0 );
251 iter.startRasterRead( 1, mLayerWidth, mLayerHeight, mExtent );
263 std::unique_ptr<QgsRasterBlock> inputBlock;
265 while ( iter.readNextRasterPart( 1, iterCols, iterRows, inputBlock, iterLeft, iterTop, &blockExtent, &tileCols, &tileRows, &tileLeft, &tileTop ) )
267 std::unique_ptr<QgsRasterBlock> outputGroundBlock;
268 if ( groundDestProvider )
269 outputGroundBlock = std::make_unique<QgsRasterBlock>( mDataType, tileCols, tileRows );
271 std::unique_ptr<QgsRasterBlock> outputNonGroundBlock;
272 if ( nonGroundDestProvider )
273 outputNonGroundBlock = std::make_unique<QgsRasterBlock>( mDataType, tileCols, tileRows );
275 double baseProgress =
static_cast<double>( blockIndex ) / numBlocks;
281 const int tileBoundaryLeft = tileLeft - iterLeft;
282 const int tileBoundaryTop = tileTop - iterTop;
284 const double rowProgressStep = 1.0 / numBlocks / tileRows;
285 double rowProgress = 0;
286 for (
int row = tileBoundaryTop; row < tileBoundaryTop + tileRows; row++ )
291 feedback->
setProgress( 100.0 * ( baseProgress + rowProgress ) );
292 rowProgress += rowProgressStep;
294 for (
int col = tileBoundaryLeft; col < tileBoundaryLeft + tileCols; col++ )
299 bool isNoData =
false;
300 const double val = inputBlock->valueAndNoData( row, col, isNoData );
303 if ( outputGroundBlock )
304 outputGroundBlock->setValue( row - tileBoundaryTop, col - tileBoundaryLeft, mNoData );
305 if ( outputNonGroundBlock )
306 outputNonGroundBlock->setValue( row - tileBoundaryTop, col - tileBoundaryLeft, mNoData );
310 bool nonGround =
false;
311 const double *kernelData = kernel.constData();
312 for (
int i = 0; i < kernelSize; ++i )
314 const int dx =
static_cast<int>( *kernelData++ );
315 const int dy =
static_cast<int>( *kernelData++ );
316 const double distance = *kernelData++;
317 const int rCol = col + dx;
318 const int rRow = row + dy;
319 if ( rCol >= 0 && ( rCol < ( iterLeft + iterCols ) ) && rRow >= 0 && ( rRow < ( iterTop + iterRows ) ) )
321 bool otherIsNoData =
false;
322 const double otherVal = inputBlock->valueAndNoData( rRow, rCol, otherIsNoData );
323 if ( !otherIsNoData )
325 const double dz = val - otherVal;
326 if ( dz > 0 && dz > distance )
336 if ( outputGroundBlock )
337 outputGroundBlock->setValue( row - tileBoundaryTop, col - tileBoundaryLeft, mNoData );
338 if ( outputNonGroundBlock )
339 outputNonGroundBlock->setValue( row - tileBoundaryTop, col - tileBoundaryLeft, val );
343 if ( outputGroundBlock )
344 outputGroundBlock->setValue( row - tileBoundaryTop, col - tileBoundaryLeft, val );
345 if ( outputNonGroundBlock )
346 outputNonGroundBlock->setValue( row - tileBoundaryTop, col - tileBoundaryLeft, mNoData );
351 if ( groundDestProvider )
353 if ( !groundDestProvider->writeBlock( outputGroundBlock.get(), mBand, tileLeft, tileTop ) )
355 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( groundDestProvider->error().summary() ) );
358 if ( nonGroundDestProvider )
360 if ( !nonGroundDestProvider->writeBlock( outputNonGroundBlock.get(), mBand, tileLeft, tileTop ) )
362 throw QgsProcessingException( QObject::tr(
"Could not write raster block: %1" ).arg( nonGroundDestProvider->error().summary() ) );
366 if ( groundDestProvider )
367 groundDestProvider->setEditable(
false );
368 if ( nonGroundDestProvider )
369 nonGroundDestProvider->setEditable(
false );
372 outputs.insert( QStringLiteral(
"OUTPUT_GROUND" ), groundOutputFile );
373 outputs.insert( QStringLiteral(
"OUTPUT_NONGROUND" ), nonGroundOutputFile );
@ Hidden
Parameter is hidden and should not be shown to users.
@ 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.
A raster band parameter for Processing algorithms.
A raster layer parameter for processing algorithms.
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.
Iterator for sequentially processing raster cells.
static const int DEFAULT_MAXIMUM_TILE_WIDTH
Default maximum tile width.
static const int DEFAULT_MAXIMUM_TILE_HEIGHT
Default maximum tile height.
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.
A rectangle specified with double values.