From d730c97f46e4eeff0ef5ad5d577e33c48d0cef2a Mon Sep 17 00:00:00 2001
From: Alessandro Pasotti <elpaso@itopen.it>
Date: Thu, 26 Sep 2019 18:00:09 +0200
Subject: [PATCH] Fix multiple raster calc issues

Fixes #32023 Raster calculator change sign does not work when OpenCL is on
Fixes #32025 QGIS Raster Calculator outputs nodata only rasters

Bonus: three new operators with full test coverage
- ABS
- MIN
- MAX
---
 .../raster/qgsrastercalcnode.sip.in           |   3 +
 .../raster/qgsrastermatrix.sip.in             |   8 +-
 src/analysis/raster/qgsrastercalclexer.ll     |   3 +
 src/analysis/raster/qgsrastercalcnode.cpp     |  44 +++-
 src/analysis/raster/qgsrastercalcnode.h       |   3 +
 src/analysis/raster/qgsrastercalcparser.yy    |   1 +
 src/analysis/raster/qgsrastercalculator.cpp   |   4 +-
 src/analysis/raster/qgsrastermatrix.cpp       |  23 ++
 src/analysis/raster/qgsrastermatrix.h         |   8 +-
 src/app/qgsrastercalcdialog.cpp               |  18 ++
 src/app/qgsrastercalcdialog.h                 |   3 +
 src/ui/qgsrastercalcdialogbase.ui             | 205 ++++++++++--------
 .../src/analysis/testqgsrastercalculator.cpp  |  68 ++++--
 13 files changed, 273 insertions(+), 118 deletions(-)

diff --git a/python/analysis/auto_generated/raster/qgsrastercalcnode.sip.in b/python/analysis/auto_generated/raster/qgsrastercalcnode.sip.in
index 61eb0f3d07e..f6e01da8a28 100644
--- a/python/analysis/auto_generated/raster/qgsrastercalcnode.sip.in
+++ b/python/analysis/auto_generated/raster/qgsrastercalcnode.sip.in
@@ -50,6 +50,9 @@ class QgsRasterCalcNode
       opSIGN,
       opLOG,
       opLOG10,
+      opABS,
+      opMAX,
+      opMIN,
       opNONE,
     };
 
diff --git a/python/analysis/auto_generated/raster/qgsrastermatrix.sip.in b/python/analysis/auto_generated/raster/qgsrastermatrix.sip.in
index cc5c9bbc556..d492e6d1f83 100644
--- a/python/analysis/auto_generated/raster/qgsrastermatrix.sip.in
+++ b/python/analysis/auto_generated/raster/qgsrastermatrix.sip.in
@@ -31,7 +31,9 @@ class QgsRasterMatrix
       opGE,
       opLE,
       opAND,
-      opOR
+      opOR,
+      opMIN,
+      opMAX
     };
 
     enum OneArgOperator
@@ -46,6 +48,7 @@ class QgsRasterMatrix
       opSIGN,
       opLOG,
       opLOG10,
+      opABS,
     };
 
     QgsRasterMatrix();
@@ -91,6 +94,8 @@ Subtracts another matrix from this one
     bool lesserEqual( const QgsRasterMatrix &other );
     bool logicalAnd( const QgsRasterMatrix &other );
     bool logicalOr( const QgsRasterMatrix &other );
+    bool max( const QgsRasterMatrix &other );
+    bool min( const QgsRasterMatrix &other );
 
     bool squareRoot();
     bool sinus();
@@ -102,6 +107,7 @@ Subtracts another matrix from this one
     bool changeSign();
     bool log();
     bool log10();
+    bool absoluteValue();
 
 };
 
diff --git a/src/analysis/raster/qgsrastercalclexer.ll b/src/analysis/raster/qgsrastercalclexer.ll
index a54b40b2b36..fcc753f625f 100644
--- a/src/analysis/raster/qgsrastercalclexer.ll
+++ b/src/analysis/raster/qgsrastercalclexer.ll
@@ -62,6 +62,9 @@ raster_band_ref_quoted  \"(\\.|[^"])*\"
 "atan" { rasterlval.op = QgsRasterCalcNode::opATAN; return FUNCTION;}
 "ln" { rasterlval.op = QgsRasterCalcNode::opLOG; return FUNCTION;}
 "log10" { rasterlval.op = QgsRasterCalcNode::opLOG10; return FUNCTION;}
+"abs" { rasterlval.op = QgsRasterCalcNode::opABS; return FUNCTION;}
+"min" { rasterlval.op = QgsRasterCalcNode::opMIN; return FUNCTION;}
+"max" { rasterlval.op = QgsRasterCalcNode::opMAX; return FUNCTION;}
 
 "AND" { return AND; }
 "OR" { return OR; }
diff --git a/src/analysis/raster/qgsrastercalcnode.cpp b/src/analysis/raster/qgsrastercalcnode.cpp
index 78354f71d6b..bd06d3ed1f0 100644
--- a/src/analysis/raster/qgsrastercalcnode.cpp
+++ b/src/analysis/raster/qgsrastercalcnode.cpp
@@ -87,9 +87,8 @@ bool QgsRasterCalcNode::calculate( QMap<QString, QgsRasterBlock * > &rasterData,
   }
   else if ( mType == tOperator )
   {
-    QgsRasterMatrix leftMatrix, rightMatrix;
-    leftMatrix.setNodataValue( result.nodataValue() );
-    rightMatrix.setNodataValue( result.nodataValue() );
+    QgsRasterMatrix leftMatrix( result.nColumns(), result.nRows(), nullptr, result.nodataValue() );
+    QgsRasterMatrix rightMatrix( result.nColumns(), result.nRows(), nullptr, result.nodataValue() );
 
     if ( !mLeft || !mLeft->calculate( rasterData, leftMatrix, row ) )
     {
@@ -141,6 +140,12 @@ bool QgsRasterCalcNode::calculate( QMap<QString, QgsRasterBlock * > &rasterData,
       case opOR:
         leftMatrix.logicalOr( rightMatrix );
         break;
+      case opMIN:
+        leftMatrix.min( rightMatrix );
+        break;
+      case opMAX:
+        leftMatrix.max( rightMatrix );
+        break;
       case opSQRT:
         leftMatrix.squareRoot();
         break;
@@ -171,6 +176,9 @@ bool QgsRasterCalcNode::calculate( QMap<QString, QgsRasterBlock * > &rasterData,
       case opLOG10:
         leftMatrix.log10();
         break;
+      case opABS:
+        leftMatrix.absoluteValue();
+        break;
       default:
         return false;
     }
@@ -181,9 +189,10 @@ bool QgsRasterCalcNode::calculate( QMap<QString, QgsRasterBlock * > &rasterData,
   }
   else if ( mType == tNumber )
   {
-    double *data = new double[1];
-    data[0] = mNumber;
-    result.setData( 1, 1, data, result.nodataValue() );
+    size_t nEntries = static_cast<size_t>( result.nColumns() * result.nRows() );
+    std::vector<double> *data = new  std::vector<double>( nEntries );
+    std::fill( std::begin( *data ), std::end( *data ), mNumber );
+    result.setData( result.nColumns(), 1, data->data(), result.nodataValue() );
     return true;
   }
   else if ( mType == tMatrix )
@@ -219,9 +228,11 @@ QString QgsRasterCalcNode::toString( bool cStyle ) const
           result = QStringLiteral( "( %1 + %2 )" ).arg( left ).arg( right );
           break;
         case opMINUS:
-        case opSIGN:
           result = QStringLiteral( "( %1 - %2 )" ).arg( left ).arg( right );
           break;
+        case opSIGN:
+          result = QStringLiteral( "-%1" ).arg( left );
+          break;
         case opMUL:
           result = QStringLiteral( "%1 * %2" ).arg( left ).arg( right );
           break;
@@ -309,6 +320,25 @@ QString QgsRasterCalcNode::toString( bool cStyle ) const
         case opLOG10:
           result = QStringLiteral( "log10( %1 )" ).arg( left );
           break;
+        case opABS:
+          if ( cStyle )
+            result = QStringLiteral( "fabs( %1 )" ).arg( left );
+          else
+            // Call the floating point version
+            result = QStringLiteral( "abs( %1 )" ).arg( left );
+          break;
+        case opMIN:
+          if ( cStyle )
+            result = QStringLiteral( "min( ( float ) ( %1 ), ( float ) ( %2 ) )" ).arg( left ).arg( right );
+          else
+            result = QStringLiteral( "min( %1, %2 )" ).arg( left ).arg( right );
+          break;
+        case opMAX:
+          if ( cStyle )
+            result = QStringLiteral( "max( ( float ) ( %1 ), ( float ) ( %2 ) )" ).arg( left ).arg( right );
+          else
+            result = QStringLiteral( "max( %1, %2 )" ).arg( left ).arg( right );
+          break;
         case opNONE:
           break;
       }
diff --git a/src/analysis/raster/qgsrastercalcnode.h b/src/analysis/raster/qgsrastercalcnode.h
index 27bd0ccee38..d3655fe222f 100644
--- a/src/analysis/raster/qgsrastercalcnode.h
+++ b/src/analysis/raster/qgsrastercalcnode.h
@@ -70,6 +70,9 @@ class ANALYSIS_EXPORT QgsRasterCalcNode
       opSIGN,       // change sign
       opLOG,
       opLOG10,
+      opABS,
+      opMAX,
+      opMIN,
       opNONE,
     };
 
diff --git a/src/analysis/raster/qgsrastercalcparser.yy b/src/analysis/raster/qgsrastercalcparser.yy
index fed84a45114..c598aaa9eb9 100644
--- a/src/analysis/raster/qgsrastercalcparser.yy
+++ b/src/analysis/raster/qgsrastercalcparser.yy
@@ -79,6 +79,7 @@ root: raster_exp{}
 
 raster_exp:
   FUNCTION '(' raster_exp ')'   { $$ = new QgsRasterCalcNode($1, $3, 0); joinTmpNodes($$, $3, 0);}
+  | FUNCTION '(' raster_exp ',' raster_exp ')' { $$ = new QgsRasterCalcNode($1, $3, $5); joinTmpNodes($$, $3, $5);}
   | 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); }
diff --git a/src/analysis/raster/qgsrastercalculator.cpp b/src/analysis/raster/qgsrastercalculator.cpp
index 14d3303b133..7806587b223 100644
--- a/src/analysis/raster/qgsrastercalculator.cpp
+++ b/src/analysis/raster/qgsrastercalculator.cpp
@@ -220,8 +220,8 @@ QgsRasterCalculator::Result QgsRasterCalculator::processCalculation( QgsFeedback
         }
       }
 
-      QgsRasterMatrix resultMatrix;
-      resultMatrix.setNodataValue( outputNodataValue );
+      // 1 row X mNumOutputColumns matrix
+      QgsRasterMatrix resultMatrix( mNumOutputColumns, 1, nullptr, outputNodataValue );
 
       _rasterData.clear();
       for ( const auto &layerRef : inputBlocks )
diff --git a/src/analysis/raster/qgsrastermatrix.cpp b/src/analysis/raster/qgsrastermatrix.cpp
index 7cedc9112d7..544d2a24d96 100644
--- a/src/analysis/raster/qgsrastermatrix.cpp
+++ b/src/analysis/raster/qgsrastermatrix.cpp
@@ -18,6 +18,7 @@
 #include "qgsrastermatrix.h"
 #include <cstring>
 #include <cmath>
+#include<algorithm>
 
 QgsRasterMatrix::QgsRasterMatrix( int nCols, int nRows, double *data, double nodataValue )
   : mColumns( nCols )
@@ -132,6 +133,16 @@ bool QgsRasterMatrix::logicalOr( const QgsRasterMatrix &other )
   return twoArgumentOperation( opOR, other );
 }
 
+bool QgsRasterMatrix::max( const QgsRasterMatrix &other )
+{
+  return twoArgumentOperation( opMAX, other );
+}
+
+bool QgsRasterMatrix::min( const QgsRasterMatrix &other )
+{
+  return twoArgumentOperation( opMIN, other );
+}
+
 bool QgsRasterMatrix::squareRoot()
 {
   return oneArgumentOperation( opSQRT );
@@ -182,6 +193,11 @@ bool QgsRasterMatrix::log10()
   return oneArgumentOperation( opLOG10 );
 }
 
+bool QgsRasterMatrix::absoluteValue()
+{
+  return oneArgumentOperation( opABS );
+}
+
 bool QgsRasterMatrix::oneArgumentOperation( OneArgOperator op )
 {
   if ( !mData )
@@ -249,6 +265,9 @@ bool QgsRasterMatrix::oneArgumentOperation( OneArgOperator op )
             mData[i] = ::log10( value );
           }
           break;
+        case opABS:
+          mData[i] = ::fabs( value );
+          break;
       }
     }
   }
@@ -299,6 +318,10 @@ double QgsRasterMatrix::calculateTwoArgumentOp( TwoArgOperator op, double arg1,
       return ( arg1 && arg2 ? 1.0 : 0.0 );
     case opOR:
       return ( arg1 || arg2 ? 1.0 : 0.0 );
+    case opMAX:
+      return std::max( arg1, arg2 );
+    case opMIN:
+      return std::min( arg1, arg2 );
   }
   return mNodataValue;
 }
diff --git a/src/analysis/raster/qgsrastermatrix.h b/src/analysis/raster/qgsrastermatrix.h
index 6b92f760fdd..9df4f0c2aa6 100644
--- a/src/analysis/raster/qgsrastermatrix.h
+++ b/src/analysis/raster/qgsrastermatrix.h
@@ -43,7 +43,9 @@ class ANALYSIS_EXPORT QgsRasterMatrix
       opGE,         // >=
       opLE,         // <=
       opAND,
-      opOR
+      opOR,
+      opMIN,
+      opMAX
     };
 
     enum OneArgOperator
@@ -58,6 +60,7 @@ class ANALYSIS_EXPORT QgsRasterMatrix
       opSIGN,
       opLOG,
       opLOG10,
+      opABS,
     };
 
     //! Takes ownership of data array
@@ -108,6 +111,8 @@ class ANALYSIS_EXPORT QgsRasterMatrix
     bool lesserEqual( const QgsRasterMatrix &other );
     bool logicalAnd( const QgsRasterMatrix &other );
     bool logicalOr( const QgsRasterMatrix &other );
+    bool max( const QgsRasterMatrix &other );
+    bool min( const QgsRasterMatrix &other );
 
     bool squareRoot();
     bool sinus();
@@ -119,6 +124,7 @@ class ANALYSIS_EXPORT QgsRasterMatrix
     bool changeSign();
     bool log();
     bool log10();
+    bool absoluteValue();
 
   private:
     int mColumns = 0;
diff --git a/src/app/qgsrastercalcdialog.cpp b/src/app/qgsrastercalcdialog.cpp
index 770f8c18ced..6fed53bb0ab 100644
--- a/src/app/qgsrastercalcdialog.cpp
+++ b/src/app/qgsrastercalcdialog.cpp
@@ -69,6 +69,9 @@ QgsRasterCalcDialog::QgsRasterCalcDialog( QgsRasterLayer *rasterLayer, QWidget *
   connect( mLesserEqualButton, &QPushButton::clicked, this, &QgsRasterCalcDialog::mLesserEqualButton_clicked );
   connect( mGreaterEqualButton, &QPushButton::clicked, this, &QgsRasterCalcDialog::mGreaterEqualButton_clicked );
   connect( mAndButton, &QPushButton::clicked, this, &QgsRasterCalcDialog::mAndButton_clicked );
+  connect( mAbsButton, &QPushButton::clicked, this, &QgsRasterCalcDialog::mAbsButton_clicked );
+  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( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsRasterCalcDialog::showHelp );
 
@@ -474,6 +477,21 @@ void QgsRasterCalcDialog::mOrButton_clicked()
   mExpressionTextEdit->insertPlainText( QStringLiteral( " OR " ) );
 }
 
+void QgsRasterCalcDialog::mAbsButton_clicked()
+{
+  mExpressionTextEdit->insertPlainText( QStringLiteral( " ABS ( " ) );
+}
+
+void QgsRasterCalcDialog::mMinButton_clicked()
+{
+  mExpressionTextEdit->insertPlainText( QStringLiteral( " MIN ( " ) );
+}
+
+void QgsRasterCalcDialog::mMaxButton_clicked()
+{
+  mExpressionTextEdit->insertPlainText( QStringLiteral( " MAX ( " ) );
+}
+
 QString QgsRasterCalcDialog::quoteBandEntry( const QString &layerName )
 {
   // '"' -> '\\"'
diff --git a/src/app/qgsrastercalcdialog.h b/src/app/qgsrastercalcdialog.h
index 02ca756c3df..b5f06359106 100644
--- a/src/app/qgsrastercalcdialog.h
+++ b/src/app/qgsrastercalcdialog.h
@@ -91,6 +91,9 @@ class APP_EXPORT QgsRasterCalcDialog: public QDialog, private Ui::QgsRasterCalcD
     void mGreaterEqualButton_clicked();
     void mAndButton_clicked();
     void mOrButton_clicked();
+    void mAbsButton_clicked();
+    void mMinButton_clicked();
+    void mMaxButton_clicked();
 
   private:
     //! Sets the extent and size of the output
diff --git a/src/ui/qgsrastercalcdialogbase.ui b/src/ui/qgsrastercalcdialogbase.ui
index 71a1db08ac6..945ceeab9eb 100644
--- a/src/ui/qgsrastercalcdialogbase.ui
+++ b/src/ui/qgsrastercalcdialogbase.ui
@@ -260,24 +260,17 @@
           <property name="bottomMargin">
            <number>0</number>
           </property>
-          <item row="1" column="6">
-           <widget class="QPushButton" name="mASinButton">
+          <item row="2" column="11">
+           <widget class="QPushButton" name="mOrButton">
             <property name="text">
-             <string>asin</string>
+             <string>OR</string>
             </property>
            </widget>
           </item>
-          <item row="0" column="4">
-           <widget class="QPushButton" name="mCosButton">
+          <item row="1" column="10">
+           <widget class="QPushButton" name="mLnButton">
             <property name="text">
-             <string>cos</string>
-            </property>
-           </widget>
-          </item>
-          <item row="2" column="4">
-           <widget class="QPushButton" name="mNotEqualButton">
-            <property name="text">
-             <string>!=</string>
+             <string>ln</string>
             </property>
            </widget>
           </item>
@@ -288,24 +281,31 @@
             </property>
            </widget>
           </item>
-          <item row="0" column="6">
-           <widget class="QPushButton" name="mSinButton">
+          <item row="2" column="10">
+           <widget class="QPushButton" name="mAndButton">
             <property name="text">
-             <string>sin</string>
+             <string>AND</string>
             </property>
            </widget>
           </item>
-          <item row="1" column="8">
-           <widget class="QPushButton" name="mATanButton">
+          <item row="0" column="10">
+           <widget class="QPushButton" name="mLogButton">
             <property name="text">
-             <string>atan</string>
+             <string>log10</string>
             </property>
            </widget>
           </item>
-          <item row="2" column="2">
-           <widget class="QPushButton" name="mEqualButton">
+          <item row="2" column="0">
+           <widget class="QPushButton" name="mLessButton">
             <property name="text">
-             <string>=</string>
+             <string>&lt;</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="8">
+           <widget class="QPushButton" name="mGreaterEqualButton">
+            <property name="text">
+             <string>&gt;=</string>
             </property>
            </widget>
           </item>
@@ -316,17 +316,31 @@
             </property>
            </widget>
           </item>
-          <item row="1" column="4">
-           <widget class="QPushButton" name="mACosButton">
+          <item row="0" column="0">
+           <widget class="QPushButton" name="mPlusPushButton">
             <property name="text">
-             <string>acos</string>
+             <string>+</string>
             </property>
            </widget>
           </item>
-          <item row="2" column="10">
-           <widget class="QPushButton" name="mAndButton">
+          <item row="0" column="1">
+           <widget class="QPushButton" name="mMultiplyPushButton">
             <property name="text">
-             <string>AND</string>
+             <string>*</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="4">
+           <widget class="QPushButton" name="mCosButton">
+            <property name="text">
+             <string>cos</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QPushButton" name="mGreaterButton">
+            <property name="text">
+             <string>&gt;</string>
             </property>
            </widget>
           </item>
@@ -343,80 +357,31 @@
             </property>
            </spacer>
           </item>
-          <item row="2" column="8">
-           <widget class="QPushButton" name="mGreaterEqualButton">
+          <item row="1" column="8">
+           <widget class="QPushButton" name="mATanButton">
             <property name="text">
-             <string>&gt;=</string>
+             <string>atan</string>
             </property>
            </widget>
           </item>
-          <item row="1" column="0">
-           <widget class="QPushButton" name="mMinusPushButton">
+          <item row="1" column="4">
+           <widget class="QPushButton" name="mACosButton">
             <property name="text">
-             <string>-</string>
+             <string>acos</string>
             </property>
            </widget>
           </item>
-          <item row="1" column="1">
-           <widget class="QPushButton" name="mDividePushButton">
+          <item row="1" column="6">
+           <widget class="QPushButton" name="mASinButton">
             <property name="text">
-             <string>/</string>
+             <string>asin</string>
             </property>
            </widget>
           </item>
-          <item row="2" column="1">
-           <widget class="QPushButton" name="mGreaterButton">
+          <item row="2" column="4">
+           <widget class="QPushButton" name="mNotEqualButton">
             <property name="text">
-             <string>&gt;</string>
-            </property>
-           </widget>
-          </item>
-          <item row="2" column="0">
-           <widget class="QPushButton" name="mLessButton">
-            <property name="text">
-             <string>&lt;</string>
-            </property>
-           </widget>
-          </item>
-          <item row="0" column="1">
-           <widget class="QPushButton" name="mMultiplyPushButton">
-            <property name="text">
-             <string>*</string>
-            </property>
-           </widget>
-          </item>
-          <item row="0" column="0">
-           <widget class="QPushButton" name="mPlusPushButton">
-            <property name="text">
-             <string>+</string>
-            </property>
-           </widget>
-          </item>
-          <item row="2" column="11">
-           <widget class="QPushButton" name="mOrButton">
-            <property name="text">
-             <string>OR</string>
-            </property>
-           </widget>
-          </item>
-          <item row="0" column="10">
-           <widget class="QPushButton" name="mLogButton">
-            <property name="text">
-             <string>log10</string>
-            </property>
-           </widget>
-          </item>
-          <item row="1" column="10">
-           <widget class="QPushButton" name="mLnButton">
-            <property name="text">
-             <string>ln</string>
-            </property>
-           </widget>
-          </item>
-          <item row="0" column="11">
-           <widget class="QPushButton" name="mOpenBracketPushButton">
-            <property name="text">
-             <string>(</string>
+             <string>!=</string>
             </property>
            </widget>
           </item>
@@ -427,10 +392,31 @@
             </property>
            </widget>
           </item>
-          <item row="0" column="2">
-           <widget class="QPushButton" name="mSqrtButton">
+          <item row="2" column="2">
+           <widget class="QPushButton" name="mEqualButton">
             <property name="text">
-             <string>sqrt</string>
+             <string>=</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="QPushButton" name="mMinusPushButton">
+            <property name="text">
+             <string>-</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="11">
+           <widget class="QPushButton" name="mOpenBracketPushButton">
+            <property name="text">
+             <string>(</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QPushButton" name="mDividePushButton">
+            <property name="text">
+             <string>/</string>
             </property>
            </widget>
           </item>
@@ -441,6 +427,41 @@
             </property>
            </widget>
           </item>
+          <item row="0" column="6">
+           <widget class="QPushButton" name="mSinButton">
+            <property name="text">
+             <string>sin</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="2">
+           <widget class="QPushButton" name="mSqrtButton">
+            <property name="text">
+             <string>sqrt</string>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="0">
+           <widget class="QPushButton" name="mAbsButton">
+            <property name="text">
+             <string>abs</string>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="1">
+           <widget class="QPushButton" name="mMinButton">
+            <property name="text">
+             <string>min</string>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="2">
+           <widget class="QPushButton" name="mMaxButton">
+            <property name="text">
+             <string>max</string>
+            </property>
+           </widget>
+          </item>
          </layout>
         </widget>
        </item>
diff --git a/tests/src/analysis/testqgsrastercalculator.cpp b/tests/src/analysis/testqgsrastercalculator.cpp
index a112bc607e4..4082ac08e22 100644
--- a/tests/src/analysis/testqgsrastercalculator.cpp
+++ b/tests/src/analysis/testqgsrastercalculator.cpp
@@ -173,7 +173,7 @@ void TestQgsRasterCalculator::dualOp()
 
   QgsRasterCalcNode node( op, new QgsRasterCalcNode( left ), new QgsRasterCalcNode( right ) );
 
-  QgsRasterMatrix result;
+  QgsRasterMatrix result( 1, 1, nullptr, -999 );
   result.setNodataValue( -9999 );
   QMap<QString, QgsRasterBlock *> rasterData;
 
@@ -223,8 +223,7 @@ void TestQgsRasterCalculator::singleOp()
 
   QgsRasterCalcNode node( op, new QgsRasterCalcNode( value ), nullptr );
 
-  QgsRasterMatrix result;
-  result.setNodataValue( -9999 );
+  QgsRasterMatrix result( 1, 1, nullptr, -9999 );
   QMap<QString, QgsRasterBlock *> rasterData;
 
   QVERIFY( node.calculate( rasterData, result ) );
@@ -249,8 +248,7 @@ void TestQgsRasterCalculator::singleOpMatrices()
 
   QgsRasterCalcNode node( QgsRasterCalcNode::opSIGN, new QgsRasterCalcNode( &m ), nullptr );
 
-  QgsRasterMatrix result;
-  result.setNodataValue( -9999 );
+  QgsRasterMatrix result( 1, 1, nullptr, -9999 );
   QMap<QString, QgsRasterBlock *> rasterData;
 
   QVERIFY( node.calculate( rasterData, result ) );
@@ -278,8 +276,7 @@ void TestQgsRasterCalculator::dualOpNumberMatrix()
 
   QgsRasterCalcNode node( QgsRasterCalcNode::opPLUS, new QgsRasterCalcNode( 5.0 ), new QgsRasterCalcNode( &m ) );
 
-  QgsRasterMatrix result;
-  result.setNodataValue( -9999 );
+  QgsRasterMatrix result( 1, 1, nullptr, -9999 );
   QMap<QString, QgsRasterBlock *> rasterData;
 
   QVERIFY( node.calculate( rasterData, result ) );
@@ -312,8 +309,7 @@ void TestQgsRasterCalculator::dualOpMatrixNumber()
 
   QgsRasterCalcNode node( QgsRasterCalcNode::opPLUS, new QgsRasterCalcNode( &m ), new QgsRasterCalcNode( 5.0 ) );
 
-  QgsRasterMatrix result;
-  result.setNodataValue( -9999 );
+  QgsRasterMatrix result( 1, 1, nullptr, -9999 );
   QMap<QString, QgsRasterBlock *> rasterData;
 
   QVERIFY( node.calculate( rasterData, result ) );
@@ -354,8 +350,7 @@ void TestQgsRasterCalculator::dualOpMatrixMatrix()
 
   QgsRasterCalcNode node( QgsRasterCalcNode::opPLUS, new QgsRasterCalcNode( &m1 ), new QgsRasterCalcNode( &m2 ) );
 
-  QgsRasterMatrix result;
-  result.setNodataValue( -9999 );
+  QgsRasterMatrix result( 1, 1, nullptr, -9999 );
   QMap<QString, QgsRasterBlock *> rasterData;
 
   QVERIFY( node.calculate( rasterData, result ) );
@@ -373,8 +368,7 @@ void TestQgsRasterCalculator::rasterRefOp()
   // test single op run on raster ref
   QgsRasterCalcNode node( QgsRasterCalcNode::opSIGN, new QgsRasterCalcNode( QStringLiteral( "raster" ) ), nullptr );
 
-  QgsRasterMatrix result;
-  result.setNodataValue( -9999 );
+  QgsRasterMatrix result( 1, 1, nullptr, -9999 );
   QMap<QString, QgsRasterBlock *> rasterData;
 
   //first test invalid raster ref
@@ -427,8 +421,7 @@ void TestQgsRasterCalculator::dualOpRasterRaster()
 
   QgsRasterCalcNode node( QgsRasterCalcNode::opPLUS, new QgsRasterCalcNode( QStringLiteral( "raster1" ) ), new QgsRasterCalcNode( QStringLiteral( "raster2" ) ) );
 
-  QgsRasterMatrix result;
-  result.setNodataValue( -9999 );
+  QgsRasterMatrix result( 1, 1, nullptr, -9999 );
 
   QVERIFY( node.calculate( rasterData, result ) );
   QCOMPARE( result.data()[0], 0.0 );
@@ -587,6 +580,18 @@ void TestQgsRasterCalculator::findNodes()
   QVERIFY( ! node );
   QVERIFY( ! errorString.isEmpty() );
 
+  // Test new abs, min, max
+  errorString = QString();
+  node = QgsRasterCalcNode::parseRasterCalcString( QStringLiteral( "abs(2)" ), errorString );
+  QVERIFY( node );
+  QVERIFY( errorString.isEmpty() );
+  node = QgsRasterCalcNode::parseRasterCalcString( QStringLiteral( "min(-1,1)" ), errorString );
+  QVERIFY( node );
+  QVERIFY( errorString.isEmpty() );
+  node = QgsRasterCalcNode::parseRasterCalcString( QStringLiteral( "max(-1,1)" ), errorString );
+  QVERIFY( node );
+  QVERIFY( errorString.isEmpty() );
+
 }
 
 void TestQgsRasterCalculator::testRasterEntries()
@@ -728,6 +733,16 @@ void TestQgsRasterCalculator::toString()
   QCOMPARE( _test( QStringLiteral( "0.5 * ( 1.4 * (\"raster@1\" + 2) )" ), true ), QString( "( float ) ( 0.5 ) * ( float ) ( 1.4 ) * ( \"raster@1\" + ( float ) ( 2 ) )" ) );
   QCOMPARE( _test( QStringLiteral( "0.5 * ( 1 > 0 )" ), false ), QString( "0.5 * 1 > 0" ) );
   QCOMPARE( _test( QStringLiteral( "0.5 * ( 1 > 0 )" ), true ), QString( "( float ) ( 0.5 ) * ( float ) ( ( float ) ( 1 ) > ( float ) ( 0 ) )" ) );
+  // Test negative numbers
+  QCOMPARE( _test( QStringLiteral( "0.5 * ( -1 > 0 )" ), false ), QString( "0.5 * -1 > 0" ) );
+  QCOMPARE( _test( QStringLiteral( "0.5 * ( -1 > 0 )" ), true ), QString( "( float ) ( 0.5 ) * ( float ) ( -( float ) ( 1 ) > ( float ) ( 0 ) )" ) );
+  // Test new functions
+  QCOMPARE( _test( QStringLiteral( "0.5 * abs( -1 )" ), false ), QString( "0.5 * abs( -1 )" ) );
+  QCOMPARE( _test( QStringLiteral( "0.5 * abs( -1 )" ), true ), QString( "( float ) ( 0.5 ) * fabs( -( float ) ( 1 ) )" ) );
+  QCOMPARE( _test( QStringLiteral( "0.5 * min( -1, 1 )" ), false ), QString( "0.5 * min( -1, 1 )" ) );
+  QCOMPARE( _test( QStringLiteral( "0.5 * min( -1, 1 )" ), true ), QString( "( float ) ( 0.5 ) * min( ( float ) ( -( float ) ( 1 ) ), ( float ) ( ( float ) ( 1 ) ) )" ) );
+  QCOMPARE( _test( QStringLiteral( "0.5 * max( -1, 1 )" ), false ), QString( "0.5 * max( -1, 1 )" ) );
+  QCOMPARE( _test( QStringLiteral( "0.5 * max( -1, 1 )" ), true ), QString( "( float ) ( 0.5 ) * max( ( float ) ( -( float ) ( 1 ) ), ( float ) ( ( float ) ( 1 ) ) )" ) );
 }
 
 void TestQgsRasterCalculator::calcFormulasWithReprojectedLayers()
@@ -753,6 +768,8 @@ void TestQgsRasterCalculator::calcFormulasWithReprojectedLayers()
   auto _chk = [ = ]( const QString & formula, const std::vector<float> &values, bool useOpenCL )
   {
 
+    qDebug() << formula;
+
 #ifdef HAVE_OPENCL
     if ( ! QgsOpenClUtils::available() )
       return ;
@@ -798,6 +815,27 @@ void TestQgsRasterCalculator::calcFormulasWithReprojectedLayers()
   _chk( QStringLiteral( "\"landsat@1\" * ( \"landsat@1\" > 124 )" ), {125.0, 125.0, 0.0, 125.0, 125.0, 0.0}, false );
   _chk( QStringLiteral( "\"landsat@1\" * ( \"landsat@1\" > 124 )" ), {125.0, 125.0, 0.0, 125.0, 125.0, 0.0}, true );
 
+  // Test negative numbers
+  _chk( QStringLiteral( "-2.5" ), { -2.5, -2.5, -2.5, -2.5, -2.5, -2.5 }, false );
+  _chk( QStringLiteral( "- 2.5" ), { -2.5, -2.5, -2.5, -2.5, -2.5, -2.5 }, false );
+  _chk( QStringLiteral( "-2.5" ), { -2.5, -2.5, -2.5, -2.5, -2.5, -2.5 }, true );
+  _chk( QStringLiteral( "- 2.5" ), { -2.5, -2.5, -2.5, -2.5, -2.5, -2.5 }, true );
+  _chk( QStringLiteral( "-\"landsat@1\"" ), {-125, -125, -124, -125, -125, -124}, false );
+  _chk( QStringLiteral( "-\"landsat@1\"" ), {-125, -125, -124, -125, -125, -124}, true );
+
+  // Test abs, min and max
+  // landsat values: 125 125 124 125 125 124
+  // landsat_4326 values: 139 138 140 139 141 137
+  _chk( QStringLiteral( "abs(-123)" ), {123, 123, 123, 123, 123, 123}, false );
+  _chk( QStringLiteral( "abs(-\"landsat@1\")" ), {125, 125, 124, 125, 125, 124}, true );
+  _chk( QStringLiteral( "abs(-123)" ), {123, 123, 123, 123, 123, 123}, false );
+  _chk( QStringLiteral( "abs(-\"landsat@1\")" ), {125, 125, 124, 125, 125, 124}, true );
+  _chk( QStringLiteral( "-\"landsat_4326@2\" + 15" ), {-124, -123, -125, -124, -126, -122}, false );
+  _chk( QStringLiteral( "min(-\"landsat@1\", -\"landsat_4326@2\" + 15 )" ), {-125, -125, -125, -125, -126, -124}, false );
+  _chk( QStringLiteral( "min(-\"landsat@1\", -\"landsat_4326@2\" + 15 )" ), {-125, -125, -125, -125, -126, -124}, true );
+  _chk( QStringLiteral( "max(-\"landsat@1\", -\"landsat_4326@2\" + 15 )" ), {-124, -123, -124, -124, -125, -122}, false );
+  _chk( QStringLiteral( "max(-\"landsat@1\", -\"landsat_4326@2\" + 15 )" ), {-124, -123, -124, -124, -125, -122}, true );
+
 }