• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.23
/apps/gdalalg_raster_calc.cpp
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 checkSRS{true};
37
    bool checkExtent{true};
38
};
39

40
static bool MatchIsCompleteVariableNameWithNoIndex(const std::string &str,
234✔
41
                                                   size_t from, size_t to)
42
{
43
    if (to < str.size())
234✔
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
        if (std::isalnum(str[to]) || str[to] == '_' || str[to] == '[' ||
300✔
50
            str[to] == '(')
104✔
51
        {
52
            return false;
93✔
53
        }
54
    }
55
    if (from > 0)
141✔
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
        if (std::isalnum(str[from - 1]) || str[from - 1] == '_')
89✔
60
        {
61
            return false;
3✔
62
        }
63
    }
64

65
    return true;
138✔
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
static std::string SetBandIndices(const std::string &origExpression,
138✔
74
                                  const std::string &variable, int band,
75
                                  bool &expressionChanged)
76
{
77
    std::string expression = origExpression;
138✔
78
    expressionChanged = false;
138✔
79

80
    std::string::size_type seekPos = 0;
138✔
81
    auto pos = expression.find(variable, seekPos);
138✔
82
    while (pos != std::string::npos)
336✔
83
    {
84
        auto end = pos + variable.size();
198✔
85

86
        if (MatchIsCompleteVariableNameWithNoIndex(expression, pos, end))
198✔
87
        {
88
            // No index specified for variable
89
            expression = expression.substr(0, pos + variable.size()) + '[' +
204✔
90
                         std::to_string(band) + ']' + expression.substr(end);
306✔
91
            expressionChanged = true;
102✔
92
        }
93

94
        seekPos = end;
198✔
95
        pos = expression.find(variable, seekPos);
198✔
96
    }
97

98
    return expression;
138✔
99
}
100

101
static bool PosIsAggregateFunctionArgument(const std::string &expression,
72✔
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
    while (pos != 0)
72✔
107
    {
108
        const char c = expression[pos];
64✔
109
        if (c == '(')
64✔
110
        {
111
            pos--;
24✔
112
            break;
24✔
113
        }
114
        if (!(isspace(c) || isalnum(c) || c == ',' || c == '.' || c == '[' ||
40✔
115
              c == ']' || c == '_'))
116
        {
117
            return false;
4✔
118
        }
119
        pos--;
36✔
120
    }
121

122
    // Now what we've found the (, the preceding characters should be an
123
    // aggregate function name
124
    if (pos < 2)
32✔
125
    {
126
        return false;
8✔
127
    }
128

129
    if (STARTS_WITH_CI(expression.c_str() + (pos - 2), "avg") ||
24✔
130
        STARTS_WITH_CI(expression.c_str() + (pos - 2), "sum") ||
20✔
131
        STARTS_WITH_CI(expression.c_str() + (pos - 2), "min") ||
52✔
132
        STARTS_WITH_CI(expression.c_str() + (pos - 2), "max"))
8✔
133
    {
134
        return true;
20✔
135
    }
136

137
    return false;
4✔
138
}
139

140
/**
141
 *  Replace X by X[1],X[2],...X[n]
142
 */
143
static std::string
144
SetBandIndicesFlattenedExpression(const std::string &origExpression,
32✔
145
                                  const std::string &variable, int nBands)
146
{
147
    std::string expression = origExpression;
32✔
148

149
    std::string::size_type seekPos = 0;
32✔
150
    auto pos = expression.find(variable, seekPos);
32✔
151
    while (pos != std::string::npos)
68✔
152
    {
153
        auto end = pos + variable.size();
36✔
154

155
        if (MatchIsCompleteVariableNameWithNoIndex(expression, pos, end) &&
72✔
156
            PosIsAggregateFunctionArgument(expression, pos))
36✔
157
        {
158
            std::string newExpr = expression.substr(0, pos);
20✔
159
            for (int i = 1; i <= nBands; ++i)
68✔
160
            {
161
                if (i > 1)
48✔
162
                    newExpr += ',';
28✔
163
                newExpr += variable;
48✔
164
                newExpr += '[';
48✔
165
                newExpr += std::to_string(i);
48✔
166
                newExpr += ']';
48✔
167
            }
168
            const size_t oldExprSize = expression.size();
20✔
169
            newExpr += expression.substr(end);
20✔
170
            expression = std::move(newExpr);
20✔
171
            end += expression.size() - oldExprSize;
20✔
172
        }
173

174
        seekPos = end;
36✔
175
        pos = expression.find(variable, seekPos);
36✔
176
    }
177

178
    return expression;
32✔
179
}
180

181
struct SourceProperties
182
{
183
    int nBands{0};
184
    int nX{0};
185
    int nY{0};
186
    GDALGeoTransform gt{};
187
    std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> srs{
188
        nullptr};
189
    std::vector<std::optional<double>> noData{};
190
    GDALDataType eDT{GDT_Unknown};
191
};
192

193
static std::optional<SourceProperties>
194
UpdateSourceProperties(SourceProperties &out, const std::string &dsn,
146✔
195
                       const GDALCalcOptions &options)
196
{
197
    SourceProperties source;
292✔
198
    bool srsMismatch = false;
146✔
199
    bool extentMismatch = false;
146✔
200
    bool dimensionMismatch = false;
146✔
201

202
    {
203
        std::unique_ptr<GDALDataset> ds(
204
            GDALDataset::Open(dsn.c_str(), GDAL_OF_RASTER));
146✔
205

206
        if (!ds)
146✔
207
        {
208
            CPLError(CE_Failure, CPLE_AppDefined, "Failed to open %s",
×
209
                     dsn.c_str());
210
            return std::nullopt;
×
211
        }
212

213
        source.nBands = ds->GetRasterCount();
146✔
214
        source.nX = ds->GetRasterXSize();
146✔
215
        source.nY = ds->GetRasterYSize();
146✔
216
        source.noData.resize(source.nBands);
146✔
217

218
        if (options.checkExtent)
146✔
219
        {
220
            ds->GetGeoTransform(source.gt);
142✔
221
        }
222

223
        if (options.checkSRS && out.srs)
146✔
224
        {
225
            const OGRSpatialReference *srs = ds->GetSpatialRef();
54✔
226
            srsMismatch = srs && !srs->IsSame(out.srs.get());
54✔
227
        }
228

229
        // Store the source data type if it is the same for all bands in the source
230
        bool bandsHaveSameType = true;
146✔
231
        for (int i = 1; i <= source.nBands; ++i)
392✔
232
        {
233
            GDALRasterBand *band = ds->GetRasterBand(i);
246✔
234

235
            if (i == 1)
246✔
236
            {
237
                source.eDT = band->GetRasterDataType();
146✔
238
            }
239
            else if (bandsHaveSameType &&
200✔
240
                     source.eDT != band->GetRasterDataType())
100✔
241
            {
242
                source.eDT = GDT_Unknown;
×
243
                bandsHaveSameType = false;
×
244
            }
245

246
            int success;
247
            double noData = band->GetNoDataValue(&success);
246✔
248
            if (success)
246✔
249
            {
250
                source.noData[i - 1] = noData;
15✔
251
            }
252
        }
253
    }
254

255
    if (source.nX != out.nX || source.nY != out.nY)
146✔
256
    {
257
        dimensionMismatch = true;
3✔
258
    }
259

260
    if (source.gt[0] != out.gt[0] || source.gt[2] != out.gt[2] ||
292✔
261
        source.gt[3] != out.gt[3] || source.gt[4] != out.gt[4])
292✔
262
    {
263
        extentMismatch = true;
5✔
264
    }
265
    if (source.gt[1] != out.gt[1] || source.gt[5] != out.gt[5])
146✔
266
    {
267
        // Resolutions are different. Are the extents the same?
268
        double xmaxOut = out.gt[0] + out.nX * out.gt[1] + out.nY * out.gt[2];
8✔
269
        double yminOut = out.gt[3] + out.nX * out.gt[4] + out.nY * out.gt[5];
8✔
270

271
        double xmax =
272
            source.gt[0] + source.nX * source.gt[1] + source.nY * source.gt[2];
8✔
273
        double ymin =
274
            source.gt[3] + source.nX * source.gt[4] + source.nY * source.gt[5];
8✔
275

276
        // Max allowable extent misalignment, expressed as fraction of a pixel
277
        constexpr double EXTENT_RTOL = 1e-3;
8✔
278

279
        if (std::abs(xmax - xmaxOut) > EXTENT_RTOL * std::abs(source.gt[1]) ||
13✔
280
            std::abs(ymin - yminOut) > EXTENT_RTOL * std::abs(source.gt[5]))
5✔
281
        {
282
            extentMismatch = true;
5✔
283
        }
284
    }
285

286
    if (options.checkExtent && extentMismatch)
146✔
287
    {
288
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
289
                 "Input extents are inconsistent.");
290
        return std::nullopt;
1✔
291
    }
292

293
    if (!options.checkExtent && dimensionMismatch)
145✔
294
    {
295
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
296
                 "Inputs do not have the same dimensions.");
297
        return std::nullopt;
1✔
298
    }
299

300
    // Find a common resolution
301
    if (source.nX > out.nX)
144✔
302
    {
303
        auto dx = CPLGreatestCommonDivisor(out.gt[1], source.gt[1]);
1✔
304
        if (dx == 0)
1✔
305
        {
306
            CPLError(CE_Failure, CPLE_AppDefined,
×
307
                     "Failed to find common resolution for inputs.");
308
            return std::nullopt;
×
309
        }
310
        out.nX = static_cast<int>(
1✔
311
            std::round(static_cast<double>(out.nX) * out.gt[1] / dx));
1✔
312
        out.gt[1] = dx;
1✔
313
    }
314
    if (source.nY > out.nY)
144✔
315
    {
316
        auto dy = CPLGreatestCommonDivisor(out.gt[5], source.gt[5]);
1✔
317
        if (dy == 0)
1✔
318
        {
319
            CPLError(CE_Failure, CPLE_AppDefined,
×
320
                     "Failed to find common resolution for inputs.");
321
            return std::nullopt;
×
322
        }
323
        out.nY = static_cast<int>(
1✔
324
            std::round(static_cast<double>(out.nY) * out.gt[5] / dy));
1✔
325
        out.gt[5] = dy;
1✔
326
    }
327

328
    if (srsMismatch)
144✔
329
    {
330
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
331
                 "Input spatial reference systems are inconsistent.");
332
        return std::nullopt;
1✔
333
    }
334

335
    return source;
143✔
336
}
337

338
/** Create XML nodes for one or more derived bands resulting from the evaluation
339
 *  of a single expression
340
 *
341
 * @param root VRTDataset node to which the band nodes should be added
342
 * @param bandType the type of the band(s) to create
343
 * @param nXOut Number of columns in VRT dataset
344
 * @param nYOut Number of rows in VRT dataset
345
 * @param expression Expression for which band(s) should be added
346
 * @param dialect Expression dialect
347
 * @param flatten Generate a single band output raster per expression, even if
348
 *                input datasets are multiband.
349
 * @param noDataText nodata value to use for the created band, or "none", or ""
350
 * @param pixelFunctionArguments Pixel function arguments.
351
 * @param sources Mapping of source names to DSNs
352
 * @param sourceProps Mapping of source names to properties
353
 * @param fakeSourceFilename If not empty, used instead of real input filenames.
354
 * @return true if the band(s) were added, false otherwise
355
 */
356
static bool
357
CreateDerivedBandXML(CPLXMLNode *root, int nXOut, int nYOut,
110✔
358
                     GDALDataType bandType, const std::string &expression,
359
                     const std::string &dialect, bool flatten,
360
                     const std::string &noDataText,
361
                     const std::vector<std::string> &pixelFunctionArguments,
362
                     const std::map<std::string, std::string> &sources,
363
                     const std::map<std::string, SourceProperties> &sourceProps,
364
                     const std::string &fakeSourceFilename)
365
{
366
    int nOutBands = 1;  // By default, each expression produces a single output
110✔
367
                        // band. When processing the expression below, we may
368
                        // discover that the expression produces multiple bands,
369
                        // in which case this will be updated.
370

371
    for (int nOutBand = 1; nOutBand <= nOutBands; nOutBand++)
243✔
372
    {
373
        // Copy the expression for each output band, because we may modify it
374
        // when adding band indices (e.g., X -> X[1]) to the variables in the
375
        // expression.
376
        std::string bandExpression = expression;
137✔
377

378
        CPLXMLNode *band = CPLCreateXMLNode(root, CXT_Element, "VRTRasterBand");
137✔
379
        CPLAddXMLAttributeAndValue(band, "subClass", "VRTDerivedRasterBand");
137✔
380
        if (bandType == GDT_Unknown)
137✔
381
        {
382
            bandType = GDT_Float64;
98✔
383
        }
384
        CPLAddXMLAttributeAndValue(band, "dataType",
137✔
385
                                   GDALGetDataTypeName(bandType));
386

387
        std::optional<double> dstNoData;
137✔
388
        bool autoSelectNoDataValue = false;
137✔
389
        if (noDataText.empty())
137✔
390
        {
391
            autoSelectNoDataValue = true;
132✔
392
        }
393
        else if (noDataText != "none")
5✔
394
        {
395
            char *end;
396
            dstNoData = CPLStrtod(noDataText.c_str(), &end);
5✔
397
            if (end != noDataText.c_str() + noDataText.size())
5✔
398
            {
399
                CPLError(CE_Failure, CPLE_AppDefined,
×
400
                         "Invalid NoData value: %s", noDataText.c_str());
401
                return false;
×
402
            }
403
        }
404

405
        for (const auto &[source_name, dsn] : sources)
313✔
406
        {
407
            auto it = sourceProps.find(source_name);
180✔
408
            CPLAssert(it != sourceProps.end());
180✔
409
            const auto &props = it->second;
180✔
410

411
            bool expressionAppliedPerBand = false;
180✔
412
            if (dialect == "builtin")
180✔
413
            {
414
                expressionAppliedPerBand = !flatten;
42✔
415
            }
416
            else
417
            {
418
                const int nDefaultInBand = std::min(props.nBands, nOutBand);
138✔
419

420
                if (flatten)
138✔
421
                {
422
                    bandExpression = SetBandIndicesFlattenedExpression(
32✔
423
                        bandExpression, source_name, props.nBands);
32✔
424
                }
425

426
                bandExpression =
427
                    SetBandIndices(bandExpression, source_name, nDefaultInBand,
276✔
428
                                   expressionAppliedPerBand);
138✔
429
            }
430

431
            if (expressionAppliedPerBand)
180✔
432
            {
433
                if (nOutBands <= 1)
126✔
434
                {
435
                    nOutBands = props.nBands;
85✔
436
                }
437
                else if (props.nBands != 1 && props.nBands != nOutBands)
41✔
438
                {
439
                    CPLError(CE_Failure, CPLE_AppDefined,
3✔
440
                             "Expression cannot operate on all bands of "
441
                             "rasters with incompatible numbers of bands "
442
                             "(source %s has %d bands but expected to have "
443
                             "1 or %d bands).",
444
                             source_name.c_str(), props.nBands, nOutBands);
3✔
445
                    return false;
4✔
446
                }
447
            }
448

449
            // Create a source for each input band that is used in
450
            // the expression.
451
            for (int nInBand = 1; nInBand <= props.nBands; nInBand++)
497✔
452
            {
453
                CPLString inBandVariable;
320✔
454
                if (dialect == "builtin")
320✔
455
                {
456
                    if (!flatten && props.nBands >= 2 && nInBand != nOutBand)
72✔
457
                        continue;
11✔
458
                }
459
                else
460
                {
461
                    inBandVariable.Printf("%s[%d]", source_name.c_str(),
462
                                          nInBand);
248✔
463
                    if (bandExpression.find(inBandVariable) ==
248✔
464
                        std::string::npos)
465
                    {
466
                        continue;
79✔
467
                    }
468
                }
469

470
                const std::optional<double> &srcNoData =
471
                    props.noData[nInBand - 1];
230✔
472

473
                CPLXMLNode *source = CPLCreateXMLNode(
230✔
474
                    band, CXT_Element,
475
                    srcNoData.has_value() ? "ComplexSource" : "SimpleSource");
230✔
476
                if (!inBandVariable.empty())
230✔
477
                {
478
                    CPLAddXMLAttributeAndValue(source, "name",
169✔
479
                                               inBandVariable.c_str());
480
                }
481

482
                CPLXMLNode *sourceFilename =
483
                    CPLCreateXMLNode(source, CXT_Element, "SourceFilename");
230✔
484
                if (fakeSourceFilename.empty())
230✔
485
                {
486
                    CPLAddXMLAttributeAndValue(sourceFilename, "relativeToVRT",
158✔
487
                                               "0");
488
                    CPLCreateXMLNode(sourceFilename, CXT_Text, dsn.c_str());
158✔
489
                }
490
                else
491
                {
492
                    CPLCreateXMLNode(sourceFilename, CXT_Text,
72✔
493
                                     fakeSourceFilename.c_str());
494
                }
495

496
                CPLXMLNode *sourceBand =
497
                    CPLCreateXMLNode(source, CXT_Element, "SourceBand");
230✔
498
                CPLCreateXMLNode(sourceBand, CXT_Text,
230✔
499
                                 std::to_string(nInBand).c_str());
460✔
500

501
                if (srcNoData.has_value())
230✔
502
                {
503
                    CPLXMLNode *srcNoDataNode =
504
                        CPLCreateXMLNode(source, CXT_Element, "NODATA");
15✔
505
                    std::string srcNoDataText =
506
                        CPLSPrintf("%.17g", srcNoData.value());
30✔
507
                    CPLCreateXMLNode(srcNoDataNode, CXT_Text,
15✔
508
                                     srcNoDataText.c_str());
509

510
                    if (autoSelectNoDataValue && !dstNoData.has_value())
15✔
511
                    {
512
                        dstNoData = srcNoData;
6✔
513
                    }
514
                }
515

516
                if (fakeSourceFilename.empty())
230✔
517
                {
518
                    CPLXMLNode *srcRect =
519
                        CPLCreateXMLNode(source, CXT_Element, "SrcRect");
158✔
520
                    CPLAddXMLAttributeAndValue(srcRect, "xOff", "0");
158✔
521
                    CPLAddXMLAttributeAndValue(srcRect, "yOff", "0");
158✔
522
                    CPLAddXMLAttributeAndValue(
158✔
523
                        srcRect, "xSize", std::to_string(props.nX).c_str());
316✔
524
                    CPLAddXMLAttributeAndValue(
158✔
525
                        srcRect, "ySize", std::to_string(props.nY).c_str());
316✔
526

527
                    CPLXMLNode *dstRect =
528
                        CPLCreateXMLNode(source, CXT_Element, "DstRect");
158✔
529
                    CPLAddXMLAttributeAndValue(dstRect, "xOff", "0");
158✔
530
                    CPLAddXMLAttributeAndValue(dstRect, "yOff", "0");
158✔
531
                    CPLAddXMLAttributeAndValue(dstRect, "xSize",
158✔
532
                                               std::to_string(nXOut).c_str());
316✔
533
                    CPLAddXMLAttributeAndValue(dstRect, "ySize",
158✔
534
                                               std::to_string(nYOut).c_str());
316✔
535
                }
536
            }
537

538
            if (dstNoData.has_value())
177✔
539
            {
540
                if (!GDALIsValueExactAs(dstNoData.value(), bandType))
15✔
541
                {
542
                    CPLError(
1✔
543
                        CE_Failure, CPLE_AppDefined,
544
                        "Band output type %s cannot represent NoData value %g",
545
                        GDALGetDataTypeName(bandType), dstNoData.value());
1✔
546
                    return false;
1✔
547
                }
548

549
                CPLXMLNode *noDataNode =
550
                    CPLCreateXMLNode(band, CXT_Element, "NoDataValue");
14✔
551
                CPLString dstNoDataText =
552
                    CPLSPrintf("%.17g", dstNoData.value());
28✔
553
                CPLCreateXMLNode(noDataNode, CXT_Text, dstNoDataText.c_str());
14✔
554
            }
555
        }
556

557
        CPLXMLNode *pixelFunctionType =
558
            CPLCreateXMLNode(band, CXT_Element, "PixelFunctionType");
133✔
559
        CPLXMLNode *arguments =
560
            CPLCreateXMLNode(band, CXT_Element, "PixelFunctionArguments");
133✔
561

562
        if (dialect == "builtin")
133✔
563
        {
564
            CPLCreateXMLNode(pixelFunctionType, CXT_Text, expression.c_str());
28✔
565
        }
566
        else
567
        {
568
            CPLCreateXMLNode(pixelFunctionType, CXT_Text, "expression");
105✔
569
            CPLAddXMLAttributeAndValue(arguments, "dialect", "muparser");
105✔
570
            // Add the expression as a last step, because we may modify the
571
            // expression as we iterate through the bands.
572
            CPLAddXMLAttributeAndValue(arguments, "expression",
105✔
573
                                       bandExpression.c_str());
574
        }
575

576
        if (!pixelFunctionArguments.empty())
133✔
577
        {
578
            const CPLStringList args(pixelFunctionArguments);
16✔
579
            for (const auto &[key, value] : cpl::IterateNameValue(args))
16✔
580
            {
581
                CPLAddXMLAttributeAndValue(arguments, key, value);
8✔
582
            }
583
        }
584
    }
585

586
    return true;
106✔
587
}
588

589
static bool ParseSourceDescriptors(const std::vector<std::string> &inputs,
115✔
590
                                   std::map<std::string, std::string> &datasets,
591
                                   std::string &firstSourceName,
592
                                   bool requireSourceNames)
593
{
594
    for (size_t iInput = 0; iInput < inputs.size(); iInput++)
264✔
595
    {
596
        const std::string &input = inputs[iInput];
154✔
597
        std::string name;
154✔
598

599
        const auto pos = input.find('=');
154✔
600
        if (pos == std::string::npos)
154✔
601
        {
602
            if (requireSourceNames && inputs.size() > 1)
59✔
603
            {
604
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
605
                         "Inputs must be named when more than one input is "
606
                         "provided.");
607
                return false;
1✔
608
            }
609
            name = "X";
58✔
610
            if (iInput > 0)
58✔
611
            {
612
                name += std::to_string(iInput);
2✔
613
            }
614
        }
615
        else
616
        {
617
            name = input.substr(0, pos);
95✔
618
        }
619

620
        // Check input name is legal
621
        for (size_t i = 0; i < name.size(); ++i)
327✔
622
        {
623
            const char c = name[i];
177✔
624
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
177✔
625
            {
626
                // ok
627
            }
628
            else if (c == '_' || (c >= '0' && c <= '9'))
20✔
629
            {
630
                if (i == 0)
19✔
631
                {
632
                    // Reserved constants in MuParser start with an underscore
633
                    CPLError(
2✔
634
                        CE_Failure, CPLE_AppDefined,
635
                        "Name '%s' is illegal because it starts with a '%c'",
636
                        name.c_str(), c);
637
                    return false;
2✔
638
                }
639
            }
640
            else
641
            {
642
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
643
                         "Name '%s' is illegal because character '%c' is not "
644
                         "allowed",
645
                         name.c_str(), c);
646
                return false;
1✔
647
            }
648
        }
649

650
        std::string dsn =
651
            (pos == std::string::npos) ? input : input.substr(pos + 1);
150✔
652
        if (datasets.find(name) != datasets.end())
150✔
653
        {
654
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
655
                     "An input with name '%s' has already been provided",
656
                     name.c_str());
657
            return false;
1✔
658
        }
659
        datasets[name] = std::move(dsn);
149✔
660

661
        if (iInput == 0)
149✔
662
        {
663
            firstSourceName = std::move(name);
111✔
664
        }
665
    }
666

667
    return true;
110✔
668
}
669

670
static bool ReadFileLists(const std::vector<GDALArgDatasetValue> &inputDS,
84✔
671
                          std::vector<std::string> &inputFilenames)
672
{
673
    for (const auto &dsVal : inputDS)
197✔
674
    {
675
        const auto &input = dsVal.GetName();
113✔
676
        if (!input.empty() && input[0] == '@')
113✔
677
        {
678
            auto f =
679
                VSIVirtualHandleUniquePtr(VSIFOpenL(input.c_str() + 1, "r"));
2✔
680
            if (!f)
2✔
681
            {
682
                CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
×
683
                         input.c_str() + 1);
×
684
                return false;
×
685
            }
686
            while (const char *filename = CPLReadLineL(f.get()))
6✔
687
            {
688
                inputFilenames.push_back(filename);
4✔
689
            }
4✔
690
        }
691
        else
692
        {
693
            inputFilenames.push_back(input);
111✔
694
        }
695
    }
696

697
    return true;
84✔
698
}
699

700
/** Creates a VRT datasource with one or more derived raster bands containing
701
 *  results of an expression.
702
 *
703
 * To make this work with muparser (which does not support vector types), we
704
 * do a simple parsing of the expression internally, transforming it into
705
 * multiple expressions with explicit band indices. For example, for a two-band
706
 * raster "X", the expression "X + 3" will be transformed into "X[1] + 3" and
707
 * "X[2] + 3". The use of brackets is for readability only; as far as the
708
 * expression engine is concerned, the variables "X[1]" and "X[2]" have nothing
709
 * to do with each other.
710
 *
711
 * @param inputs A list of sources, expressed as NAME=DSN
712
 * @param expressions A list of expressions to be evaluated
713
 * @param dialect Expression dialect
714
 * @param flatten Generate a single band output raster per expression, even if
715
 *                input datasets are multiband.
716
 * @param noData NoData values to use for output bands, or "none", or ""
717
 * @param pixelFunctionArguments Pixel function arguments.
718
 * @param options flags controlling which checks should be performed on the inputs
719
 * @param[out] maxSourceBands Maximum number of bands in source dataset(s)
720
 * @param fakeSourceFilename If not empty, used instead of real input filenames.
721
 *
722
 * @return a newly created VRTDataset, or nullptr on error
723
 */
724
static std::unique_ptr<GDALDataset> GDALCalcCreateVRTDerived(
115✔
725
    const std::vector<std::string> &inputs,
726
    const std::vector<std::string> &expressions, const std::string &dialect,
727
    bool flatten, const std::string &noData,
728
    const std::vector<std::vector<std::string>> &pixelFunctionArguments,
729
    const GDALCalcOptions &options, int &maxSourceBands,
730
    const std::string &fakeSourceFilename = std::string())
731
{
732
    if (inputs.empty())
115✔
733
    {
734
        return nullptr;
×
735
    }
736

737
    std::map<std::string, std::string> sources;
230✔
738
    std::string firstSource;
230✔
739
    bool requireSourceNames = dialect != "builtin";
115✔
740
    if (!ParseSourceDescriptors(inputs, sources, firstSource,
115✔
741
                                requireSourceNames))
742
    {
743
        return nullptr;
5✔
744
    }
745

746
    // Use the first source provided to determine properties of the output
747
    const char *firstDSN = sources[firstSource].c_str();
110✔
748

749
    maxSourceBands = 0;
110✔
750

751
    // Read properties from the first source
752
    SourceProperties out;
220✔
753
    {
754
        std::unique_ptr<GDALDataset> ds(
755
            GDALDataset::Open(firstDSN, GDAL_OF_RASTER));
110✔
756

757
        if (!ds)
110✔
758
        {
759
            CPLError(CE_Failure, CPLE_AppDefined, "Failed to open %s",
×
760
                     firstDSN);
761
            return nullptr;
×
762
        }
763

764
        out.nX = ds->GetRasterXSize();
110✔
765
        out.nY = ds->GetRasterYSize();
110✔
766
        out.nBands = 1;
110✔
767
        out.srs.reset(ds->GetSpatialRef() ? ds->GetSpatialRef()->Clone()
110✔
768
                                          : nullptr);
769
        ds->GetGeoTransform(out.gt);
110✔
770
    }
771

772
    CPLXMLTreeCloser root(CPLCreateXMLNode(nullptr, CXT_Element, "VRTDataset"));
220✔
773

774
    maxSourceBands = 0;
110✔
775

776
    // Collect properties of the different sources, and verity them for
777
    // consistency.
778
    std::map<std::string, SourceProperties> sourceProps;
220✔
779
    for (const auto &[source_name, dsn] : sources)
253✔
780
    {
781
        // TODO avoid opening the first source twice.
782
        auto props = UpdateSourceProperties(out, dsn, options);
146✔
783
        if (props.has_value())
146✔
784
        {
785
            maxSourceBands = std::max(maxSourceBands, props->nBands);
143✔
786
            sourceProps[source_name] = std::move(props.value());
143✔
787
        }
788
        else
789
        {
790
            return nullptr;
3✔
791
        }
792
    }
793

794
    size_t iExpr = 0;
107✔
795
    for (const auto &origExpression : expressions)
213✔
796
    {
797
        GDALDataType bandType = options.dstType;
110✔
798

799
        // If output band type has not been specified, set it equal to the
800
        // input band type for certain pixel functions, if the inputs have
801
        // a consistent band type.
802
        if (bandType == GDT_Unknown && dialect == "builtin" &&
160✔
803
            (origExpression == "min" || origExpression == "max" ||
72✔
804
             origExpression == "mode"))
22✔
805
        {
806
            for (const auto &[_, props] : sourceProps)
12✔
807
            {
808
                if (bandType == GDT_Unknown)
6✔
809
                {
810
                    bandType = props.eDT;
6✔
811
                }
812
                else if (props.eDT == GDT_Unknown || props.eDT != bandType)
×
813
                {
814
                    bandType = GDT_Unknown;
×
815
                    break;
×
816
                }
817
            }
818
        }
819

820
        if (!CreateDerivedBandXML(root.get(), out.nX, out.nY, bandType,
110✔
821
                                  origExpression, dialect, flatten, noData,
822
                                  pixelFunctionArguments[iExpr], sources,
110✔
823
                                  sourceProps, fakeSourceFilename))
824
        {
825
            return nullptr;
4✔
826
        }
827
        ++iExpr;
106✔
828
    }
829

830
    //CPLDebug("VRT", "%s", CPLSerializeXMLTree(root.get()));
831

832
    auto ds = fakeSourceFilename.empty()
103✔
833
                  ? std::make_unique<VRTDataset>(out.nX, out.nY)
834
                  : std::make_unique<VRTDataset>(1, 1);
206✔
835
    if (ds->XMLInit(root.get(), "") != CE_None)
103✔
836
    {
837
        return nullptr;
×
838
    };
839
    ds->SetGeoTransform(out.gt);
103✔
840
    if (out.srs)
103✔
841
    {
842
        ds->SetSpatialRef(out.srs.get());
52✔
843
    }
844

845
    return ds;
103✔
846
}
847

848
/************************************************************************/
849
/*          GDALRasterCalcAlgorithm::GDALRasterCalcAlgorithm()          */
850
/************************************************************************/
851

852
GDALRasterCalcAlgorithm::GDALRasterCalcAlgorithm(bool standaloneStep) noexcept
106✔
853
    : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
854
                                      ConstructorOptions()
318✔
855
                                          .SetStandaloneStep(standaloneStep)
106✔
856
                                          .SetAddDefaultArguments(false)
106✔
857
                                          .SetAutoOpenInputDatasets(false)
106✔
858
                                          .SetInputDatasetMetaVar("INPUTS")
212✔
859
                                          .SetInputDatasetMaxCount(INT_MAX))
318✔
860
{
861
    AddRasterInputArgs(false, false);
106✔
862
    if (standaloneStep)
106✔
863
    {
864
        AddProgressArg();
85✔
865
        AddRasterOutputArgs(false);
85✔
866
    }
867

868
    AddOutputDataTypeArg(&m_type);
106✔
869

870
    AddArg("no-check-srs", 0,
871
           _("Do not check consistency of input spatial reference systems"),
872
           &m_noCheckSRS);
106✔
873
    AddArg("no-check-extent", 0, _("Do not check consistency of input extents"),
874
           &m_noCheckExtent);
106✔
875

876
    AddArg("propagate-nodata", 0,
877
           _("Whether to set pixels to the output NoData value if any of the "
878
             "input pixels is NoData"),
879
           &m_propagateNoData);
106✔
880

881
    AddArg("calc", 0, _("Expression(s) to evaluate"), &m_expr)
212✔
882
        .SetRequired()
106✔
883
        .SetPackedValuesAllowed(false)
106✔
884
        .SetMinCount(1)
106✔
885
        .SetAutoCompleteFunction(
886
            [this](const std::string &currentValue)
4✔
887
            {
888
                std::vector<std::string> ret;
4✔
889
                if (m_dialect == "builtin")
2✔
890
                {
891
                    if (currentValue.find('(') == std::string::npos)
1✔
892
                        return VRTDerivedRasterBand::GetPixelFunctionNames();
1✔
893
                }
894
                return ret;
1✔
895
            });
106✔
896

897
    AddArg("dialect", 0, _("Expression dialect"), &m_dialect)
212✔
898
        .SetDefault(m_dialect)
106✔
899
        .SetChoices("muparser", "builtin");
106✔
900

901
    AddArg("flatten", 0,
902
           _("Generate a single band output raster per expression, even if "
903
             "input datasets are multiband"),
904
           &m_flatten);
106✔
905

906
    AddNodataArg(&m_nodata, true);
106✔
907

908
    // This is a hidden option only used by test_gdalalg_raster_calc_expression_rewriting()
909
    // for now
910
    AddArg("no-check-expression", 0,
911
           _("Whether to skip expression validity checks for virtual format "
912
             "output"),
913
           &m_noCheckExpression)
212✔
914
        .SetHidden();
106✔
915

916
    AddValidationAction(
106✔
917
        [this]()
166✔
918
        {
919
            GDALPipelineStepRunContext ctxt;
88✔
920
            return m_noCheckExpression || !IsGDALGOutput() || RunStep(ctxt);
88✔
921
        });
922
}
106✔
923

924
/************************************************************************/
925
/*                  GDALRasterCalcAlgorithm::RunImpl()                  */
926
/************************************************************************/
927

928
bool GDALRasterCalcAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
79✔
929
                                      void *pProgressData)
930
{
931
    GDALPipelineStepRunContext stepCtxt;
79✔
932
    stepCtxt.m_pfnProgress = pfnProgress;
79✔
933
    stepCtxt.m_pProgressData = pProgressData;
79✔
934
    return RunPreStepPipelineValidations() && RunStep(stepCtxt);
79✔
935
}
936

937
/************************************************************************/
938
/*                GDALRasterCalcAlgorithm::RunStep()                    */
939
/************************************************************************/
940

941
bool GDALRasterCalcAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
84✔
942
{
943
    CPLAssert(!m_outputDataset.GetDatasetRef());
84✔
944

945
    GDALCalcOptions options;
84✔
946
    options.checkExtent = !m_noCheckExtent;
84✔
947
    options.checkSRS = !m_noCheckSRS;
84✔
948
    if (!m_type.empty())
84✔
949
    {
950
        options.dstType = GDALGetDataTypeByName(m_type.c_str());
4✔
951
    }
952

953
    std::vector<std::string> inputFilenames;
168✔
954
    if (!ReadFileLists(m_inputDataset, inputFilenames))
84✔
955
    {
956
        return false;
×
957
    }
958

959
    std::vector<std::vector<std::string>> pixelFunctionArgs;
168✔
960
    if (m_dialect == "builtin")
84✔
961
    {
962
        for (std::string &expr : m_expr)
27✔
963
        {
964
            const CPLStringList aosTokens(
965
                CSLTokenizeString2(expr.c_str(), "()",
966
                                   CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
14✔
967
            const char *pszFunction = aosTokens[0];
14✔
968
            const auto *pair =
969
                VRTDerivedRasterBand::GetPixelFunction(pszFunction);
14✔
970
            if (!pair)
14✔
971
            {
972
                ReportError(CE_Failure, CPLE_NotSupported,
×
973
                            "'%s' is a unknown builtin function", pszFunction);
974
                return false;
×
975
            }
976
            if (aosTokens.size() == 2)
14✔
977
            {
978
                std::vector<std::string> validArguments;
2✔
979
                AddOptionsSuggestions(pair->second.c_str(), 0, std::string(),
2✔
980
                                      validArguments);
981
                for (std::string &s : validArguments)
6✔
982
                {
983
                    if (!s.empty() && s.back() == '=')
4✔
984
                        s.pop_back();
4✔
985
                }
986

987
                const CPLStringList aosTokensArgs(CSLTokenizeString2(
988
                    aosTokens[1], ",",
989
                    CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
2✔
990
                for (const auto &[key, value] :
4✔
991
                     cpl::IterateNameValue(aosTokensArgs))
6✔
992
                {
993
                    if (std::find(validArguments.begin(), validArguments.end(),
2✔
994
                                  key) == validArguments.end())
2✔
995
                    {
996
                        if (validArguments.empty())
×
997
                        {
998
                            ReportError(
×
999
                                CE_Failure, CPLE_IllegalArg,
1000
                                "'%s' is a unrecognized argument for builtin "
1001
                                "function '%s'. It does not accept any "
1002
                                "argument",
1003
                                key, pszFunction);
1004
                        }
1005
                        else
1006
                        {
1007
                            std::string validArgumentsStr;
×
1008
                            for (const std::string &s : validArguments)
×
1009
                            {
1010
                                if (!validArgumentsStr.empty())
×
1011
                                    validArgumentsStr += ", ";
×
1012
                                validArgumentsStr += '\'';
×
1013
                                validArgumentsStr += s;
×
1014
                                validArgumentsStr += '\'';
×
1015
                            }
1016
                            ReportError(
×
1017
                                CE_Failure, CPLE_IllegalArg,
1018
                                "'%s' is a unrecognized argument for builtin "
1019
                                "function '%s'. Only %s %s supported",
1020
                                key, pszFunction,
1021
                                validArguments.size() == 1 ? "is" : "are",
×
1022
                                validArgumentsStr.c_str());
1023
                        }
1024
                        return false;
×
1025
                    }
1026
                    CPL_IGNORE_RET_VAL(value);
2✔
1027
                }
1028
                pixelFunctionArgs.emplace_back(aosTokensArgs);
2✔
1029
            }
1030
            else
1031
            {
1032
                pixelFunctionArgs.push_back(std::vector<std::string>());
12✔
1033
            }
1034
            expr = pszFunction;
14✔
1035
        }
1036
    }
1037
    else
1038
    {
1039
        pixelFunctionArgs.resize(m_expr.size());
71✔
1040
    }
1041

1042
    if (m_propagateNoData)
84✔
1043
    {
1044
        if (m_nodata == "none")
2✔
1045
        {
1046
            ReportError(CE_Failure, CPLE_AppDefined,
×
1047
                        "Output NoData value must be specified to use "
1048
                        "--propagate-nodata");
1049
            return false;
×
1050
        }
1051
        for (auto &args : pixelFunctionArgs)
4✔
1052
        {
1053
            args.push_back("propagateNoData=1");
2✔
1054
        }
1055
    }
1056

1057
    int maxSourceBands = 0;
84✔
1058
    auto vrt = GDALCalcCreateVRTDerived(inputFilenames, m_expr, m_dialect,
84✔
1059
                                        m_flatten, m_nodata, pixelFunctionArgs,
84✔
1060
                                        options, maxSourceBands);
168✔
1061
    if (vrt == nullptr)
84✔
1062
    {
1063
        return false;
12✔
1064
    }
1065

1066
    if (!m_noCheckExpression)
72✔
1067
    {
1068
        const bool bIsVRT =
1069
            m_format == "VRT" ||
144✔
1070
            (m_format.empty() &&
58✔
1071
             EQUAL(
54✔
1072
                 CPLGetExtensionSafe(m_outputDataset.GetName().c_str()).c_str(),
1073
                 "VRT"));
59✔
1074
        const bool bIsGDALG =
1075
            m_format == "GDALG" ||
144✔
1076
            (m_format.empty() &&
58✔
1077
             cpl::ends_with(m_outputDataset.GetName(), ".gdalg.json"));
27✔
1078
        if (!m_standaloneStep || m_format == "stream" || bIsVRT || bIsGDALG)
59✔
1079
        {
1080
            // Try reading a single pixel to check formulas are valid.
1081
            std::vector<GByte> dummyData(vrt->GetRasterCount());
31✔
1082

1083
            auto poGTIFFDrv = GetGDALDriverManager()->GetDriverByName("GTiff");
31✔
1084
            std::string osTmpFilename;
31✔
1085
            if (poGTIFFDrv)
31✔
1086
            {
1087
                std::string osFilename =
1088
                    VSIMemGenerateHiddenFilename("tmp.tif");
62✔
1089
                auto poDS = std::unique_ptr<GDALDataset>(
1090
                    poGTIFFDrv->Create(osFilename.c_str(), 1, 1, maxSourceBands,
1091
                                       GDT_Byte, nullptr));
62✔
1092
                if (poDS)
31✔
1093
                    osTmpFilename = std::move(osFilename);
31✔
1094
            }
1095
            if (!osTmpFilename.empty())
31✔
1096
            {
1097
                auto fakeVRT = GDALCalcCreateVRTDerived(
1098
                    inputFilenames, m_expr, m_dialect, m_flatten, m_nodata,
31✔
1099
                    pixelFunctionArgs, options, maxSourceBands, osTmpFilename);
31✔
1100
                if (fakeVRT &&
62✔
1101
                    fakeVRT->RasterIO(GF_Read, 0, 0, 1, 1, dummyData.data(), 1,
31✔
1102
                                      1, GDT_Byte, vrt->GetRasterCount(),
1103
                                      nullptr, 0, 0, 0, nullptr) != CE_None)
31✔
1104
                {
1105
                    return false;
5✔
1106
                }
1107
            }
1108
            if (bIsGDALG)
26✔
1109
            {
1110
                return true;
1✔
1111
            }
1112
        }
1113
    }
1114

1115
    if (m_format == "stream" || !m_standaloneStep)
66✔
1116
    {
1117
        m_outputDataset.Set(std::move(vrt));
24✔
1118
        return true;
24✔
1119
    }
1120

1121
    CPLStringList translateArgs;
84✔
1122
    if (!m_format.empty())
42✔
1123
    {
1124
        translateArgs.AddString("-of");
7✔
1125
        translateArgs.AddString(m_format.c_str());
7✔
1126
    }
1127
    for (const auto &co : m_creationOptions)
43✔
1128
    {
1129
        translateArgs.AddString("-co");
1✔
1130
        translateArgs.AddString(co.c_str());
1✔
1131
    }
1132

1133
    GDALTranslateOptions *translateOptions =
1134
        GDALTranslateOptionsNew(translateArgs.List(), nullptr);
42✔
1135
    GDALTranslateOptionsSetProgress(translateOptions, ctxt.m_pfnProgress,
42✔
1136
                                    ctxt.m_pProgressData);
1137

1138
    auto poOutDS =
1139
        std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(GDALTranslate(
1140
            m_outputDataset.GetName().c_str(), GDALDataset::ToHandle(vrt.get()),
42✔
1141
            translateOptions, nullptr)));
84✔
1142
    GDALTranslateOptionsFree(translateOptions);
42✔
1143

1144
    const bool bOK = poOutDS != nullptr;
42✔
1145
    m_outputDataset.Set(std::move(poOutDS));
42✔
1146

1147
    return bOK;
42✔
1148
}
1149

1150
GDALRasterCalcAlgorithmStandalone::~GDALRasterCalcAlgorithmStandalone() =
1151
    default;
1152

1153
//! @endcond
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc