Skip to content

Commit ef81f3c

Browse files
committed
[FEATURE] Native reclassify raster algorithms
Adds two new native QGIS raster reclassification algorithms: - Reclassify by layer: reclassifies a raster using the ranges specified via min/max/value fields from a vector table - Reclassify by table: reclassifies a raster using a fixed table entered by users in the algorithm dialog Sponsored by SMEC/SJ
1 parent 4d3ff9a commit ef81f3c

File tree

4 files changed

+325
-0
lines changed

4 files changed

+325
-0
lines changed

src/analysis/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ SET(QGIS_ANALYSIS_SRCS
6060
processing/qgsalgorithmprojectpointcartesian.cpp
6161
processing/qgsalgorithmpromotetomultipart.cpp
6262
processing/qgsalgorithmrasterlayeruniquevalues.cpp
63+
processing/qgsalgorithmreclassifybylayer.cpp
6364
processing/qgsalgorithmremoveduplicatevertices.cpp
6465
processing/qgsalgorithmremoveholes.cpp
6566
processing/qgsalgorithmremovenullgeometry.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/***************************************************************************
2+
qgsalgorithmreclassifybylayer.cpp
3+
---------------------
4+
begin : June, 2018
5+
copyright : (C) 2018 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+
18+
#include "qgsalgorithmreclassifybylayer.h"
19+
#include "qgsgeos.h"
20+
#include "qgslogger.h"
21+
#include "qgsrasterfilewriter.h"
22+
#include "qgis.h"
23+
24+
///@cond PRIVATE
25+
26+
QString QgsReclassifyByLayerAlgorithm::name() const
27+
{
28+
return QStringLiteral( "reclassifybylayer" );
29+
}
30+
31+
QString QgsReclassifyByLayerAlgorithm::displayName() const
32+
{
33+
return QObject::tr( "Reclassify by layer" );
34+
}
35+
36+
QStringList QgsReclassifyByLayerAlgorithm::tags() const
37+
{
38+
return QObject::tr( "raster,reclassify,classes,calculator" ).split( ',' );
39+
}
40+
41+
QString QgsReclassifyByLayerAlgorithm::group() const
42+
{
43+
return QObject::tr( "Raster analysis" );
44+
}
45+
46+
QString QgsReclassifyByLayerAlgorithm::groupId() const
47+
{
48+
return QStringLiteral( "rasteranalysis" );
49+
}
50+
51+
void QgsReclassifyByLayerAlgorithm::initAlgorithm( const QVariantMap & )
52+
{
53+
addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT_RASTER" ),
54+
QObject::tr( "Raster layer" ) ) );
55+
addParameter( new QgsProcessingParameterBand( QStringLiteral( "RASTER_BAND" ),
56+
QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT_RASTER" ) ) );
57+
58+
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT_TABLE" ),
59+
QObject::tr( "Layer containing class breaks" ), QList< int >() << QgsProcessing::TypeVector ) );
60+
addParameter( new QgsProcessingParameterField( QStringLiteral( "MIN_FIELD" ),
61+
QObject::tr( "Minimum class value field" ), QVariant(), QStringLiteral( "INPUT_TABLE" ), QgsProcessingParameterField::Numeric ) );
62+
addParameter( new QgsProcessingParameterField( QStringLiteral( "MAX_FIELD" ),
63+
QObject::tr( "Maximum class value field" ), QVariant(), QStringLiteral( "INPUT_TABLE" ), QgsProcessingParameterField::Numeric ) );
64+
addParameter( new QgsProcessingParameterField( QStringLiteral( "VALUE_FIELD" ),
65+
QObject::tr( "Output value field" ), QVariant(), QStringLiteral( "INPUT_TABLE" ), QgsProcessingParameterField::Numeric ) );
66+
67+
std::unique_ptr< QgsProcessingParameterNumber > noDataValueParam = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "NO_DATA" ),
68+
QObject::tr( "Output no data value" ), QgsProcessingParameterNumber::Double, -9999 );
69+
noDataValueParam->setFlags( QgsProcessingParameterDefinition::FlagAdvanced );
70+
addParameter( noDataValueParam.release() );
71+
addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Reclassified raster" ) ) );
72+
}
73+
74+
QString QgsReclassifyByLayerAlgorithm::shortHelpString() const
75+
{
76+
return QObject::tr( "This algorithm reclassifies a raster band by assigning new class values based on the ranges specified in a vector table." );
77+
}
78+
79+
QgsReclassifyByLayerAlgorithm *QgsReclassifyByLayerAlgorithm::createInstance() const
80+
{
81+
return new QgsReclassifyByLayerAlgorithm();
82+
}
83+
84+
bool QgsReclassifyByLayerAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
85+
{
86+
QgsRasterLayer *layer = parameterAsRasterLayer( parameters, QStringLiteral( "INPUT_RASTER" ), context );
87+
mBand = parameterAsInt( parameters, QStringLiteral( "RASTER_BAND" ), context );
88+
89+
if ( !layer )
90+
throw QgsProcessingException( invalidRasterError( parameters, QStringLiteral( "INPUT_RASTER" ) ) );
91+
92+
mInterface.reset( layer->dataProvider()->clone() );
93+
mExtent = layer->extent();
94+
mCrs = layer->crs();
95+
mRasterUnitsPerPixelX = std::abs( layer->rasterUnitsPerPixelX() );
96+
mRasterUnitsPerPixelY = std::abs( layer->rasterUnitsPerPixelX() );
97+
mNbCellsXProvider = mInterface->xSize();
98+
mNbCellsYProvider = mInterface->ySize();
99+
100+
mNoDataValue = parameterAsDouble( parameters, QStringLiteral( "NO_DATA" ), context );
101+
102+
std::unique_ptr< QgsFeatureSource >tableSource( parameterAsSource( parameters, QStringLiteral( "INPUT_TABLE" ), context ) );
103+
if ( !tableSource )
104+
throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_TABLE" ) ) );
105+
106+
QString fieldMin = parameterAsString( parameters, QStringLiteral( "MIN_FIELD" ), context );
107+
mMinFieldIdx = tableSource->fields().lookupField( fieldMin );
108+
if ( mMinFieldIdx < 0 )
109+
throw QgsProcessingException( QObject::tr( "Invalid field specified for MIN_FIELD: %1" ).arg( fieldMin ) );
110+
QString fieldMax = parameterAsString( parameters, QStringLiteral( "MAX_FIELD" ), context );
111+
mMaxFieldIdx = tableSource->fields().lookupField( fieldMax );
112+
if ( mMaxFieldIdx < 0 )
113+
throw QgsProcessingException( QObject::tr( "Invalid field specified for MAX_FIELD: %1" ).arg( fieldMax ) );
114+
QString fieldValue = parameterAsString( parameters, QStringLiteral( "VALUE_FIELD" ), context );
115+
mValueFieldIdx = tableSource->fields().lookupField( fieldValue );
116+
if ( mValueFieldIdx < 0 )
117+
throw QgsProcessingException( QObject::tr( "Invalid field specified for VALUE_FIELD: %1" ).arg( fieldValue ) );
118+
119+
QgsFeatureRequest request;
120+
request.setFlags( QgsFeatureRequest::NoGeometry );
121+
request.setSubsetOfAttributes( QgsAttributeList() << mMinFieldIdx << mMaxFieldIdx << mValueFieldIdx );
122+
mTableIterator = tableSource->getFeatures( request );
123+
124+
return true;
125+
}
126+
127+
QVariantMap QgsReclassifyByLayerAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
128+
{
129+
// step 1 - build up the table of reclassification values
130+
QVector< Class > classes;
131+
QgsFeature f;
132+
while ( mTableIterator.nextFeature( f ) )
133+
{
134+
bool ok = false;
135+
double minValue = f.attribute( mMinFieldIdx ).toDouble( &ok );
136+
if ( !ok )
137+
throw QgsProcessingException( QObject::tr( "Invalid value for minimum: %1" ).arg( f.attribute( mMinFieldIdx ).toString() ) );
138+
double maxValue = f.attribute( mMaxFieldIdx ).toDouble( &ok );
139+
if ( !ok )
140+
throw QgsProcessingException( QObject::tr( "Invalid value for maximum: %1" ).arg( f.attribute( mMaxFieldIdx ).toString() ) );
141+
double value = f.attribute( mValueFieldIdx ).toDouble( &ok );
142+
if ( !ok )
143+
throw QgsProcessingException( QObject::tr( "Invalid output value: %1" ).arg( f.attribute( mValueFieldIdx ).toString() ) );
144+
145+
classes << Class( minValue, maxValue, value );
146+
}
147+
148+
const QString outputFile = parameterAsOutputLayer( parameters, QStringLiteral( "OUTPUT" ), context );
149+
QFileInfo fi( outputFile );
150+
const QString outputFormat = QgsRasterFileWriter::driverForExtension( fi.suffix() );
151+
152+
std::unique_ptr< QgsRasterFileWriter > writer = qgis::make_unique< QgsRasterFileWriter >( outputFile );
153+
writer->setOutputProviderKey( QStringLiteral( "gdal" ) );
154+
writer->setOutputFormat( outputFormat );
155+
std::unique_ptr<QgsRasterDataProvider > provider( writer->createOneBandRaster( Qgis::Float32, mNbCellsXProvider, mNbCellsYProvider, mExtent, mCrs ) );
156+
if ( !provider )
157+
throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
158+
159+
provider->setNoDataValue( 1, mNoDataValue );
160+
161+
reclassify( classes, provider.get(), feedback );
162+
163+
QVariantMap outputs;
164+
outputs.insert( QStringLiteral( "OUTPUT" ), outputFile );
165+
return outputs;
166+
}
167+
168+
void QgsReclassifyByLayerAlgorithm::reclassify( const QVector<QgsReclassifyByLayerAlgorithm::Class> &classes, QgsRasterDataProvider *destinationRaster, QgsProcessingFeedback *feedback )
169+
{
170+
int maxWidth = 4000;
171+
int maxHeight = 4000;
172+
173+
QgsRasterIterator iter( mInterface.get() );
174+
iter.setMaximumTileWidth( maxWidth );
175+
iter.setMaximumTileHeight( maxHeight );
176+
iter.startRasterRead( mBand, mNbCellsXProvider, mNbCellsYProvider, mExtent );
177+
178+
int nbBlocksWidth = std::ceil( 1.0 * mNbCellsXProvider / maxWidth );
179+
int nbBlocksHeight = std::ceil( 1.0 * mNbCellsYProvider / maxHeight );
180+
int nbBlocks = nbBlocksWidth * nbBlocksHeight;
181+
182+
int iterLeft = 0;
183+
int iterTop = 0;
184+
int iterCols = 0;
185+
int iterRows = 0;
186+
destinationRaster->setEditable( true );
187+
QgsRasterBlock *rasterBlock = nullptr;
188+
while ( iter.readNextRasterPart( mBand, iterCols, iterRows, &rasterBlock, iterLeft, iterTop ) )
189+
{
190+
feedback->setProgress( 100 * ( ( iterTop / maxHeight * nbBlocksWidth ) + iterLeft / maxWidth ) / nbBlocks );
191+
std::unique_ptr< QgsRasterBlock > reclassifiedBlock = qgis::make_unique< QgsRasterBlock >( Qgis::Float32, iterCols, iterRows );
192+
193+
for ( int row = 0; row < iterRows; row++ )
194+
{
195+
if ( feedback->isCanceled() )
196+
break;
197+
for ( int column = 0; column < iterCols; column++ )
198+
{
199+
if ( rasterBlock->isNoData( row, column ) )
200+
reclassifiedBlock->setValue( row, column, mNoDataValue );
201+
else
202+
{
203+
double value = rasterBlock->value( row, column );
204+
double newValue = reclassifyValue( classes, value );
205+
reclassifiedBlock->setValue( row, column, newValue );
206+
}
207+
}
208+
}
209+
destinationRaster->writeBlock( reclassifiedBlock.get(), 1, iterLeft, iterTop );
210+
211+
delete rasterBlock;
212+
}
213+
destinationRaster->setEditable( false );
214+
}
215+
216+
double QgsReclassifyByLayerAlgorithm::reclassifyValue( const QVector<QgsReclassifyByLayerAlgorithm::Class> &classes, double input ) const
217+
{
218+
for ( const QgsReclassifyByLayerAlgorithm::Class &c : classes )
219+
{
220+
if ( input >= c.range.min() && input < c.range.max() )
221+
return c.value;
222+
}
223+
return mNoDataValue;
224+
225+
}
226+
227+
///@endcond
228+
229+
230+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/***************************************************************************
2+
qgsalgorithmreclassifybylayer.h
3+
---------------------
4+
begin : June, 2018
5+
copyright : (C) 2018 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+
18+
#ifndef QGSALGORITHMRECLASSIFYBYLAYER_H
19+
#define QGSALGORITHMRECLASSIFYBYLAYER_H
20+
21+
#define SIP_NO_FILE
22+
23+
#include "qgis.h"
24+
#include "qgsprocessingalgorithm.h"
25+
26+
///@cond PRIVATE
27+
28+
/**
29+
* Native zonal histogram algorithm.
30+
*/
31+
class QgsReclassifyByLayerAlgorithm : public QgsProcessingAlgorithm
32+
{
33+
34+
public:
35+
36+
QgsReclassifyByLayerAlgorithm() = default;
37+
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
38+
QString name() const override;
39+
QString displayName() const override;
40+
QStringList tags() const override;
41+
QString group() const override;
42+
QString groupId() const override;
43+
QString shortHelpString() const override;
44+
QgsReclassifyByLayerAlgorithm *createInstance() const override SIP_FACTORY;
45+
46+
protected:
47+
48+
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
49+
QVariantMap processAlgorithm( const QVariantMap &parameters,
50+
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
51+
52+
53+
struct Class
54+
{
55+
Class() = default;
56+
Class( double minValue, double maxValue, double value )
57+
: range( minValue, maxValue )
58+
, value( value )
59+
{}
60+
QgsRasterRange range;
61+
double value = 0;
62+
};
63+
64+
65+
void reclassify( const QVector< Class > &classes, QgsRasterDataProvider *destinationRaster, QgsProcessingFeedback *feedback );
66+
double reclassifyValue( const QVector< Class > &classes, double input ) const;
67+
68+
private:
69+
70+
std::unique_ptr< QgsRasterInterface > mInterface;
71+
72+
int mMinFieldIdx = -1;
73+
int mMaxFieldIdx = -1;
74+
int mValueFieldIdx = -1;
75+
double mNoDataValue = -9999;
76+
77+
QgsFeatureIterator mTableIterator;
78+
int mBand = 1;
79+
QgsRectangle mExtent;
80+
QgsCoordinateReferenceSystem mCrs;
81+
double mRasterUnitsPerPixelX = 0;
82+
double mRasterUnitsPerPixelY = 0;
83+
int mNbCellsXProvider = 0;
84+
int mNbCellsYProvider = 0;
85+
86+
};
87+
88+
///@endcond PRIVATE
89+
90+
#endif // QGSALGORITHMRECLASSIFYBYLAYER_H
91+
92+

src/analysis/processing/qgsnativealgorithms.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include "qgsalgorithmprojectpointcartesian.h"
5858
#include "qgsalgorithmpromotetomultipart.h"
5959
#include "qgsalgorithmrasterlayeruniquevalues.h"
60+
#include "qgsalgorithmreclassifybylayer.h"
6061
#include "qgsalgorithmremoveduplicatevertices.h"
6162
#include "qgsalgorithmremoveholes.h"
6263
#include "qgsalgorithmremovenullgeometry.h"
@@ -164,6 +165,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
164165
addAlgorithm( new QgsPromoteToMultipartAlgorithm() );
165166
addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );
166167
addAlgorithm( new QgsAlgorithmRemoveDuplicateVertices() );
168+
addAlgorithm( new QgsReclassifyByLayerAlgorithm() );
167169
addAlgorithm( new QgsRemoveHolesAlgorithm() );
168170
addAlgorithm( new QgsRemoveNullGeometryAlgorithm() );
169171
addAlgorithm( new QgsRenameLayerAlgorithm() );

0 commit comments

Comments
 (0)