Coverage Report

Created: 2026-04-10 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_edit.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "edit" step of "raster pipeline"
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_raster_edit.h"
14
15
#include "gdal_priv.h"
16
#include "gdal_utils.h"
17
#include "ogrsf_frmts.h"
18
19
#include <optional>
20
21
//! @cond Doxygen_Suppress
22
23
#ifndef _
24
0
#define _(x) (x)
25
#endif
26
27
/************************************************************************/
28
/*                           GetGCPFilename()                           */
29
/************************************************************************/
30
31
static std::string GetGCPFilename(const std::vector<std::string> &gcps)
32
0
{
33
0
    if (gcps.size() == 1 && !gcps[0].empty() && gcps[0][0] == '@')
34
0
    {
35
0
        return gcps[0].substr(1);
36
0
    }
37
0
    return std::string();
38
0
}
39
40
/************************************************************************/
41
/*          GDALRasterEditAlgorithm::GDALRasterEditAlgorithm()          */
42
/************************************************************************/
43
44
GDALRasterEditAlgorithm::GDALRasterEditAlgorithm(bool standaloneStep)
45
0
    : GDALRasterPipelineStepAlgorithm(
46
0
          NAME, DESCRIPTION, HELP_URL,
47
0
          ConstructorOptions().SetAddDefaultArguments(false))
48
0
{
49
0
    if (standaloneStep)
50
0
    {
51
0
        AddProgressArg();
52
53
0
        AddArg("dataset", 0,
54
0
               _("Dataset (to be updated in-place, unless --auxiliary)"),
55
0
               &m_dataset, GDAL_OF_RASTER | GDAL_OF_UPDATE)
56
0
            .SetPositional()
57
0
            .SetRequired();
58
0
        AddOpenOptionsArg(&m_openOptions);
59
0
        AddArg("auxiliary", 0,
60
0
               _("Ask for an auxiliary .aux.xml file to be edited"),
61
0
               &m_readOnly)
62
0
            .AddHiddenAlias("ro")
63
0
            .AddHiddenAlias(GDAL_ARG_NAME_READ_ONLY);
64
0
    }
65
0
    else
66
0
    {
67
0
        AddRasterHiddenInputDatasetArg();
68
0
    }
69
70
0
    AddArg("crs", 0, _("Override CRS (without reprojection)"), &m_overrideCrs)
71
0
        .AddHiddenAlias("a_srs")
72
0
        .AddHiddenAlias("srs")
73
0
        .SetIsCRSArg(/*noneAllowed=*/true);
74
75
0
    AddBBOXArg(&m_bbox);
76
77
0
    AddNodataArg(&m_nodata, /* noneAllowed = */ true);
78
79
0
    AddArg("color-interpretation", 0, _("Set band color interpretation"),
80
0
           &m_colorInterpretation)
81
0
        .SetMetaVar("[all|<BAND>=]<COLOR-INTEPRETATION>")
82
0
        .SetAutoCompleteFunction(
83
0
            [this](const std::string &s)
84
0
            {
85
0
                std::vector<std::string> ret;
86
0
                int nValues = 0;
87
0
                const auto paeVals = GDALGetColorInterpretationList(&nValues);
88
0
                if (s.find('=') == std::string::npos)
89
0
                {
90
0
                    ret.push_back("all=");
91
0
                    if (auto poDS = m_dataset.GetDatasetRef())
92
0
                    {
93
0
                        for (int i = 0; i < poDS->GetRasterCount(); ++i)
94
0
                            ret.push_back(std::to_string(i + 1).append("="));
95
0
                    }
96
0
                    for (int i = 0; i < nValues; ++i)
97
0
                        ret.push_back(
98
0
                            GDALGetColorInterpretationName(paeVals[i]));
99
0
                }
100
0
                else
101
0
                {
102
0
                    for (int i = 0; i < nValues; ++i)
103
0
                        ret.push_back(
104
0
                            GDALGetColorInterpretationName(paeVals[i]));
105
0
                }
106
0
                return ret;
107
0
            });
108
109
0
    const auto ValidationActionScaleOffset =
110
0
        [this](const char *argName, const std::vector<std::string> &values)
111
0
    {
112
0
        for (const std::string &s : values)
113
0
        {
114
0
            bool valid = true;
115
0
            const auto nPos = s.find('=');
116
0
            if (nPos != std::string::npos)
117
0
            {
118
0
                if (CPLGetValueType(s.substr(0, nPos).c_str()) !=
119
0
                        CPL_VALUE_INTEGER ||
120
0
                    CPLGetValueType(s.substr(nPos + 1).c_str()) ==
121
0
                        CPL_VALUE_STRING)
122
0
                {
123
0
                    valid = false;
124
0
                }
125
0
            }
126
0
            else if (CPLGetValueType(s.c_str()) == CPL_VALUE_STRING)
127
0
            {
128
0
                valid = false;
129
0
            }
130
0
            if (!valid)
131
0
            {
132
0
                ReportError(CE_Failure, CPLE_IllegalArg,
133
0
                            "Invalid value '%s' for '%s'", s.c_str(), argName);
134
0
                return false;
135
0
            }
136
0
        }
137
0
        return true;
138
0
    };
139
140
0
    AddArg("scale", 0, _("Override band scale factor"), &m_scale)
141
0
        .SetMetaVar("[<BAND>=]<SCALE>")
142
0
        .AddValidationAction(
143
0
            [this, ValidationActionScaleOffset]()
144
0
            { return ValidationActionScaleOffset("scale", m_scale); });
145
146
0
    AddArg("offset", 0, _("Override band offset constant"), &m_offset)
147
0
        .SetMetaVar("[<BAND>=]<OFFSET>")
148
0
        .AddValidationAction(
149
0
            [this, ValidationActionScaleOffset]()
150
0
            { return ValidationActionScaleOffset("offset", m_offset); });
151
152
0
    {
153
0
        auto &arg = AddArg("metadata", 0, _("Add/update dataset metadata item"),
154
0
                           &m_metadata)
155
0
                        .SetMetaVar("<KEY>=<VALUE>")
156
0
                        .SetPackedValuesAllowed(false);
157
0
        arg.AddValidationAction([this, &arg]()
158
0
                                { return ParseAndValidateKeyValue(arg); });
159
0
        arg.AddHiddenAlias("mo");
160
0
    }
161
162
0
    AddArg("unset-metadata", 0, _("Remove dataset metadata item(s)"),
163
0
           &m_unsetMetadata)
164
0
        .SetMetaVar("<KEY>");
165
166
0
    AddArg("unset-metadata-domain", 0, _("Remove dataset metadata domain(s)"),
167
0
           &m_unsetMetadataDomain)
168
0
        .SetMetaVar("<DOMAIN>");
169
170
0
    AddArg("gcp", 0,
171
0
           _("Add ground control point, formatted as "
172
0
             "pixel,line,easting,northing[,elevation], or @filename"),
173
0
           &m_gcps)
174
0
        .SetPackedValuesAllowed(false)
175
0
        .AddValidationAction(
176
0
            [this]()
177
0
            {
178
0
                if (GetGCPFilename(m_gcps).empty())
179
0
                {
180
0
                    for (const std::string &gcp : m_gcps)
181
0
                    {
182
0
                        const CPLStringList aosTokens(
183
0
                            CSLTokenizeString2(gcp.c_str(), ",", 0));
184
0
                        if (aosTokens.size() != 4 && aosTokens.size() != 5)
185
0
                        {
186
0
                            ReportError(CE_Failure, CPLE_IllegalArg,
187
0
                                        "Bad format for %s", gcp.c_str());
188
0
                            return false;
189
0
                        }
190
0
                        for (int i = 0; i < aosTokens.size(); ++i)
191
0
                        {
192
0
                            if (CPLGetValueType(aosTokens[i]) ==
193
0
                                CPL_VALUE_STRING)
194
0
                            {
195
0
                                ReportError(CE_Failure, CPLE_IllegalArg,
196
0
                                            "Bad format for %s", gcp.c_str());
197
0
                                return false;
198
0
                            }
199
0
                        }
200
0
                    }
201
0
                }
202
0
                return true;
203
0
            });
204
205
0
    if (standaloneStep)
206
0
    {
207
0
        AddArg("stats", 0, _("Compute statistics, using all pixels"), &m_stats)
208
0
            .SetMutualExclusionGroup("stats");
209
0
        AddArg("approx-stats", 0,
210
0
               _("Compute statistics, using a subset of pixels"),
211
0
               &m_approxStats)
212
0
            .SetMutualExclusionGroup("stats");
213
0
        AddArg("hist", 0, _("Compute histogram"), &m_hist);
214
0
    }
215
0
}
216
217
/************************************************************************/
218
/*         GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm()          */
219
/************************************************************************/
220
221
0
GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm() = default;
222
223
/************************************************************************/
224
/*                             ParseGCPs()                              */
225
/************************************************************************/
226
227
std::vector<gdal::GCP> GDALRasterEditAlgorithm::ParseGCPs() const
228
0
{
229
0
    std::vector<gdal::GCP> ret;
230
0
    const std::string osGCPFilename = GetGCPFilename(m_gcps);
231
0
    if (!osGCPFilename.empty())
232
0
    {
233
0
        auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
234
0
            osGCPFilename.c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR));
235
0
        if (!poDS)
236
0
            return ret;
237
0
        if (poDS->GetLayerCount() != 1)
238
0
        {
239
0
            ReportError(CE_Failure, CPLE_AppDefined,
240
0
                        "GCPs can only be specified for single-layer datasets");
241
0
            return ret;
242
0
        }
243
0
        auto poLayer = poDS->GetLayer(0);
244
0
        const auto poLayerDefn = poLayer->GetLayerDefn();
245
0
        int nIdIdx = -1, nInfoIdx = -1, nColIdx = -1, nLineIdx = -1, nXIdx = -1,
246
0
            nYIdx = -1, nZIdx = -1;
247
248
0
        const struct
249
0
        {
250
0
            int &idx;
251
0
            const char *name;
252
0
            bool required;
253
0
        } aFields[] = {
254
0
            {nIdIdx, "id", false},     {nInfoIdx, "info", false},
255
0
            {nColIdx, "column", true}, {nLineIdx, "line", true},
256
0
            {nXIdx, "x", true},        {nYIdx, "y", true},
257
0
            {nZIdx, "z", false},
258
0
        };
259
260
0
        for (auto &field : aFields)
261
0
        {
262
0
            field.idx = poLayerDefn->GetFieldIndex(field.name);
263
0
            if (field.idx < 0 && field.required)
264
0
            {
265
0
                ReportError(CE_Failure, CPLE_AppDefined,
266
0
                            "Field '%s' cannot be found in '%s'", field.name,
267
0
                            poDS->GetDescription());
268
0
                return ret;
269
0
            }
270
0
        }
271
0
        for (auto &&poFeature : poLayer)
272
0
        {
273
0
            gdal::GCP gcp;
274
0
            if (nIdIdx >= 0)
275
0
                gcp.SetId(poFeature->GetFieldAsString(nIdIdx));
276
0
            if (nInfoIdx >= 0)
277
0
                gcp.SetInfo(poFeature->GetFieldAsString(nInfoIdx));
278
0
            gcp.Pixel() = poFeature->GetFieldAsDouble(nColIdx);
279
0
            gcp.Line() = poFeature->GetFieldAsDouble(nLineIdx);
280
0
            gcp.X() = poFeature->GetFieldAsDouble(nXIdx);
281
0
            gcp.Y() = poFeature->GetFieldAsDouble(nYIdx);
282
0
            if (nZIdx >= 0 && poFeature->IsFieldSetAndNotNull(nZIdx))
283
0
                gcp.Z() = poFeature->GetFieldAsDouble(nZIdx);
284
0
            ret.push_back(std::move(gcp));
285
0
        }
286
0
    }
287
0
    else
288
0
    {
289
0
        for (const std::string &gcpStr : m_gcps)
290
0
        {
291
0
            const CPLStringList aosTokens(
292
0
                CSLTokenizeString2(gcpStr.c_str(), ",", 0));
293
            // Verified by validation action
294
0
            CPLAssert(aosTokens.size() == 4 || aosTokens.size() == 5);
295
0
            gdal::GCP gcp;
296
0
            gcp.Pixel() = CPLAtof(aosTokens[0]);
297
0
            gcp.Line() = CPLAtof(aosTokens[1]);
298
0
            gcp.X() = CPLAtof(aosTokens[2]);
299
0
            gcp.Y() = CPLAtof(aosTokens[3]);
300
0
            if (aosTokens.size() == 5)
301
0
                gcp.Z() = CPLAtof(aosTokens[4]);
302
0
            ret.push_back(std::move(gcp));
303
0
        }
304
0
    }
305
0
    return ret;
306
0
}
307
308
/************************************************************************/
309
/*                  GDALRasterEditAlgorithm::RunStep()                  */
310
/************************************************************************/
311
312
bool GDALRasterEditAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
313
0
{
314
0
    GDALDataset *poDS = m_dataset.GetDatasetRef();
315
0
    if (poDS)
316
0
    {
317
0
        if (poDS->GetAccess() != GA_Update && !m_readOnly)
318
0
        {
319
0
            ReportError(CE_Failure, CPLE_AppDefined,
320
0
                        "Dataset should be opened in update mode unless "
321
0
                        "--auxiliary is set");
322
0
            return false;
323
0
        }
324
0
    }
325
0
    else
326
0
    {
327
0
        const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
328
0
        CPLAssert(poSrcDS);
329
0
        CPLAssert(m_outputDataset.GetName().empty());
330
0
        CPLAssert(!m_outputDataset.GetDatasetRef());
331
332
0
        CPLStringList aosOptions;
333
0
        aosOptions.push_back("-of");
334
0
        aosOptions.push_back("VRT");
335
0
        GDALTranslateOptions *psOptions =
336
0
            GDALTranslateOptionsNew(aosOptions.List(), nullptr);
337
0
        GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
338
0
        auto poRetDS = GDALDataset::FromHandle(
339
0
            GDALTranslate("", hSrcDS, psOptions, nullptr));
340
0
        GDALTranslateOptionsFree(psOptions);
341
0
        m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
342
0
        poDS = m_outputDataset.GetDatasetRef();
343
0
    }
344
345
0
    bool ret = poDS != nullptr;
346
347
0
    if (poDS)
348
0
    {
349
0
        if (m_overrideCrs == "null" || m_overrideCrs == "none")
350
0
        {
351
0
            if (poDS->SetSpatialRef(nullptr) != CE_None)
352
0
            {
353
0
                ReportError(CE_Failure, CPLE_AppDefined,
354
0
                            "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
355
0
                return false;
356
0
            }
357
0
        }
358
0
        else if (!m_overrideCrs.empty() && m_gcps.empty())
359
0
        {
360
0
            OGRSpatialReference oSRS;
361
0
            oSRS.SetFromUserInput(m_overrideCrs.c_str());
362
0
            oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
363
0
            if (poDS->SetSpatialRef(&oSRS) != CE_None)
364
0
            {
365
0
                ReportError(CE_Failure, CPLE_AppDefined,
366
0
                            "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
367
0
                return false;
368
0
            }
369
0
        }
370
371
0
        if (!m_bbox.empty())
372
0
        {
373
0
            if (poDS->GetRasterXSize() == 0 || poDS->GetRasterYSize() == 0)
374
0
            {
375
0
                ReportError(CE_Failure, CPLE_AppDefined,
376
0
                            "Cannot set extent because one of dataset height "
377
0
                            "or width is null");
378
0
                return false;
379
0
            }
380
0
            GDALGeoTransform gt;
381
0
            gt.xorig = m_bbox[0];
382
0
            gt.xscale = (m_bbox[2] - m_bbox[0]) / poDS->GetRasterXSize();
383
0
            gt.xrot = 0;
384
0
            gt.yorig = m_bbox[3];
385
0
            gt.yrot = 0;
386
0
            gt.yscale = -(m_bbox[3] - m_bbox[1]) / poDS->GetRasterYSize();
387
0
            if (poDS->SetGeoTransform(gt) != CE_None)
388
0
            {
389
0
                ReportError(CE_Failure, CPLE_AppDefined,
390
0
                            "Setting extent failed");
391
0
                return false;
392
0
            }
393
0
        }
394
395
0
        if (!m_nodata.empty())
396
0
        {
397
0
            for (int i = 0; i < poDS->GetRasterCount(); ++i)
398
0
            {
399
0
                if (EQUAL(m_nodata.c_str(), "none"))
400
0
                    poDS->GetRasterBand(i + 1)->DeleteNoDataValue();
401
0
                else
402
0
                    poDS->GetRasterBand(i + 1)->SetNoDataValue(
403
0
                        CPLAtof(m_nodata.c_str()));
404
0
            }
405
0
        }
406
407
0
        if (!m_colorInterpretation.empty())
408
0
        {
409
0
            const auto GetColorInterp =
410
0
                [this](const char *pszStr) -> std::optional<GDALColorInterp>
411
0
            {
412
0
                if (EQUAL(pszStr, "undefined"))
413
0
                    return GCI_Undefined;
414
0
                const GDALColorInterp eInterp =
415
0
                    GDALGetColorInterpretationByName(pszStr);
416
0
                if (eInterp != GCI_Undefined)
417
0
                    return eInterp;
418
0
                ReportError(CE_Failure, CPLE_NotSupported,
419
0
                            "Unsupported color interpretation: %s", pszStr);
420
0
                return {};
421
0
            };
422
423
0
            if (m_colorInterpretation.size() == 1 &&
424
0
                poDS->GetRasterCount() > 1 &&
425
0
                !cpl::starts_with(m_colorInterpretation[0], "all="))
426
0
            {
427
0
                ReportError(
428
0
                    CE_Failure, CPLE_NotSupported,
429
0
                    "With several bands, specify as many color interpretation "
430
0
                    "as bands, one or many values of the form "
431
0
                    "<band_number>=<color> or a single value all=<color>");
432
0
                return false;
433
0
            }
434
0
            else
435
0
            {
436
0
                int nBandIter = 0;
437
0
                bool bSyntaxAll = false;
438
0
                bool bSyntaxExplicitBand = false;
439
0
                bool bSyntaxImplicitBand = false;
440
0
                for (const std::string &token : m_colorInterpretation)
441
0
                {
442
0
                    const CPLStringList aosTokens(
443
0
                        CSLTokenizeString2(token.c_str(), "=", 0));
444
0
                    if (aosTokens.size() == 2 && EQUAL(aosTokens[0], "all"))
445
0
                    {
446
0
                        bSyntaxAll = true;
447
0
                        const auto eColorInterp = GetColorInterp(aosTokens[1]);
448
0
                        if (!eColorInterp)
449
0
                        {
450
0
                            return false;
451
0
                        }
452
0
                        else
453
0
                        {
454
0
                            for (int i = 0; i < poDS->GetRasterCount(); ++i)
455
0
                            {
456
0
                                if (poDS->GetRasterBand(i + 1)
457
0
                                        ->SetColorInterpretation(
458
0
                                            *eColorInterp) != CE_None)
459
0
                                    return false;
460
0
                            }
461
0
                        }
462
0
                    }
463
0
                    else if (aosTokens.size() == 2)
464
0
                    {
465
0
                        bSyntaxExplicitBand = true;
466
0
                        const int nBand = atoi(aosTokens[0]);
467
0
                        if (nBand <= 0 || nBand > poDS->GetRasterCount())
468
0
                        {
469
0
                            ReportError(CE_Failure, CPLE_NotSupported,
470
0
                                        "Invalid band number '%s' in '%s'",
471
0
                                        aosTokens[0], token.c_str());
472
0
                            return false;
473
0
                        }
474
0
                        const auto eColorInterp = GetColorInterp(aosTokens[1]);
475
0
                        if (!eColorInterp)
476
0
                        {
477
0
                            return false;
478
0
                        }
479
0
                        else if (poDS->GetRasterBand(nBand)
480
0
                                     ->SetColorInterpretation(*eColorInterp) !=
481
0
                                 CE_None)
482
0
                        {
483
0
                            return false;
484
0
                        }
485
0
                    }
486
0
                    else
487
0
                    {
488
0
                        bSyntaxImplicitBand = true;
489
0
                        ++nBandIter;
490
0
                        if (nBandIter > poDS->GetRasterCount())
491
0
                        {
492
0
                            ReportError(CE_Failure, CPLE_IllegalArg,
493
0
                                        "More color interpretation values "
494
0
                                        "specified than bands in the dataset");
495
0
                            return false;
496
0
                        }
497
0
                        const auto eColorInterp = GetColorInterp(token.c_str());
498
0
                        if (!eColorInterp)
499
0
                        {
500
0
                            return false;
501
0
                        }
502
0
                        else if (poDS->GetRasterBand(nBandIter)
503
0
                                     ->SetColorInterpretation(*eColorInterp) !=
504
0
                                 CE_None)
505
0
                        {
506
0
                            return false;
507
0
                        }
508
0
                    }
509
0
                }
510
0
                if ((bSyntaxAll ? 1 : 0) + (bSyntaxExplicitBand ? 1 : 0) +
511
0
                        (bSyntaxImplicitBand ? 1 : 0) !=
512
0
                    1)
513
0
                {
514
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
515
0
                                "Mix of different syntaxes to specify color "
516
0
                                "interpretation");
517
0
                    return false;
518
0
                }
519
0
                if (bSyntaxImplicitBand && nBandIter != poDS->GetRasterCount())
520
0
                {
521
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
522
0
                                "Less color interpretation values specified "
523
0
                                "than bands in the dataset");
524
0
                    return false;
525
0
                }
526
0
            }
527
0
        }
528
529
0
        const auto ScaleOffsetSetterLambda =
530
0
            [this, poDS](const char *argName,
531
0
                         const std::vector<std::string> &values,
532
0
                         CPLErr (GDALRasterBand::*Setter)(double))
533
0
        {
534
0
            if (values.size() == 1 && values[0].find('=') == std::string::npos)
535
0
            {
536
0
                const double dfScale = CPLAtof(values[0].c_str());
537
0
                for (int i = 0; i < poDS->GetRasterCount(); ++i)
538
0
                {
539
0
                    if ((poDS->GetRasterBand(i + 1)->*Setter)(dfScale) !=
540
0
                        CE_None)
541
0
                        return false;
542
0
                }
543
0
            }
544
0
            else
545
0
            {
546
0
                int nBandIter = 0;
547
0
                bool bSyntaxExplicitBand = false;
548
0
                bool bSyntaxImplicitBand = false;
549
0
                for (const std::string &token : values)
550
0
                {
551
0
                    const CPLStringList aosTokens(
552
0
                        CSLTokenizeString2(token.c_str(), "=", 0));
553
0
                    if (aosTokens.size() == 2)
554
0
                    {
555
0
                        bSyntaxExplicitBand = true;
556
0
                        const int nBand = atoi(aosTokens[0]);
557
0
                        if (nBand <= 0 || nBand > poDS->GetRasterCount())
558
0
                        {
559
0
                            ReportError(CE_Failure, CPLE_NotSupported,
560
0
                                        "Invalid band number '%s' in '%s'",
561
0
                                        aosTokens[0], token.c_str());
562
0
                            return false;
563
0
                        }
564
0
                        const double dfScale = CPLAtof(aosTokens[1]);
565
0
                        if ((poDS->GetRasterBand(nBand)->*Setter)(dfScale) !=
566
0
                            CE_None)
567
0
                        {
568
0
                            return false;
569
0
                        }
570
0
                    }
571
0
                    else
572
0
                    {
573
0
                        bSyntaxImplicitBand = true;
574
0
                        ++nBandIter;
575
0
                        if (nBandIter > poDS->GetRasterCount())
576
0
                        {
577
0
                            ReportError(CE_Failure, CPLE_IllegalArg,
578
0
                                        "More %s values "
579
0
                                        "specified than bands in the dataset",
580
0
                                        argName);
581
0
                            return false;
582
0
                        }
583
0
                        const double dfScale = CPLAtof(token.c_str());
584
0
                        if ((poDS->GetRasterBand(nBandIter)->*Setter)(
585
0
                                dfScale) != CE_None)
586
0
                        {
587
0
                            return false;
588
0
                        }
589
0
                    }
590
0
                }
591
0
                if (((bSyntaxExplicitBand ? 1 : 0) +
592
0
                     (bSyntaxImplicitBand ? 1 : 0)) != 1)
593
0
                {
594
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
595
0
                                "Mix of different syntaxes to specify %s",
596
0
                                argName);
597
0
                    return false;
598
0
                }
599
0
                if (bSyntaxImplicitBand && nBandIter != poDS->GetRasterCount())
600
0
                {
601
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
602
0
                                "Less %s values specified "
603
0
                                "than bands in the dataset",
604
0
                                argName);
605
0
                    return false;
606
0
                }
607
0
            }
608
609
0
            return true;
610
0
        };
611
612
0
        if (!m_scale.empty())
613
0
        {
614
0
            if (!ScaleOffsetSetterLambda("scale", m_scale,
615
0
                                         &GDALRasterBand::SetScale))
616
0
                return false;
617
0
        }
618
619
0
        if (!m_offset.empty())
620
0
        {
621
0
            if (!ScaleOffsetSetterLambda("offset", m_offset,
622
0
                                         &GDALRasterBand::SetOffset))
623
0
                return false;
624
0
        }
625
626
0
        const CPLStringList aosMD(m_metadata);
627
0
        for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
628
0
        {
629
0
            if (poDS->SetMetadataItem(key, value) != CE_None)
630
0
            {
631
0
                ReportError(CE_Failure, CPLE_AppDefined,
632
0
                            "SetMetadataItem('%s', '%s') failed", key, value);
633
0
                return false;
634
0
            }
635
0
        }
636
637
0
        for (const std::string &key : m_unsetMetadata)
638
0
        {
639
0
            if (poDS->SetMetadataItem(key.c_str(), nullptr) != CE_None)
640
0
            {
641
0
                ReportError(CE_Failure, CPLE_AppDefined,
642
0
                            "SetMetadataItem('%s', NULL) failed", key.c_str());
643
0
                return false;
644
0
            }
645
0
        }
646
647
0
        for (const std::string &domain : m_unsetMetadataDomain)
648
0
        {
649
0
            if (poDS->SetMetadata(nullptr, domain.c_str()) != CE_None)
650
0
            {
651
0
                ReportError(CE_Failure, CPLE_AppDefined,
652
0
                            "SetMetadata(NULL, '%s') failed", domain.c_str());
653
0
                return false;
654
0
            }
655
0
        }
656
657
0
        if (!m_gcps.empty())
658
0
        {
659
0
            const auto gcps = ParseGCPs();
660
0
            if (gcps.empty())
661
0
                return false;  // error already emitted by ParseGCPs()
662
663
0
            OGRSpatialReference oSRS;
664
0
            if (!m_overrideCrs.empty())
665
0
            {
666
0
                oSRS.SetFromUserInput(m_overrideCrs.c_str());
667
0
                oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
668
0
            }
669
670
0
            if (poDS->SetGCPs(static_cast<int>(gcps.size()), gcps[0].c_ptr(),
671
0
                              oSRS.IsEmpty() ? nullptr : &oSRS) != CE_None)
672
0
            {
673
0
                ReportError(CE_Failure, CPLE_AppDefined, "Setting GCPs failed");
674
0
                return false;
675
0
            }
676
0
        }
677
678
0
        const int nBands = poDS->GetRasterCount();
679
0
        int nCurProgress = 0;
680
0
        const double dfTotalProgress =
681
0
            ((m_stats || m_approxStats) ? nBands : 0) + (m_hist ? nBands : 0);
682
0
        if (m_stats || m_approxStats)
683
0
        {
684
0
            for (int i = 0; (i < nBands) && ret; ++i)
685
0
            {
686
0
                void *pScaledProgress = GDALCreateScaledProgress(
687
0
                    nCurProgress / dfTotalProgress,
688
0
                    (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
689
0
                    ctxt.m_pProgressData);
690
0
                ++nCurProgress;
691
0
                double dfMin = 0.0;
692
0
                double dfMax = 0.0;
693
0
                double dfMean = 0.0;
694
0
                double dfStdDev = 0.0;
695
0
                ret = poDS->GetRasterBand(i + 1)->ComputeStatistics(
696
0
                          m_approxStats, &dfMin, &dfMax, &dfMean, &dfStdDev,
697
0
                          pScaledProgress ? GDALScaledProgress : nullptr,
698
0
                          pScaledProgress) == CE_None;
699
0
                GDALDestroyScaledProgress(pScaledProgress);
700
0
            }
701
0
        }
702
703
0
        if (m_hist)
704
0
        {
705
0
            for (int i = 0; (i < nBands) && ret; ++i)
706
0
            {
707
0
                void *pScaledProgress = GDALCreateScaledProgress(
708
0
                    nCurProgress / dfTotalProgress,
709
0
                    (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
710
0
                    ctxt.m_pProgressData);
711
0
                ++nCurProgress;
712
0
                double dfMin = 0.0;
713
0
                double dfMax = 0.0;
714
0
                int nBucketCount = 0;
715
0
                GUIntBig *panHistogram = nullptr;
716
0
                ret = poDS->GetRasterBand(i + 1)->GetDefaultHistogram(
717
0
                          &dfMin, &dfMax, &nBucketCount, &panHistogram, TRUE,
718
0
                          pScaledProgress ? GDALScaledProgress : nullptr,
719
0
                          pScaledProgress) == CE_None;
720
0
                if (ret)
721
0
                {
722
0
                    ret = poDS->GetRasterBand(i + 1)->SetDefaultHistogram(
723
0
                              dfMin, dfMax, nBucketCount, panHistogram) ==
724
0
                          CE_None;
725
0
                }
726
0
                CPLFree(panHistogram);
727
0
                GDALDestroyScaledProgress(pScaledProgress);
728
0
            }
729
0
        }
730
0
    }
731
732
0
    return poDS != nullptr;
733
0
}
734
735
GDALRasterEditAlgorithmStandalone::~GDALRasterEditAlgorithmStandalone() =
736
    default;
737
738
//! @endcond