Skip to content
Permalink
Browse files
Add if() function to raster calculator (#44839)
* start to work on new branch for conditional statement

* adjust the layout

* some pseudocode

* start to design the type tFunct, that should lead to the conditional statement

* modify the raw with a smart pointer

* change the test method and some other parts in the tFunct type

* complete the conditional statement option and update the test

* change evaluation method

* some optimization in the evaluation method

* minor adjustment

* minor adjustmentto test method

* add the button to the ui and some change to the code

* add a comment

* modify the parser and lexer in order to let the raster calc work with case-insensitive IF/if/If/iF

* change some parts according to the review and simplify the test method

* minor changes

* modify comment

* minor changes to enum type (tFunction)

* add some parts to test toString()  method

* add the possibility to use scalar condition in eveluationFunction() method and the corresponding test code

* update toString method

* update and optimize toString method

Co-authored-by: franc <Franc-Brs>
  • Loading branch information
Franc-Brs committed Sep 7, 2021
1 parent 5dc4138 commit d602f77a33a79c3bfac6d893589d4a65d2111993
@@ -10,6 +10,7 @@




class QgsRasterCalcNode
{
%Docstring(signature="appended")
@@ -25,7 +26,8 @@ Represents a node in a raster calculator.
tOperator,
tNumber,
tRasterRef,
tMatrix
tMatrix,
tFunction
};

enum Operator
@@ -67,6 +69,10 @@ Constructor for QgsRasterCalcNode.
QgsRasterCalcNode( double number );
QgsRasterCalcNode( QgsRasterMatrix *matrix );
QgsRasterCalcNode( Operator op, QgsRasterCalcNode *left, QgsRasterCalcNode *right );
QgsRasterCalcNode( QString functionName, QVector <QgsRasterCalcNode *> functionArgs );
%Docstring
Constructor for the tFunction type
%End
QgsRasterCalcNode( const QString &rasterName );
~QgsRasterCalcNode();

@@ -73,6 +73,8 @@ raster_band_ref_quoted \"(\\.|[^"])*\"
"<=" { return LE; }
">=" { return GE; }
"if" { return IF; }
[=><+-/*^] { return yytext[0]; }
@@ -36,6 +36,13 @@ QgsRasterCalcNode::QgsRasterCalcNode( Operator op, QgsRasterCalcNode *left, QgsR
{
}

QgsRasterCalcNode::QgsRasterCalcNode( QString functionName, QVector <QgsRasterCalcNode *> functionArgs )
: mType( tFunction )
, mFunctionName( functionName )
, mFunctionArgs( functionArgs )
{
}

QgsRasterCalcNode::QgsRasterCalcNode( const QString &rasterName )
: mType( tRasterRef )
, mRasterName( rasterName )
@@ -208,6 +215,21 @@ bool QgsRasterCalcNode::calculate( QMap<QString, QgsRasterBlock * > &rasterData,
result.setData( mMatrix->nColumns(), mMatrix->nRows(), data, result.nodataValue() );
return true;
}
else if ( mType == tFunction )
{
QVector <QgsRasterMatrix *> matrixContainer;
for ( int i = 0; i < mFunctionArgs.size(); ++i )
{
std::unique_ptr< QgsRasterMatrix > singleMatrix( new QgsRasterMatrix( result.nColumns(), result.nRows(), nullptr, result.nodataValue() ) );
if ( !mFunctionArgs.at( i ) || !mFunctionArgs.at( i )->calculate( rasterData, *singleMatrix, row ) )
{
return false;
}
matrixContainer.append( singleMatrix.release() );
}
evaluateFunction( matrixContainer, result );
return true;
}
return false;
}

@@ -360,6 +382,18 @@ QString QgsRasterCalcNode::toString( bool cStyle ) const
break;
case tMatrix:
break;
case tFunction:
if ( mFunctionName == "if" )
{
const QString argOne = mFunctionArgs.at( 0 )->toString( cStyle );
const QString argTwo = mFunctionArgs.at( 1 )->toString( cStyle );
const QString argThree = mFunctionArgs.at( 2 )->toString( cStyle );
if ( cStyle )
result = QStringLiteral( " ( %1 ) ? ( %2 ) : ( %3 ) " ).arg( argOne, argTwo, argThree );
else
result = QStringLiteral( "if( %1 , %2 , %3 )" ).arg( argOne, argTwo, argThree );
}
break;
}
return result;
}
@@ -373,6 +407,10 @@ QList<const QgsRasterCalcNode *> QgsRasterCalcNode::findNodes( const QgsRasterCa
nodeList.append( mLeft->findNodes( type ) );
if ( mRight )
nodeList.append( mRight->findNodes( type ) );

for ( QgsRasterCalcNode *node : mFunctionArgs )
nodeList.append( node->findNodes( type ) );

return nodeList;
}

@@ -417,3 +455,48 @@ QStringList QgsRasterCalcNode::cleanRasterReferences()

return rasterReferences;
}

QgsRasterMatrix QgsRasterCalcNode::evaluateFunction( const QVector<QgsRasterMatrix *> &matrixVector, QgsRasterMatrix &result ) const
{

if ( mFunctionName == "if" )
{
//scalar condition
if ( matrixVector.at( 0 )->isNumber() )
{
result = ( matrixVector.at( 0 )->data() ? * matrixVector.at( 1 ) : * matrixVector.at( 2 ) );
return result;
}
int nCols = matrixVector.at( 0 )->nColumns();
int nRows = matrixVector.at( 0 )->nRows();
int nEntries = nCols * nRows;
std::unique_ptr< double > dataResult( new double[nEntries] );
double *dataResultRawPtr = dataResult.get();

double *condition = matrixVector.at( 0 )->data();
double *firstOption = matrixVector.at( 1 )->data();
double *secondOption = matrixVector.at( 2 )->data();

bool isFirstOptionNumber = matrixVector.at( 1 )->isNumber();
bool isSecondCOptionNumber = matrixVector.at( 2 )->isNumber();
double noDataValueCondition = matrixVector.at( 0 )->nodataValue();

for ( int i = 0; i < nEntries; ++i )
{
if ( condition[i] == noDataValueCondition )
{
dataResultRawPtr[i] = result.nodataValue();
continue;
}
else if ( condition[i] != 0 )
{
dataResultRawPtr[i] = isFirstOptionNumber ? firstOption[0] : firstOption[i];
continue;
}
dataResultRawPtr[i] = isSecondCOptionNumber ? secondOption[0] : secondOption[i];
}

result.setData( nCols, nRows, dataResult.release(), result.nodataValue() );
}
return result;
}
@@ -25,6 +25,8 @@
#include <QString>
#include "qgis_analysis.h"

#include <QVector>

class QgsRasterBlock;
class QgsRasterMatrix;

@@ -42,7 +44,8 @@ class ANALYSIS_EXPORT QgsRasterCalcNode
tOperator = 1,
tNumber,
tRasterRef,
tMatrix
tMatrix,
tFunction
};

//! possible operators
@@ -85,6 +88,8 @@ class ANALYSIS_EXPORT QgsRasterCalcNode
QgsRasterCalcNode( double number );
QgsRasterCalcNode( QgsRasterMatrix *matrix );
QgsRasterCalcNode( Operator op, QgsRasterCalcNode *left, QgsRasterCalcNode *right );
//!Constructor for the tFunction type
QgsRasterCalcNode( QString functionName, QVector <QgsRasterCalcNode *> functionArgs );
QgsRasterCalcNode( const QString &rasterName );
~QgsRasterCalcNode();

@@ -143,14 +148,22 @@ class ANALYSIS_EXPORT QgsRasterCalcNode
QgsRasterCalcNode( const QgsRasterCalcNode &rh );
#endif

/**
* Calculates result of raster calculation when tFunct type is used
* \since QGIS 3.22
*/
QgsRasterMatrix evaluateFunction( const QVector<QgsRasterMatrix *> &matrixVector, QgsRasterMatrix &result ) const;

Type mType = tNumber;
QgsRasterCalcNode *mLeft = nullptr;
QgsRasterCalcNode *mRight = nullptr;
double mNumber = 0;
QString mRasterName;
QgsRasterMatrix *mMatrix = nullptr;
Operator mOperator = opNONE;

//added for the conditional statement
QString mFunctionName;
QVector <QgsRasterCalcNode *> mFunctionArgs;
};


@@ -58,6 +58,8 @@
%token<op> FUNCTION
%token<op> FUNCTION_2_ARGS

%token IF

%type <node> root
%type <node> raster_exp

@@ -81,6 +83,12 @@ root: raster_exp{}
raster_exp:
FUNCTION '(' raster_exp ')' { $$ = new QgsRasterCalcNode($1, $3, 0); joinTmpNodes($$, $3, 0);}
| FUNCTION_2_ARGS '(' raster_exp ',' raster_exp ')' { $$ = new QgsRasterCalcNode($1, $3, $5); joinTmpNodes($$, $3, $5);}
| IF '(' raster_exp ',' raster_exp ',' raster_exp ')' { QVector <QgsRasterCalcNode *> tmpVect;
tmpVect<< $3<< $5<< $7;
$$ = new QgsRasterCalcNode("if", tmpVect);
joinTmpNodes($$, $3, $5);
gTmpNodes.removeAll($7);
}
| raster_exp AND raster_exp { $$ = new QgsRasterCalcNode( QgsRasterCalcNode::opAND, $1, $3 ); joinTmpNodes($$,$1,$3); }
| raster_exp OR raster_exp { $$ = new QgsRasterCalcNode( QgsRasterCalcNode::opOR, $1, $3 ); joinTmpNodes($$,$1,$3); }
| raster_exp '=' raster_exp { $$ = new QgsRasterCalcNode( QgsRasterCalcNode::opEQ, $1, $3 ); joinTmpNodes($$,$1,$3); }
@@ -73,6 +73,7 @@ QgsRasterCalcDialog::QgsRasterCalcDialog( QgsRasterLayer *rasterLayer, QWidget *
connect( mMinButton, &QPushButton::clicked, this, &QgsRasterCalcDialog::mMinButton_clicked );
connect( mMaxButton, &QPushButton::clicked, this, &QgsRasterCalcDialog::mMaxButton_clicked );
connect( mOrButton, &QPushButton::clicked, this, &QgsRasterCalcDialog::mOrButton_clicked );
connect( mConditionalStatButton, &QPushButton::clicked, this, &QgsRasterCalcDialog::mConditionalStatButton_clicked );
connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterCalcDialog::showHelp );

if ( rasterLayer && rasterLayer->dataProvider() && rasterLayer->providerType() == QLatin1String( "gdal" ) )
@@ -539,6 +540,11 @@ void QgsRasterCalcDialog::mMaxButton_clicked()
mExpressionTextEdit->insertPlainText( QStringLiteral( " MAX ( " ) );
}

void QgsRasterCalcDialog::mConditionalStatButton_clicked()
{
mExpressionTextEdit->insertPlainText( QStringLiteral( " if ( " ) );
}

QString QgsRasterCalcDialog::quoteBandEntry( const QString &layerName )
{
// '"' -> '\\"'
@@ -100,6 +100,7 @@ class APP_EXPORT QgsRasterCalcDialog: public QDialog, private Ui::QgsRasterCalcD
void mAbsButton_clicked();
void mMinButton_clicked();
void mMaxButton_clicked();
void mConditionalStatButton_clicked();

private:
//! Sets the extent and size of the output
@@ -492,6 +492,13 @@
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QPushButton" name="mConditionalStatButton">
<property name="text">
<string notr="true">if</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

0 comments on commit d602f77

Please sign in to comment.