LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_calc.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 463 501 92.4 %
Date: 2026-07-09 08:35:43 Functions: 14 14 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "gdal raster calc" subcommand
       5             :  * Author:   Daniel Baston
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, ISciences LLC
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_raster_calc.h"
      14             : 
      15             : #include "../frmts/vrt/gdal_vrt.h"
      16             : #include "../frmts/vrt/vrtdataset.h"
      17             : 
      18             : #include "cpl_float.h"
      19             : #include "cpl_vsi_virtual.h"
      20             : #include "gdal_priv.h"
      21             : #include "gdal_utils.h"
      22             : #include "vrtdataset.h"
      23             : 
      24             : #include <algorithm>
      25             : #include <optional>
      26             : 
      27             : //! @cond Doxygen_Suppress
      28             : 
      29             : #ifndef _
      30             : #define _(x) (x)
      31             : #endif
      32             : 
      33             : struct GDALCalcOptions
      34             : {
      35             :     GDALDataType dstType{GDT_Unknown};
      36             :     bool checkCRS{true};
      37             :     bool checkExtent{true};
      38             : };
      39             : 
      40         242 : static bool MatchIsCompleteVariableNameWithNoIndex(const std::string &str,
      41             :                                                    size_t from, size_t to)
      42             : {
      43         242 :     if (to < str.size())
      44             :     {
      45             :         // If the character after the end of the match is:
      46             :         // * alphanumeric or _ : we've matched only part of a variable name
      47             :         // * [ : we've matched a variable that already has an index
      48             :         // * ( : we've matched a function name
      49         308 :         if (std::isalnum(str[to]) || str[to] == '_' || str[to] == '[' ||
      50         108 :             str[to] == '(')
      51             :         {
      52          93 :             return false;
      53             :         }
      54             :     }
      55         149 :     if (from > 0)
      56             :     {
      57             :         // If the character before the start of the match is alphanumeric or _,
      58             :         // we've matched only part of a variable name.
      59          91 :         if (std::isalnum(str[from - 1]) || str[from - 1] == '_')
      60             :         {
      61           3 :             return false;
      62             :         }
      63             :     }
      64             : 
      65         146 :     return true;
      66             : }
      67             : 
      68             : /**
      69             :  *  Add a band subscript to all instances of a specified variable that
      70             :  *  do not already have such a subscript. For example, "X" would be
      71             :  *  replaced with "X[3]" but "X[1]" would be left untouched.
      72             :  */
      73         146 : static std::string SetBandIndices(const std::string &origExpression,
      74             :                                   const std::string &variable, int band,
      75             :                                   bool &expressionChanged)
      76             : {
      77         146 :     std::string expression = origExpression;
      78         146 :     expressionChanged = false;
      79             : 
      80         146 :     std::string::size_type seekPos = 0;
      81         146 :     auto pos = expression.find(variable, seekPos);
      82         352 :     while (pos != std::string::npos)
      83             :     {
      84         206 :         auto end = pos + variable.size();
      85             : 
      86         206 :         if (MatchIsCompleteVariableNameWithNoIndex(expression, pos, end))
      87             :         {
      88             :             // No index specified for variable
      89         220 :             expression = expression.substr(0, pos + variable.size()) + '[' +
      90         330 :                          std::to_string(band) + ']' + expression.substr(end);
      91         110 :             expressionChanged = true;
      92             :         }
      93             : 
      94         206 :         seekPos = end;
      95         206 :         pos = expression.find(variable, seekPos);
      96             :     }
      97             : 
      98         146 :     return expression;
      99             : }
     100             : 
     101          72 : static bool PosIsAggregateFunctionArgument(const std::string &expression,
     102             :                                            size_t pos)
     103             : {
     104             :     // If this position is a function argument, we should be able to
     105             :     // scan backwards for a ( and find only variable names, literals or commas.
     106          72 :     while (pos != 0)
     107             :     {
     108          64 :         const char c = expression[pos];
     109          64 :         if (c == '(')
     110             :         {
     111          24 :             pos--;
     112          24 :             break;
     113             :         }
     114          40 :         if (!(isspace(c) || isalnum(c) || c == ',' || c == '.' || c == '[' ||
     115             :               c == ']' || c == '_'))
     116             :         {
     117           4 :             return false;
     118             :         }
     119          36 :         pos--;
     120             :     }
     121             : 
     122             :     // Now what we've found the (, the preceding characters should be an
     123             :     // aggregate function name
     124          32 :     if (pos < 2)
     125             :     {
     126           8 :         return false;
     127             :     }
     128             : 
     129          24 :     if (STARTS_WITH_CI(expression.c_str() + (pos - 2), "avg") ||
     130          20 :         STARTS_WITH_CI(expression.c_str() + (pos - 2), "sum") ||
     131          52 :         STARTS_WITH_CI(expression.c_str() + (pos - 2), "min") ||
     132           8 :         STARTS_WITH_CI(expression.c_str() + (pos - 2), "max"))
     133             :     {
     134          20 :         return true;
     135             :     }
     136             : 
     137           4 :     return false;
     138             : }
     139             : 
     140             : /**
     141             :  *  Replace X by X[1],X[2],...X[n]
     142             :  */
     143             : static std::string
     144          32 : SetBandIndicesFlattenedExpression(const std::string &origExpression,
     145             :                                   const std::string &variable, int nBands)
     146             : {
     147          32 :     std::string expression = origExpression;
     148             : 
     149          32 :     std::string::size_type seekPos = 0;
     150          32 :     auto pos = expression.find(variable, seekPos);
     151          68 :     while (pos != std::string::npos)
     152             :     {
     153          36 :         auto end = pos + variable.size();
     154             : 
     155          72 :         if (MatchIsCompleteVariableNameWithNoIndex(expression, pos, end) &&
     156          36 :             PosIsAggregateFunctionArgument(expression, pos))
     157             :         {
     158          20 :             std::string newExpr = expression.substr(0, pos);
     159          68 :             for (int i = 1; i <= nBands; ++i)
     160             :             {
     161          48 :                 if (i > 1)
     162          28 :                     newExpr += ',';
     163          48 :                 newExpr += variable;
     164          48 :                 newExpr += '[';
     165          48 :                 newExpr += std::to_string(i);
     166          48 :                 newExpr += ']';
     167             :             }
     168          20 :             const size_t oldExprSize = expression.size();
     169          20 :             newExpr += expression.substr(end);
     170          20 :             expression = std::move(newExpr);
     171          20 :             end += expression.size() - oldExprSize;
     172             :         }
     173             : 
     174          36 :         seekPos = end;
     175          36 :         pos = expression.find(variable, seekPos);
     176             :     }
     177             : 
     178          32 :     return expression;
     179             : }
     180             : 
     181             : struct SourceProperties
     182             : {
     183             :     int nBands{0};
     184             :     int nX{0};
     185             :     int nY{0};
     186             :     bool hasGT{false};
     187             :     GDALGeoTransform gt{};
     188             :     OGRSpatialReferenceRefCountedPtr srs{};
     189             :     std::vector<std::optional<double>> noData{};
     190             :     GDALDataType eDT{GDT_Unknown};
     191             : };
     192             : 
     193             : static std::optional<SourceProperties>
     194         156 : UpdateSourceProperties(SourceProperties &out, const std::string &dsn,
     195             :                        const GDALCalcOptions &options)
     196             : {
     197         312 :     SourceProperties source;
     198         156 :     bool srsMismatch = false;
     199         156 :     bool extentMismatch = false;
     200         156 :     bool dimensionMismatch = false;
     201             : 
     202             :     {
     203             :         std::unique_ptr<GDALDataset> ds(
     204         156 :             GDALDataset::Open(dsn.c_str(), GDAL_OF_RASTER));
     205             : 
     206         156 :         if (!ds)
     207             :         {
     208           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Failed to open %s",
     209             :                      dsn.c_str());
     210           0 :             return std::nullopt;
     211             :         }
     212             : 
     213         156 :         source.nBands = ds->GetRasterCount();
     214         156 :         source.nX = ds->GetRasterXSize();
     215         156 :         source.nY = ds->GetRasterYSize();
     216         156 :         source.noData.resize(source.nBands);
     217             : 
     218         156 :         if (options.checkExtent)
     219             :         {
     220         150 :             ds->GetGeoTransform(source.gt);
     221             :         }
     222             : 
     223         156 :         if (options.checkCRS && out.srs)
     224             :         {
     225          57 :             const OGRSpatialReference *srs = ds->GetSpatialRef();
     226          57 :             srsMismatch = srs && !srs->IsSame(out.srs.get());
     227             :         }
     228             : 
     229             :         // Store the source data type if it is the same for all bands in the source
     230         156 :         bool bandsHaveSameType = true;
     231         412 :         for (int i = 1; i <= source.nBands; ++i)
     232             :         {
     233         256 :             GDALRasterBand *band = ds->GetRasterBand(i);
     234             : 
     235         256 :             if (i == 1)
     236             :             {
     237         156 :                 source.eDT = band->GetRasterDataType();
     238             :             }
     239         200 :             else if (bandsHaveSameType &&
     240         100 :                      source.eDT != band->GetRasterDataType())
     241             :             {
     242           0 :                 source.eDT = GDT_Unknown;
     243           0 :                 bandsHaveSameType = false;
     244             :             }
     245             : 
     246             :             int success;
     247         256 :             double noData = band->GetNoDataValue(&success);
     248         256 :             if (success)
     249             :             {
     250          17 :                 source.noData[i - 1] = noData;
     251             :             }
     252             :         }
     253             :     }
     254             : 
     255         156 :     if (source.nX != out.nX || source.nY != out.nY)
     256             :     {
     257           3 :         dimensionMismatch = true;
     258             :     }
     259             : 
     260         156 :     if (source.gt.xorig != out.gt.xorig || source.gt.xrot != out.gt.xrot ||
     261         156 :         source.gt.yorig != out.gt.yorig || source.gt.yrot != out.gt.yrot)
     262             :     {
     263           6 :         extentMismatch = true;
     264             :     }
     265         156 :     if (source.gt.xscale != out.gt.xscale || source.gt.yscale != out.gt.yscale)
     266             :     {
     267             :         // Resolutions are different. Are the extents the same?
     268           9 :         double xmaxOut =
     269           9 :             out.gt.xorig + out.nX * out.gt.xscale + out.nY * out.gt.xrot;
     270           9 :         double yminOut =
     271           9 :             out.gt.yorig + out.nX * out.gt.yrot + out.nY * out.gt.yscale;
     272             : 
     273           9 :         double xmax = source.gt.xorig + source.nX * source.gt.xscale +
     274           9 :                       source.nY * source.gt.xrot;
     275           9 :         double ymin = source.gt.yorig + source.nX * source.gt.yrot +
     276           9 :                       source.nY * source.gt.yscale;
     277             : 
     278             :         // Max allowable extent misalignment, expressed as fraction of a pixel
     279           9 :         constexpr double EXTENT_RTOL = 1e-3;
     280             : 
     281           9 :         if (std::abs(xmax - xmaxOut) >
     282          15 :                 EXTENT_RTOL * std::abs(source.gt.xscale) ||
     283           6 :             std::abs(ymin - yminOut) > EXTENT_RTOL * std::abs(source.gt.yscale))
     284             :         {
     285           6 :             extentMismatch = true;
     286             :         }
     287             :     }
     288             : 
     289         156 :     if (options.checkExtent && extentMismatch)
     290             :     {
     291           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     292             :                  "Input extents are inconsistent.");
     293           2 :         return std::nullopt;
     294             :     }
     295             : 
     296         154 :     if (!options.checkExtent && dimensionMismatch)
     297             :     {
     298           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     299             :                  "Inputs do not have the same dimensions.");
     300           1 :         return std::nullopt;
     301             :     }
     302             : 
     303             :     // Find a common resolution
     304         153 :     if (source.nX > out.nX)
     305             :     {
     306           1 :         auto dx = CPLGreatestCommonDivisor(out.gt.xscale, source.gt.xscale);
     307           1 :         if (dx == 0)
     308             :         {
     309           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     310             :                      "Failed to find common resolution for inputs.");
     311           0 :             return std::nullopt;
     312             :         }
     313           1 :         out.nX = static_cast<int>(
     314           1 :             std::round(static_cast<double>(out.nX) * out.gt.xscale / dx));
     315           1 :         out.gt.xscale = dx;
     316             :     }
     317         153 :     if (source.nY > out.nY)
     318             :     {
     319           1 :         auto dy = CPLGreatestCommonDivisor(out.gt.yscale, source.gt.yscale);
     320           1 :         if (dy == 0)
     321             :         {
     322           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     323             :                      "Failed to find common resolution for inputs.");
     324           0 :             return std::nullopt;
     325             :         }
     326           1 :         out.nY = static_cast<int>(
     327           1 :             std::round(static_cast<double>(out.nY) * out.gt.yscale / dy));
     328           1 :         out.gt.yscale = dy;
     329             :     }
     330             : 
     331         153 :     if (srsMismatch)
     332             :     {
     333           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     334             :                  "Input spatial reference systems are inconsistent.");
     335           1 :         return std::nullopt;
     336             :     }
     337             : 
     338         152 :     return source;
     339             : }
     340             : 
     341             : /** Create XML nodes for one or more derived bands resulting from the evaluation
     342             :  *  of a single expression
     343             :  *
     344             :  * @param pszVRTFilename VRT filename
     345             :  * @param root VRTDataset node to which the band nodes should be added
     346             :  * @param bandType the type of the band(s) to create
     347             :  * @param nXOut Number of columns in VRT dataset
     348             :  * @param nYOut Number of rows in VRT dataset
     349             :  * @param expression Expression for which band(s) should be added
     350             :  * @param dialect Expression dialect
     351             :  * @param flatten Generate a single band output raster per expression, even if
     352             :  *                input datasets are multiband.
     353             :  * @param noDataText nodata value to use for the created band, or "none", or ""
     354             :  * @param pixelFunctionArguments Pixel function arguments.
     355             :  * @param sources Mapping of source names to DSNs
     356             :  * @param sourceProps Mapping of source names to properties
     357             :  * @param fakeSourceFilename If not empty, used instead of real input filenames.
     358             :  * @return true if the band(s) were added, false otherwise
     359             :  */
     360             : static bool
     361         116 : CreateDerivedBandXML(const char *pszVRTFilename, CPLXMLNode *root, int nXOut,
     362             :                      int nYOut, GDALDataType bandType,
     363             :                      const std::string &expression, const std::string &dialect,
     364             :                      bool flatten, const std::string &noDataText,
     365             :                      const std::vector<std::string> &pixelFunctionArguments,
     366             :                      const std::map<std::string, std::string> &sources,
     367             :                      const std::map<std::string, SourceProperties> &sourceProps,
     368             :                      const std::string &fakeSourceFilename)
     369             : {
     370         116 :     int nOutBands = 1;  // By default, each expression produces a single output
     371             :                         // band. When processing the expression below, we may
     372             :                         // discover that the expression produces multiple bands,
     373             :                         // in which case this will be updated.
     374             : 
     375         255 :     for (int nOutBand = 1; nOutBand <= nOutBands; nOutBand++)
     376             :     {
     377             :         // Copy the expression for each output band, because we may modify it
     378             :         // when adding band indices (e.g., X -> X[1]) to the variables in the
     379             :         // expression.
     380         143 :         std::string bandExpression = expression;
     381             : 
     382         143 :         CPLXMLNode *band = CPLCreateXMLNode(root, CXT_Element, "VRTRasterBand");
     383         143 :         CPLAddXMLAttributeAndValue(band, "subClass", "VRTDerivedRasterBand");
     384         143 :         if (bandType == GDT_Unknown)
     385             :         {
     386         103 :             bandType = GDT_Float64;
     387             :         }
     388         143 :         CPLAddXMLAttributeAndValue(band, "dataType",
     389             :                                    GDALGetDataTypeName(bandType));
     390             : 
     391         143 :         std::optional<double> dstNoData;
     392         143 :         bool autoSelectNoDataValue = false;
     393         143 :         if (noDataText.empty())
     394             :         {
     395         138 :             autoSelectNoDataValue = true;
     396             :         }
     397           5 :         else if (noDataText != "none")
     398             :         {
     399          10 :             if (auto parsed = cpl::strict_parse<double>(noDataText);
     400           5 :                 parsed.has_value())
     401             :             {
     402           5 :                 dstNoData = parsed.value();
     403             :             }
     404             :             else
     405             :             {
     406           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     407             :                          "Invalid NoData value: %s", noDataText.c_str());
     408           0 :                 return false;
     409             :             }
     410             :         }
     411             : 
     412         327 :         for (const auto &[source_name, dsn] : sources)
     413             :         {
     414         188 :             auto it = sourceProps.find(source_name);
     415         188 :             CPLAssert(it != sourceProps.end());
     416         188 :             const auto &props = it->second;
     417             : 
     418         188 :             bool expressionAppliedPerBand = false;
     419         188 :             if (dialect == "builtin")
     420             :             {
     421          42 :                 expressionAppliedPerBand = !flatten;
     422             :             }
     423             :             else
     424             :             {
     425         146 :                 const int nDefaultInBand = std::min(props.nBands, nOutBand);
     426             : 
     427         146 :                 if (flatten)
     428             :                 {
     429          32 :                     bandExpression = SetBandIndicesFlattenedExpression(
     430          32 :                         bandExpression, source_name, props.nBands);
     431             :                 }
     432             : 
     433             :                 bandExpression =
     434         292 :                     SetBandIndices(bandExpression, source_name, nDefaultInBand,
     435         146 :                                    expressionAppliedPerBand);
     436             :             }
     437             : 
     438         188 :             if (expressionAppliedPerBand)
     439             :             {
     440         134 :                 if (nOutBands <= 1)
     441             :                 {
     442          93 :                     nOutBands = props.nBands;
     443             :                 }
     444          41 :                 else if (props.nBands != 1 && props.nBands != nOutBands)
     445             :                 {
     446           3 :                     CPLError(CE_Failure, CPLE_AppDefined,
     447             :                              "Expression cannot operate on all bands of "
     448             :                              "rasters with incompatible numbers of bands "
     449             :                              "(source %s has %d bands but expected to have "
     450             :                              "1 or %d bands).",
     451           3 :                              source_name.c_str(), props.nBands, nOutBands);
     452           4 :                     return false;
     453             :                 }
     454             :             }
     455             : 
     456             :             // Create a source for each input band that is used in
     457             :             // the expression.
     458         513 :             for (int nInBand = 1; nInBand <= props.nBands; nInBand++)
     459             :             {
     460         328 :                 CPLString inBandVariable;
     461         328 :                 if (dialect == "builtin")
     462             :                 {
     463          72 :                     if (!flatten && props.nBands >= 2 && nInBand != nOutBand)
     464          11 :                         continue;
     465             :                 }
     466             :                 else
     467             :                 {
     468             :                     inBandVariable.Printf("%s[%d]", source_name.c_str(),
     469         256 :                                           nInBand);
     470         256 :                     if (bandExpression.find(inBandVariable) ==
     471             :                         std::string::npos)
     472             :                     {
     473          79 :                         continue;
     474             :                     }
     475             :                 }
     476             : 
     477             :                 const std::optional<double> &srcNoData =
     478         238 :                     props.noData[nInBand - 1];
     479             : 
     480         238 :                 CPLXMLNode *source = CPLCreateXMLNode(
     481             :                     band, CXT_Element,
     482         238 :                     srcNoData.has_value() ? "ComplexSource" : "SimpleSource");
     483         238 :                 if (!inBandVariable.empty())
     484             :                 {
     485         177 :                     CPLAddXMLAttributeAndValue(source, "name",
     486             :                                                inBandVariable.c_str());
     487             :                 }
     488             : 
     489             :                 CPLXMLNode *sourceFilename =
     490         238 :                     CPLCreateXMLNode(source, CXT_Element, "SourceFilename");
     491         238 :                 if (fakeSourceFilename.empty())
     492             :                 {
     493         330 :                     std::string osSourceFilename = dsn;
     494         165 :                     bool bRelativeToVRT = false;
     495         165 :                     if (pszVRTFilename[0])
     496             :                     {
     497          16 :                         std::tie(osSourceFilename, bRelativeToVRT) =
     498          32 :                             VRTSimpleSource::ComputeSourceNameAndRelativeFlag(
     499          48 :                                 CPLGetPathSafe(pszVRTFilename).c_str(), dsn);
     500             :                     }
     501         165 :                     CPLAddXMLAttributeAndValue(sourceFilename, "relativeToVRT",
     502             :                                                bRelativeToVRT ? "1" : "0");
     503         165 :                     CPLCreateXMLNode(sourceFilename, CXT_Text,
     504             :                                      osSourceFilename.c_str());
     505             :                 }
     506             :                 else
     507             :                 {
     508          73 :                     CPLCreateXMLNode(sourceFilename, CXT_Text,
     509             :                                      fakeSourceFilename.c_str());
     510             :                 }
     511             : 
     512             :                 CPLXMLNode *sourceBand =
     513         238 :                     CPLCreateXMLNode(source, CXT_Element, "SourceBand");
     514         238 :                 CPLCreateXMLNode(sourceBand, CXT_Text,
     515         476 :                                  std::to_string(nInBand).c_str());
     516             : 
     517         238 :                 if (srcNoData.has_value())
     518             :                 {
     519             :                     CPLXMLNode *srcNoDataNode =
     520          17 :                         CPLCreateXMLNode(source, CXT_Element, "NODATA");
     521             :                     std::string srcNoDataText =
     522          34 :                         CPLSPrintf("%.17g", srcNoData.value());
     523          17 :                     CPLCreateXMLNode(srcNoDataNode, CXT_Text,
     524             :                                      srcNoDataText.c_str());
     525             : 
     526          17 :                     if (autoSelectNoDataValue && !dstNoData.has_value())
     527             :                     {
     528           8 :                         dstNoData = srcNoData;
     529             :                     }
     530             :                 }
     531             : 
     532         238 :                 if (fakeSourceFilename.empty())
     533             :                 {
     534             :                     CPLXMLNode *srcRect =
     535         165 :                         CPLCreateXMLNode(source, CXT_Element, "SrcRect");
     536         165 :                     CPLAddXMLAttributeAndValue(srcRect, "xOff", "0");
     537         165 :                     CPLAddXMLAttributeAndValue(srcRect, "yOff", "0");
     538         165 :                     CPLAddXMLAttributeAndValue(
     539         330 :                         srcRect, "xSize", std::to_string(props.nX).c_str());
     540         165 :                     CPLAddXMLAttributeAndValue(
     541         330 :                         srcRect, "ySize", std::to_string(props.nY).c_str());
     542             : 
     543             :                     CPLXMLNode *dstRect =
     544         165 :                         CPLCreateXMLNode(source, CXT_Element, "DstRect");
     545         165 :                     CPLAddXMLAttributeAndValue(dstRect, "xOff", "0");
     546         165 :                     CPLAddXMLAttributeAndValue(dstRect, "yOff", "0");
     547         165 :                     CPLAddXMLAttributeAndValue(dstRect, "xSize",
     548         330 :                                                std::to_string(nXOut).c_str());
     549         165 :                     CPLAddXMLAttributeAndValue(dstRect, "ySize",
     550         330 :                                                std::to_string(nYOut).c_str());
     551             :                 }
     552             :             }
     553             : 
     554         185 :             if (dstNoData.has_value())
     555             :             {
     556          17 :                 if (!GDALIsValueExactAs(dstNoData.value(), bandType))
     557             :                 {
     558           1 :                     CPLError(
     559             :                         CE_Failure, CPLE_AppDefined,
     560             :                         "Band output type %s cannot represent NoData value %g",
     561           1 :                         GDALGetDataTypeName(bandType), dstNoData.value());
     562           1 :                     return false;
     563             :                 }
     564             : 
     565             :                 CPLXMLNode *noDataNode =
     566          16 :                     CPLCreateXMLNode(band, CXT_Element, "NoDataValue");
     567             :                 CPLString dstNoDataText =
     568          32 :                     CPLSPrintf("%.17g", dstNoData.value());
     569          16 :                 CPLCreateXMLNode(noDataNode, CXT_Text, dstNoDataText.c_str());
     570             :             }
     571             :         }
     572             : 
     573             :         CPLXMLNode *pixelFunctionType =
     574         139 :             CPLCreateXMLNode(band, CXT_Element, "PixelFunctionType");
     575             :         CPLXMLNode *arguments =
     576         139 :             CPLCreateXMLNode(band, CXT_Element, "PixelFunctionArguments");
     577             : 
     578         139 :         if (dialect == "builtin")
     579             :         {
     580          28 :             CPLCreateXMLNode(pixelFunctionType, CXT_Text, expression.c_str());
     581             :         }
     582             :         else
     583             :         {
     584         111 :             CPLCreateXMLNode(pixelFunctionType, CXT_Text, "expression");
     585         111 :             CPLAddXMLAttributeAndValue(arguments, "dialect", "muparser");
     586             :             // Add the expression as a last step, because we may modify the
     587             :             // expression as we iterate through the bands.
     588         111 :             CPLAddXMLAttributeAndValue(arguments, "expression",
     589             :                                        bandExpression.c_str());
     590             :         }
     591             : 
     592         139 :         if (!pixelFunctionArguments.empty())
     593             :         {
     594          16 :             const CPLStringList args(pixelFunctionArguments);
     595          16 :             for (const auto &[key, value] : cpl::IterateNameValue(args))
     596             :             {
     597           8 :                 CPLAddXMLAttributeAndValue(arguments, key, value);
     598             :             }
     599             :         }
     600             :     }
     601             : 
     602         112 :     return true;
     603             : }
     604             : 
     605         122 : static bool ParseSourceDescriptors(const std::vector<std::string> &inputs,
     606             :                                    std::map<std::string, std::string> &datasets,
     607             :                                    std::string &firstSourceName,
     608             :                                    bool requireSourceNames)
     609             : {
     610         281 :     for (size_t iInput = 0; iInput < inputs.size(); iInput++)
     611             :     {
     612         164 :         const std::string &input = inputs[iInput];
     613         164 :         std::string name;
     614             : 
     615         164 :         const auto pos = input.find('=');
     616         164 :         if (pos == std::string::npos)
     617             :         {
     618          61 :             if (requireSourceNames && inputs.size() > 1)
     619             :             {
     620           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     621             :                          "Inputs must be named when more than one input is "
     622             :                          "provided.");
     623           1 :                 return false;
     624             :             }
     625          60 :             name = "X";
     626          60 :             if (iInput > 0)
     627             :             {
     628           2 :                 name += std::to_string(iInput);
     629             :             }
     630             :         }
     631             :         else
     632             :         {
     633         103 :             name = input.substr(0, pos);
     634             :         }
     635             : 
     636             :         // Check input name is legal
     637         347 :         for (size_t i = 0; i < name.size(); ++i)
     638             :         {
     639         187 :             const char c = name[i];
     640         187 :             if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
     641             :             {
     642             :                 // ok
     643             :             }
     644          20 :             else if (c == '_' || (c >= '0' && c <= '9'))
     645             :             {
     646          19 :                 if (i == 0)
     647             :                 {
     648             :                     // Reserved constants in MuParser start with an underscore
     649           2 :                     CPLError(
     650             :                         CE_Failure, CPLE_AppDefined,
     651             :                         "Name '%s' is illegal because it starts with a '%c'",
     652             :                         name.c_str(), c);
     653           2 :                     return false;
     654             :                 }
     655             :             }
     656             :             else
     657             :             {
     658           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     659             :                          "Name '%s' is illegal because character '%c' is not "
     660             :                          "allowed",
     661             :                          name.c_str(), c);
     662           1 :                 return false;
     663             :             }
     664             :         }
     665             : 
     666             :         std::string dsn =
     667         160 :             (pos == std::string::npos) ? input : input.substr(pos + 1);
     668             : 
     669         160 :         if (!dsn.empty() && dsn.front() == '[' && dsn.back() == ']')
     670             :         {
     671             :             dsn = "{\"type\":\"gdal_streamed_alg\", \"command_line\":\"gdal "
     672           0 :                   "raster pipeline " +
     673           2 :                   CPLString(dsn.substr(1, dsn.size() - 2))
     674           2 :                       .replaceAll('\\', "\\\\")
     675           2 :                       .replaceAll('"', "\\\"") +
     676           1 :                   "\"}";
     677             :         }
     678             : 
     679         160 :         if (datasets.find(name) != datasets.end())
     680             :         {
     681           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     682             :                      "An input with name '%s' has already been provided",
     683             :                      name.c_str());
     684           1 :             return false;
     685             :         }
     686         159 :         datasets[name] = std::move(dsn);
     687             : 
     688         159 :         if (iInput == 0)
     689             :         {
     690         118 :             firstSourceName = std::move(name);
     691             :         }
     692             :     }
     693             : 
     694         117 :     return true;
     695             : }
     696             : 
     697          90 : static bool ReadFileLists(const std::vector<GDALArgDatasetValue> &inputDS,
     698             :                           std::vector<std::string> &inputFilenames)
     699             : {
     700         212 :     for (const auto &dsVal : inputDS)
     701             :     {
     702         122 :         const auto &input = dsVal.GetName();
     703         122 :         if (!input.empty() && input[0] == '@')
     704             :         {
     705             :             auto f =
     706           2 :                 VSIVirtualHandleUniquePtr(VSIFOpenL(input.c_str() + 1, "r"));
     707           2 :             if (!f)
     708             :             {
     709           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
     710           0 :                          input.c_str() + 1);
     711           0 :                 return false;
     712             :             }
     713           6 :             while (const char *filename = CPLReadLineL(f.get()))
     714             :             {
     715           4 :                 inputFilenames.push_back(filename);
     716           4 :             }
     717             :         }
     718             :         else
     719             :         {
     720         120 :             inputFilenames.push_back(input);
     721             :         }
     722             :     }
     723             : 
     724          90 :     return true;
     725             : }
     726             : 
     727             : /** Creates a VRT datasource with one or more derived raster bands containing
     728             :  *  results of an expression.
     729             :  *
     730             :  * To make this work with muparser (which does not support vector types), we
     731             :  * do a simple parsing of the expression internally, transforming it into
     732             :  * multiple expressions with explicit band indices. For example, for a two-band
     733             :  * raster "X", the expression "X + 3" will be transformed into "X[1] + 3" and
     734             :  * "X[2] + 3". The use of brackets is for readability only; as far as the
     735             :  * expression engine is concerned, the variables "X[1]" and "X[2]" have nothing
     736             :  * to do with each other.
     737             :  *
     738             :  * @param pszVRTFilename VRT filename
     739             :  * @param inputs A list of sources, expressed as NAME=DSN
     740             :  * @param expressions A list of expressions to be evaluated
     741             :  * @param dialect Expression dialect
     742             :  * @param flatten Generate a single band output raster per expression, even if
     743             :  *                input datasets are multiband.
     744             :  * @param noData NoData values to use for output bands, or "none", or ""
     745             :  * @param pixelFunctionArguments Pixel function arguments.
     746             :  * @param options flags controlling which checks should be performed on the inputs
     747             :  * @param[out] maxSourceBands Maximum number of bands in source dataset(s)
     748             :  * @param fakeSourceFilename If not empty, used instead of real input filenames.
     749             :  *
     750             :  * @return a newly created VRTDataset, or nullptr on error
     751             :  */
     752         122 : static std::unique_ptr<GDALDataset> GDALCalcCreateVRTDerived(
     753             :     const char *pszVRTFilename, const std::vector<std::string> &inputs,
     754             :     const std::vector<std::string> &expressions, const std::string &dialect,
     755             :     bool flatten, const std::string &noData,
     756             :     const std::vector<std::vector<std::string>> &pixelFunctionArguments,
     757             :     const GDALCalcOptions &options, int &maxSourceBands,
     758             :     const std::string &fakeSourceFilename = std::string())
     759             : {
     760         122 :     if (inputs.empty())
     761             :     {
     762           0 :         return nullptr;
     763             :     }
     764             : 
     765         244 :     std::map<std::string, std::string> sources;
     766         244 :     std::string firstSource;
     767         122 :     bool requireSourceNames = dialect != "builtin";
     768         122 :     if (!ParseSourceDescriptors(inputs, sources, firstSource,
     769             :                                 requireSourceNames))
     770             :     {
     771           5 :         return nullptr;
     772             :     }
     773             : 
     774             :     // Use the first source provided to determine properties of the output
     775         117 :     const char *firstDSN = sources[firstSource].c_str();
     776             : 
     777         117 :     maxSourceBands = 0;
     778             : 
     779             :     // Read properties from the first source
     780         234 :     SourceProperties out;
     781             :     {
     782             :         std::unique_ptr<GDALDataset> ds(
     783         117 :             GDALDataset::Open(firstDSN, GDAL_OF_RASTER));
     784             : 
     785         117 :         if (!ds)
     786             :         {
     787           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Failed to open %s",
     788             :                      firstDSN);
     789           0 :             return nullptr;
     790             :         }
     791             : 
     792         117 :         out.nX = ds->GetRasterXSize();
     793         117 :         out.nY = ds->GetRasterYSize();
     794         117 :         out.nBands = 1;
     795             :         out.srs =
     796         117 :             OGRSpatialReferenceRefCountedPtr::makeClone(ds->GetSpatialRef());
     797         117 :         out.hasGT = ds->GetGeoTransform(out.gt) == CE_None;
     798             :     }
     799             : 
     800         234 :     CPLXMLTreeCloser root(CPLCreateXMLNode(nullptr, CXT_Element, "VRTDataset"));
     801             : 
     802         117 :     maxSourceBands = 0;
     803             : 
     804             :     // Collect properties of the different sources, and verity them for
     805             :     // consistency.
     806         234 :     std::map<std::string, SourceProperties> sourceProps;
     807         269 :     for (const auto &[source_name, dsn] : sources)
     808             :     {
     809             :         // TODO avoid opening the first source twice.
     810         156 :         auto props = UpdateSourceProperties(out, dsn, options);
     811         156 :         if (props.has_value())
     812             :         {
     813         152 :             maxSourceBands = std::max(maxSourceBands, props->nBands);
     814         152 :             sourceProps[source_name] = std::move(props.value());
     815             :         }
     816             :         else
     817             :         {
     818           4 :             return nullptr;
     819             :         }
     820             :     }
     821             : 
     822         113 :     size_t iExpr = 0;
     823         225 :     for (const auto &origExpression : expressions)
     824             :     {
     825         116 :         GDALDataType bandType = options.dstType;
     826             : 
     827             :         // If output band type has not been specified, set it equal to the
     828             :         // input band type for certain pixel functions, if the inputs have
     829             :         // a consistent band type.
     830         166 :         if (bandType == GDT_Unknown && dialect == "builtin" &&
     831          72 :             (origExpression == "min" || origExpression == "max" ||
     832          22 :              origExpression == "mode"))
     833             :         {
     834          12 :             for (const auto &[_, props] : sourceProps)
     835             :             {
     836           6 :                 if (bandType == GDT_Unknown)
     837             :                 {
     838           6 :                     bandType = props.eDT;
     839             :                 }
     840           0 :                 else if (props.eDT == GDT_Unknown || props.eDT != bandType)
     841             :                 {
     842           0 :                     bandType = GDT_Unknown;
     843           0 :                     break;
     844             :                 }
     845             :             }
     846             :         }
     847             : 
     848         116 :         if (!CreateDerivedBandXML(pszVRTFilename, root.get(), out.nX, out.nY,
     849             :                                   bandType, origExpression, dialect, flatten,
     850         116 :                                   noData, pixelFunctionArguments[iExpr],
     851             :                                   sources, sourceProps, fakeSourceFilename))
     852             :         {
     853           4 :             return nullptr;
     854             :         }
     855         112 :         ++iExpr;
     856             :     }
     857             : 
     858             :     // CPLDebug("VRT", "%s", CPLSerializeXMLTree(root.get()));
     859             : 
     860         109 :     auto ds = fakeSourceFilename.empty()
     861             :                   ? std::make_unique<VRTDataset>(out.nX, out.nY)
     862         218 :                   : std::make_unique<VRTDataset>(1, 1);
     863         218 :     if (ds->XMLInit(root.get(), pszVRTFilename[0]
     864         126 :                                     ? CPLGetPathSafe(pszVRTFilename).c_str()
     865         218 :                                     : "") != CE_None)
     866             :     {
     867           0 :         return nullptr;
     868             :     };
     869         109 :     if (out.hasGT)
     870             :     {
     871          57 :         ds->SetGeoTransform(out.gt);
     872             :     }
     873         109 :     if (out.srs)
     874             :     {
     875          55 :         ds->SetSpatialRef(out.srs.get());
     876             :     }
     877             : 
     878         109 :     return ds;
     879             : }
     880             : 
     881             : /************************************************************************/
     882             : /*          GDALRasterCalcAlgorithm::GDALRasterCalcAlgorithm()          */
     883             : /************************************************************************/
     884             : 
     885         177 : GDALRasterCalcAlgorithm::GDALRasterCalcAlgorithm(bool standaloneStep) noexcept
     886             :     : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
     887         531 :                                       ConstructorOptions()
     888         177 :                                           .SetStandaloneStep(standaloneStep)
     889         177 :                                           .SetAddDefaultArguments(false)
     890         177 :                                           .SetAutoOpenInputDatasets(false)
     891         177 :                                           .SetInputDatasetInputFlags(GADV_NAME)
     892         354 :                                           .SetInputDatasetMetaVar("INPUTS")
     893         531 :                                           .SetInputDatasetMaxCount(INT_MAX))
     894             : {
     895         177 :     AddRasterInputArgs(false, false);
     896         177 :     if (standaloneStep)
     897             :     {
     898         138 :         AddProgressArg();
     899         138 :         AddRasterOutputArgs(false);
     900             :     }
     901             : 
     902         177 :     AddOutputDataTypeArg(&m_type);
     903             : 
     904             :     AddArg("no-check-crs", 0,
     905             :            _("Do not check consistency of input coordinate reference systems"),
     906         354 :            &m_noCheckCRS)
     907         177 :         .AddHiddenAlias("no-check-srs");
     908             :     AddArg("no-check-extent", 0, _("Do not check consistency of input extents"),
     909         177 :            &m_noCheckExtent);
     910             : 
     911             :     AddArg("propagate-nodata", 0,
     912             :            _("Whether to set pixels to the output NoData value if any of the "
     913             :              "input pixels is NoData"),
     914         177 :            &m_propagateNoData);
     915             : 
     916         354 :     AddArg("calc", 0, _("Expression(s) to evaluate"), &m_expr)
     917         177 :         .SetRequired()
     918         177 :         .SetPackedValuesAllowed(false)
     919         177 :         .SetMinCount(1)
     920             :         .SetAutoCompleteFunction(
     921           4 :             [this](const std::string &currentValue)
     922             :             {
     923           4 :                 std::vector<std::string> ret;
     924           2 :                 if (m_dialect == "builtin")
     925             :                 {
     926           1 :                     if (currentValue.find('(') == std::string::npos)
     927           1 :                         return VRTDerivedRasterBand::GetPixelFunctionNames();
     928             :                 }
     929           1 :                 return ret;
     930         177 :             });
     931             : 
     932         354 :     AddArg("dialect", 0, _("Expression dialect"), &m_dialect)
     933         177 :         .SetDefault(m_dialect)
     934         177 :         .SetChoices("muparser", "builtin");
     935             : 
     936             :     AddArg("flatten", 0,
     937             :            _("Generate a single band output raster per expression, even if "
     938             :              "input datasets are multiband"),
     939         177 :            &m_flatten);
     940             : 
     941         177 :     AddNodataArg(&m_nodata, true);
     942             : 
     943             :     // This is a hidden option only used by test_gdalalg_raster_calc_expression_rewriting()
     944             :     // for now
     945             :     AddArg("no-check-expression", 0,
     946             :            _("Whether to skip expression validity checks for virtual format "
     947             :              "output"),
     948         354 :            &m_noCheckExpression)
     949         177 :         .SetHidden();
     950             : 
     951         177 :     AddValidationAction(
     952         178 :         [this]()
     953             :         {
     954          94 :             GDALPipelineStepRunContext ctxt;
     955          94 :             return m_noCheckExpression || !IsGDALGOutput() || RunStep(ctxt);
     956             :         });
     957         177 : }
     958             : 
     959             : /************************************************************************/
     960             : /*                  GDALRasterCalcAlgorithm::RunImpl()                  */
     961             : /************************************************************************/
     962             : 
     963          85 : bool GDALRasterCalcAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     964             :                                       void *pProgressData)
     965             : {
     966          85 :     GDALPipelineStepRunContext stepCtxt;
     967          85 :     stepCtxt.m_pfnProgress = pfnProgress;
     968          85 :     stepCtxt.m_pProgressData = pProgressData;
     969          85 :     return RunPreStepPipelineValidations() && RunStep(stepCtxt);
     970             : }
     971             : 
     972             : /************************************************************************/
     973             : /*                  GDALRasterCalcAlgorithm::RunStep()                  */
     974             : /************************************************************************/
     975             : 
     976          90 : bool GDALRasterCalcAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     977             : {
     978          90 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     979             : 
     980          90 :     GDALCalcOptions options;
     981          90 :     options.checkExtent = !m_noCheckExtent;
     982          90 :     options.checkCRS = !m_noCheckCRS;
     983          90 :     if (!m_type.empty())
     984             :     {
     985           5 :         options.dstType = GDALGetDataTypeByName(m_type.c_str());
     986             :     }
     987             : 
     988         180 :     std::vector<std::string> inputFilenames;
     989          90 :     if (!ReadFileLists(m_inputDataset, inputFilenames))
     990             :     {
     991           0 :         return false;
     992             :     }
     993             : 
     994         180 :     std::vector<std::vector<std::string>> pixelFunctionArgs;
     995          90 :     if (m_dialect == "builtin")
     996             :     {
     997          27 :         for (std::string &expr : m_expr)
     998             :         {
     999             :             const CPLStringList aosTokens(
    1000             :                 CSLTokenizeString2(expr.c_str(), "()",
    1001          14 :                                    CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
    1002          14 :             const char *pszFunction = aosTokens[0];
    1003             :             const auto *pair =
    1004          14 :                 VRTDerivedRasterBand::GetPixelFunction(pszFunction);
    1005          14 :             if (!pair)
    1006             :             {
    1007           0 :                 ReportError(CE_Failure, CPLE_NotSupported,
    1008             :                             "'%s' is a unknown builtin function", pszFunction);
    1009           0 :                 return false;
    1010             :             }
    1011          14 :             if (aosTokens.size() == 2)
    1012             :             {
    1013           2 :                 std::vector<std::string> validArguments;
    1014           2 :                 AddOptionsSuggestions(pair->second.c_str(), 0, std::string(),
    1015             :                                       validArguments);
    1016           6 :                 for (std::string &s : validArguments)
    1017             :                 {
    1018           4 :                     if (!s.empty() && s.back() == '=')
    1019           4 :                         s.pop_back();
    1020             :                 }
    1021             : 
    1022             :                 const CPLStringList aosTokensArgs(CSLTokenizeString2(
    1023             :                     aosTokens[1], ",",
    1024           2 :                     CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
    1025           4 :                 for (const auto &[key, value] :
    1026           6 :                      cpl::IterateNameValue(aosTokensArgs))
    1027             :                 {
    1028           2 :                     if (std::find(validArguments.begin(), validArguments.end(),
    1029           2 :                                   key) == validArguments.end())
    1030             :                     {
    1031           0 :                         if (validArguments.empty())
    1032             :                         {
    1033           0 :                             ReportError(
    1034             :                                 CE_Failure, CPLE_IllegalArg,
    1035             :                                 "'%s' is a unrecognized argument for builtin "
    1036             :                                 "function '%s'. It does not accept any "
    1037             :                                 "argument",
    1038             :                                 key, pszFunction);
    1039             :                         }
    1040             :                         else
    1041             :                         {
    1042           0 :                             std::string validArgumentsStr;
    1043           0 :                             for (const std::string &s : validArguments)
    1044             :                             {
    1045           0 :                                 if (!validArgumentsStr.empty())
    1046           0 :                                     validArgumentsStr += ", ";
    1047           0 :                                 validArgumentsStr += '\'';
    1048           0 :                                 validArgumentsStr += s;
    1049           0 :                                 validArgumentsStr += '\'';
    1050             :                             }
    1051           0 :                             ReportError(
    1052             :                                 CE_Failure, CPLE_IllegalArg,
    1053             :                                 "'%s' is a unrecognized argument for builtin "
    1054             :                                 "function '%s'. Only %s %s supported",
    1055             :                                 key, pszFunction,
    1056           0 :                                 validArguments.size() == 1 ? "is" : "are",
    1057             :                                 validArgumentsStr.c_str());
    1058             :                         }
    1059           0 :                         return false;
    1060             :                     }
    1061           2 :                     CPL_IGNORE_RET_VAL(value);
    1062             :                 }
    1063           2 :                 pixelFunctionArgs.emplace_back(aosTokensArgs);
    1064             :             }
    1065             :             else
    1066             :             {
    1067          12 :                 pixelFunctionArgs.push_back(std::vector<std::string>());
    1068             :             }
    1069          14 :             expr = pszFunction;
    1070             :         }
    1071             :     }
    1072             :     else
    1073             :     {
    1074          77 :         pixelFunctionArgs.resize(m_expr.size());
    1075             :     }
    1076             : 
    1077          90 :     if (m_propagateNoData)
    1078             :     {
    1079           2 :         if (m_nodata == "none")
    1080             :         {
    1081           0 :             ReportError(CE_Failure, CPLE_AppDefined,
    1082             :                         "Output NoData value must be specified to use "
    1083             :                         "--propagate-nodata");
    1084           0 :             return false;
    1085             :         }
    1086           4 :         for (auto &args : pixelFunctionArgs)
    1087             :         {
    1088           2 :             args.push_back("propagateNoData=1");
    1089             :         }
    1090             :     }
    1091             : 
    1092          90 :     int maxSourceBands = 0;
    1093             :     const bool bIsVRT =
    1094         230 :         m_format == "VRT" ||
    1095          89 :         (m_format.empty() &&
    1096         102 :          EQUAL(CPLGetExtensionSafe(m_outputDataset.GetName().c_str()).c_str(),
    1097          90 :                "VRT"));
    1098             : 
    1099             :     auto vrt = GDALCalcCreateVRTDerived(
    1100          19 :         bIsVRT ? m_outputDataset.GetName().c_str() : "", inputFilenames, m_expr,
    1101          90 :         m_dialect, m_flatten, m_nodata, pixelFunctionArgs, options,
    1102         199 :         maxSourceBands);
    1103          90 :     if (vrt == nullptr)
    1104             :     {
    1105          13 :         return false;
    1106             :     }
    1107             : 
    1108          77 :     if (!m_noCheckExpression)
    1109             :     {
    1110             :         const bool bIsGDALG =
    1111         157 :             m_format == "GDALG" ||
    1112          63 :             (m_format.empty() &&
    1113          30 :              cpl::ends_with(m_outputDataset.GetName(), ".gdalg.json"));
    1114          64 :         if (!m_standaloneStep || m_format == "stream" || bIsVRT || bIsGDALG)
    1115             :         {
    1116             :             // Try reading a single pixel to check formulas are valid.
    1117          32 :             std::vector<GByte> dummyData(vrt->GetRasterCount());
    1118             : 
    1119          32 :             auto poGTIFFDrv = GetGDALDriverManager()->GetDriverByName("GTiff");
    1120          32 :             std::string osTmpFilename;
    1121          32 :             if (poGTIFFDrv)
    1122             :             {
    1123             :                 std::string osFilename =
    1124          64 :                     VSIMemGenerateHiddenFilename("tmp.tif");
    1125             :                 auto poDS = std::unique_ptr<GDALDataset>(
    1126             :                     poGTIFFDrv->Create(osFilename.c_str(), 1, 1, maxSourceBands,
    1127          64 :                                        GDT_UInt8, nullptr));
    1128          32 :                 if (poDS)
    1129          32 :                     osTmpFilename = std::move(osFilename);
    1130             :             }
    1131          32 :             if (!osTmpFilename.empty())
    1132             :             {
    1133             :                 auto fakeVRT = GDALCalcCreateVRTDerived(
    1134          32 :                     "", inputFilenames, m_expr, m_dialect, m_flatten, m_nodata,
    1135          32 :                     pixelFunctionArgs, options, maxSourceBands, osTmpFilename);
    1136          64 :                 if (fakeVRT &&
    1137          32 :                     fakeVRT->RasterIO(GF_Read, 0, 0, 1, 1, dummyData.data(), 1,
    1138             :                                       1, GDT_UInt8, vrt->GetRasterCount(),
    1139          32 :                                       nullptr, 0, 0, 0, nullptr) != CE_None)
    1140             :                 {
    1141           5 :                     return false;
    1142             :                 }
    1143             :             }
    1144          27 :             if (bIsGDALG)
    1145             :             {
    1146           1 :                 return true;
    1147             :             }
    1148             :         }
    1149             :     }
    1150             : 
    1151          71 :     if (m_format == "stream" || !m_standaloneStep)
    1152             :     {
    1153          24 :         m_outputDataset.Set(std::move(vrt));
    1154          24 :         return true;
    1155             :     }
    1156             : 
    1157          47 :     CPLStringList translateArgs;
    1158          47 :     if (!m_format.empty())
    1159             :     {
    1160           9 :         translateArgs.AddString("-of");
    1161           9 :         translateArgs.AddString(m_format.c_str());
    1162             :     }
    1163          48 :     for (const auto &co : m_creationOptions)
    1164             :     {
    1165           1 :         translateArgs.AddString("-co");
    1166           1 :         translateArgs.AddString(co.c_str());
    1167             :     }
    1168             : 
    1169          47 :     bool bOK = false;
    1170             :     GDALTranslateOptions *translateOptions =
    1171          47 :         GDALTranslateOptionsNew(translateArgs.List(), nullptr);
    1172          47 :     if (translateOptions)
    1173             :     {
    1174          47 :         GDALTranslateOptionsSetProgress(translateOptions, ctxt.m_pfnProgress,
    1175             :                                         ctxt.m_pProgressData);
    1176             : 
    1177             :         auto poOutDS =
    1178             :             std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(GDALTranslate(
    1179          47 :                 m_outputDataset.GetName().c_str(),
    1180          94 :                 GDALDataset::ToHandle(vrt.get()), translateOptions, nullptr)));
    1181          47 :         GDALTranslateOptionsFree(translateOptions);
    1182             : 
    1183          47 :         bOK = poOutDS != nullptr;
    1184          47 :         m_outputDataset.Set(std::move(poOutDS));
    1185             :     }
    1186             : 
    1187          47 :     return bOK;
    1188             : }
    1189             : 
    1190             : GDALRasterCalcAlgorithmStandalone::~GDALRasterCalcAlgorithmStandalone() =
    1191             :     default;
    1192             : 
    1193             : //! @endcond

Generated by: LCOV version 1.14