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

Generated by: LCOV version 1.14