Skip to content

Commit 20dc7fb

Browse files
committedNov 22, 2016
[FEATURE] raster_statistic expression function for retrieving
raster band stats from a loaded layer Allows raster band stats (eg min, max, avg) to be used in expressions
1 parent 86ab302 commit 20dc7fb

File tree

3 files changed

+112
-0
lines changed

3 files changed

+112
-0
lines changed
 
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "raster_statistic",
3+
"type": "function",
4+
"description": "Returns statistics from a raster layer.",
5+
"arguments": [
6+
{"arg":"layer", "description":"a string, representing either a raster layer name or layer ID"},
7+
{"arg":"band", "description":"integer representing the band number from the raster layer, starting at 1"},
8+
{"arg":"property", "description":"a string corresponding to the property to return. Valid options are:<br /><ul><li>min: minimum value</li><li>max: maximum value</li><li>avg: average (mean) value</li><li>stdev: standard deviation of values</li><li>range: range of values (max - min)</li><li>sum: sum of all values from raster</li></ul>"}
9+
],
10+
"examples": [
11+
{ "expression":"raster_statistic('lc',1,'avg')", "returns":"Average value from band 1 from 'lc' raster layer"},
12+
{ "expression":"raster_statistic('ac2010',3,'min')", "returns":"Minimum value from band 3 from 'ac2010' raster layer"}
13+
]
14+
}

‎src/core/qgsexpression.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
#include "qgsmaptopixelgeometrysimplifier.h"
5555
#include "qgsmessagelog.h"
5656
#include "qgscsexception.h"
57+
#include "qgsrasterlayer.h"
58+
#include "qgsrasterdataprovider.h"
5759

5860
// from parser
5961
extern QgsExpression::Node* parseExpression( const QString& str, QString& parserErrorMsg );
@@ -3423,6 +3425,75 @@ static QVariant fcnGetLayerProperty( const QVariantList& values, const QgsExpres
34233425
return QVariant();
34243426
}
34253427

3428+
static QVariant fcnGetRasterBandStat( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
3429+
{
3430+
QString layerIdOrName = getStringValue( values.at( 0 ), parent );
3431+
3432+
//try to find a matching layer by name
3433+
QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIdOrName ); //search by id first
3434+
if ( !layer )
3435+
{
3436+
QList<QgsMapLayer *> layersByName = QgsMapLayerRegistry::instance()->mapLayersByName( layerIdOrName );
3437+
if ( !layersByName.isEmpty() )
3438+
{
3439+
layer = layersByName.at( 0 );
3440+
}
3441+
}
3442+
3443+
if ( !layer )
3444+
return QVariant();
3445+
3446+
QgsRasterLayer* rl = qobject_cast< QgsRasterLayer* >( layer );
3447+
if ( !rl )
3448+
return QVariant();
3449+
3450+
int band = getIntValue( values.at( 1 ), parent );
3451+
if ( band < 1 || band > rl->bandCount() )
3452+
{
3453+
parent->setEvalErrorString( QObject::tr( "Invalid band number %1 for layer %2" ).arg( band ).arg( layerIdOrName ) );
3454+
return QVariant();
3455+
}
3456+
3457+
QString layerProperty = getStringValue( values.at( 2 ), parent );
3458+
int stat = 0;
3459+
3460+
if ( QString::compare( layerProperty, QStringLiteral( "avg" ), Qt::CaseInsensitive ) == 0 )
3461+
stat = QgsRasterBandStats::Mean;
3462+
else if ( QString::compare( layerProperty, QStringLiteral( "stdev" ), Qt::CaseInsensitive ) == 0 )
3463+
stat = QgsRasterBandStats::StdDev;
3464+
else if ( QString::compare( layerProperty, QStringLiteral( "min" ), Qt::CaseInsensitive ) == 0 )
3465+
stat = QgsRasterBandStats::Min;
3466+
else if ( QString::compare( layerProperty, QStringLiteral( "max" ), Qt::CaseInsensitive ) == 0 )
3467+
stat = QgsRasterBandStats::Max;
3468+
else if ( QString::compare( layerProperty, QStringLiteral( "range" ), Qt::CaseInsensitive ) == 0 )
3469+
stat = QgsRasterBandStats::Range;
3470+
else if ( QString::compare( layerProperty, QStringLiteral( "sum" ), Qt::CaseInsensitive ) == 0 )
3471+
stat = QgsRasterBandStats::Sum;
3472+
else
3473+
{
3474+
parent->setEvalErrorString( QObject::tr( "Invalid raster statistic: '%1'" ).arg( layerProperty ) );
3475+
return QVariant();
3476+
}
3477+
3478+
QgsRasterBandStats stats = rl->dataProvider()->bandStatistics( band, stat );
3479+
switch ( stat )
3480+
{
3481+
case QgsRasterBandStats::Mean:
3482+
return stats.mean;
3483+
case QgsRasterBandStats::StdDev:
3484+
return stats.stdDev;
3485+
case QgsRasterBandStats::Min:
3486+
return stats.minimumValue;
3487+
case QgsRasterBandStats::Max:
3488+
return stats.maximumValue;
3489+
case QgsRasterBandStats::Range:
3490+
return stats.range;
3491+
case QgsRasterBandStats::Sum:
3492+
return stats.sum;
3493+
}
3494+
return QVariant();
3495+
}
3496+
34263497
static QVariant fcnArray( const QVariantList& values, const QgsExpressionContext*, QgsExpression* )
34273498
{
34283499
return values;
@@ -4007,6 +4078,9 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
40074078
// **General** functions
40084079

40094080
<< new StaticFunction( QStringLiteral( "layer_property" ), 2, fcnGetLayerProperty, QStringLiteral( "General" ) )
4081+
<< new StaticFunction( QStringLiteral( "raster_statistic" ), ParameterList() << Parameter( QStringLiteral( "layer" ) )
4082+
<< Parameter( QStringLiteral( "band" ) )
4083+
<< Parameter( QStringLiteral( "statistic" ) ), fcnGetRasterBandStat, QStringLiteral( "General" ) )
40104084
<< new StaticFunction( QStringLiteral( "var" ), 1, fcnGetVariable, QStringLiteral( "General" ) )
40114085

40124086
//return all attributes string for referencedColumns - this is caught by

‎tests/src/core/testqgsexpression.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "qgsmaplayerregistry.h"
3333
#include "qgsvectordataprovider.h"
3434
#include "qgsdistancearea.h"
35+
#include "qgsrasterlayer.h"
3536
#include "qgsproject.h"
3637

3738
static void _parseAndEvalExpr( int arg )
@@ -55,6 +56,7 @@ class TestQgsExpression: public QObject
5556
, mMemoryLayer( nullptr )
5657
, mAggregatesLayer( nullptr )
5758
, mChildLayer( nullptr )
59+
, mRasterLayer( nullptr )
5860
{}
5961

6062
private:
@@ -63,6 +65,7 @@ class TestQgsExpression: public QObject
6365
QgsVectorLayer* mMemoryLayer;
6466
QgsVectorLayer* mAggregatesLayer;
6567
QgsVectorLayer* mChildLayer;
68+
QgsRasterLayer* mRasterLayer;
6669

6770
private slots:
6871

@@ -94,6 +97,12 @@ class TestQgsExpression: public QObject
9497
mPointsLayer->setMaximumScale( 500 );
9598
mPointsLayer->setMinimumScale( 1000 );
9699

100+
QString rasterFileName = testDataDir + "tenbytenraster.asc";
101+
QFileInfo rasterFileInfo( rasterFileName );
102+
mRasterLayer = new QgsRasterLayer( rasterFileInfo.filePath(),
103+
rasterFileInfo.completeBaseName() );
104+
QgsMapLayerRegistry::instance()->addMapLayer( mRasterLayer );
105+
97106
// test memory layer for get_feature tests
98107
mMemoryLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:string" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
99108
QVERIFY( mMemoryLayer->isValid() );
@@ -1039,6 +1048,21 @@ class TestQgsExpression: public QObject
10391048
QTest::newRow( "layer_property storage_type" ) << QStringLiteral( "layer_property('%1','storage_type')" ).arg( mPointsLayer->name() ) << false << QVariant( "ESRI Shapefile" );
10401049
QTest::newRow( "layer_property geometry_type" ) << QStringLiteral( "layer_property('%1','geometry_type')" ).arg( mPointsLayer->name() ) << false << QVariant( "Point" );
10411050

1051+
// raster_statistic tests
1052+
QTest::newRow( "raster_statistic no layer" ) << "raster_statistic('',1,'min')" << false << QVariant();
1053+
QTest::newRow( "raster_statistic bad layer" ) << "raster_statistic('bad',1,'min')" << false << QVariant();
1054+
QTest::newRow( "raster_statistic bad band" ) << QStringLiteral( "raster_statistic('%1',0,'min')" ).arg( mRasterLayer->name() ) << true << QVariant();
1055+
QTest::newRow( "raster_statistic bad band 2" ) << QStringLiteral( "raster_statistic('%1',100,'min')" ).arg( mRasterLayer->name() ) << true << QVariant();
1056+
QTest::newRow( "raster_statistic no property" ) << QStringLiteral( "raster_statistic('%1',1,'')" ).arg( mRasterLayer->name() ) << true << QVariant();
1057+
QTest::newRow( "raster_statistic bad property" ) << QStringLiteral( "raster_statistic('%1',1,'bad')" ).arg( mRasterLayer->name() ) << true << QVariant();
1058+
QTest::newRow( "raster_statistic min by id" ) << QStringLiteral( "raster_statistic('%1',1,'min')" ).arg( mRasterLayer->id() ) << false << QVariant( 0.0 );
1059+
QTest::newRow( "raster_statistic min name" ) << QStringLiteral( "raster_statistic('%1',1,'min')" ).arg( mRasterLayer->name() ) << false << QVariant( 0.0 );
1060+
QTest::newRow( "raster_statistic max" ) << QStringLiteral( "raster_statistic('%1',1,'max')" ).arg( mRasterLayer->id() ) << false << QVariant( 9.0 );
1061+
QTest::newRow( "raster_statistic avg" ) << QStringLiteral( "round(10*raster_statistic('%1',1,'avg'))" ).arg( mRasterLayer->id() ) << false << QVariant( 45 );
1062+
QTest::newRow( "raster_statistic stdev" ) << QStringLiteral( "round(100*raster_statistic('%1',1,'stdev'))" ).arg( mRasterLayer->id() ) << false << QVariant( 287 );
1063+
QTest::newRow( "raster_statistic range" ) << QStringLiteral( "raster_statistic('%1',1,'range')" ).arg( mRasterLayer->id() ) << false << QVariant( 9.0 );
1064+
QTest::newRow( "raster_statistic sum" ) << QStringLiteral( "round(raster_statistic('%1',1,'sum'))" ).arg( mRasterLayer->id() ) << false << QVariant( 450 );
1065+
10421066
//test conversions to bool
10431067
QTest::newRow( "feature to bool false" ) << QStringLiteral( "case when get_feature('none','none',499) then true else false end" ) << false << QVariant( false );
10441068
QTest::newRow( "feature to bool true" ) << QStringLiteral( "case when get_feature('test','col1',10) then true else false end" ) << false << QVariant( true );

1 commit comments

Comments
 (1)

nirvn commented on Nov 22, 2016

@nirvn
Contributor

That's a really useful function, thanks. It'd be really cool for it to support an optional 4th parameter that would take a geometry (point, line, or polygon) to limit statistics to pixels overlapping the given geometry.

Please sign in to comment.