Coverage Report

Created: 2026-07-25 06:50

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
            .SetAvailableInPipelineStep(false);
59
0
        AddOpenOptionsArg(&m_openOptions).SetAvailableInPipelineStep(false);
60
0
        AddArg("auxiliary", 0,
61
0
               _("Ask for an auxiliary .aux.xml file to be edited"),
62
0
               &m_readOnly)
63
0
            .AddHiddenAlias("ro")
64
0
            .AddHiddenAlias(GDAL_ARG_NAME_READ_ONLY)
65
0
            .SetAvailableInPipelineStep(false);
66
0
    }
67
0
    else
68
0
    {
69
0
        AddRasterHiddenInputDatasetArg();
70
0
    }
71
72
0
    AddBandArg(&m_band, _("Active band (1-based index)"));
73
74
0
    AddArg("crs", 0, _("Override CRS (without reprojection)"), &m_overrideCrs)
75
0
        .AddHiddenAlias("a_srs")
76
0
        .AddHiddenAlias("srs")
77
0
        .SetIsCRSArg(/*noneAllowed=*/true);
78
79
0
    AddBBOXArg(&m_bbox);
80
81
0
    AddNodataArg(&m_nodata, /* noneAllowed = */ true);
82
83
0
    AddArg("color-interpretation", 0, _("Set band color interpretation"),
84
0
           &m_colorInterpretation)
85
0
        .SetMetaVar("[all|<BAND>=]<COLOR-INTEPRETATION>")
86
0
        .SetAutoCompleteFunction(
87
0
            [this](const std::string &s)
88
0
            {
89
0
                std::vector<std::string> ret;
90
0
                int nValues = 0;
91
0
                const auto paeVals = GDALGetColorInterpretationList(&nValues);
92
0
                if (s.find('=') == std::string::npos)
93
0
                {
94
0
                    ret.push_back("all=");
95
0
                    if (auto poDS = m_dataset.GetDatasetRef())
96
0
                    {
97
0
                        for (int i = 0; i < poDS->GetRasterCount(); ++i)
98
0
                            ret.push_back(std::to_string(i + 1).append("="));
99
0
                    }
100
0
                    for (int i = 0; i < nValues; ++i)
101
0
                        ret.push_back(
102
0
                            GDALGetColorInterpretationName(paeVals[i]));
103
0
                }
104
0
                else
105
0
                {
106
0
                    for (int i = 0; i < nValues; ++i)
107
0
                        ret.push_back(
108
0
                            GDALGetColorInterpretationName(paeVals[i]));
109
0
                }
110
0
                return ret;
111
0
            });
112
113
0
    AddArg("color-map", 0, _("Color map filename"), &m_colorMap)
114
0
        .SetMutualExclusionGroup("color-table")
115
0
        .AddHiddenAlias("color-table");
116
117
0
    const auto ValidationActionScaleOffset =
118
0
        [this](const char *argName, const std::vector<std::string> &values)
119
0
    {
120
0
        for (const std::string &s : values)
121
0
        {
122
0
            bool valid = true;
123
0
            const auto nPos = s.find('=');
124
0
            if (nPos != std::string::npos)
125
0
            {
126
0
                if (CPLGetValueType(s.substr(0, nPos).c_str()) !=
127
0
                        CPL_VALUE_INTEGER ||
128
0
                    CPLGetValueType(s.substr(nPos + 1).c_str()) ==
129
0
                        CPL_VALUE_STRING)
130
0
                {
131
0
                    valid = false;
132
0
                }
133
0
            }
134
0
            else if (CPLGetValueType(s.c_str()) == CPL_VALUE_STRING)
135
0
            {
136
0
                valid = false;
137
0
            }
138
0
            if (!valid)
139
0
            {
140
0
                ReportError(CE_Failure, CPLE_IllegalArg,
141
0
                            "Invalid value '%s' for '%s'", s.c_str(), argName);
142
0
                return false;
143
0
            }
144
0
        }
145
0
        return true;
146
0
    };
147
148
0
    AddArg("scale", 0, _("Override band scale factor"), &m_scale)
149
0
        .SetMetaVar("[<BAND>=]<SCALE>")
150
0
        .AddValidationAction(
151
0
            [this, ValidationActionScaleOffset]()
152
0
            { return ValidationActionScaleOffset("scale", m_scale); });
153
154
0
    AddArg("offset", 0, _("Override band offset constant"), &m_offset)
155
0
        .SetMetaVar("[<BAND>=]<OFFSET>")
156
0
        .AddValidationAction(
157
0
            [this, ValidationActionScaleOffset]()
158
0
            { return ValidationActionScaleOffset("offset", m_offset); });
159
160
0
    {
161
0
        auto &arg = AddArg("metadata", 0, _("Add/update dataset metadata item"),
162
0
                           &m_metadata)
163
0
                        .SetMetaVar("<KEY>=<VALUE>")
164
0
                        .SetPackedValuesAllowed(false);
165
0
        arg.AddValidationAction([this, &arg]()
166
0
                                { return ParseAndValidateKeyValue(arg); });
167
0
        arg.AddHiddenAlias("mo");
168
0
    }
169
170
0
    AddArg("unset-color-table", 0,
171
0
           _("Unset color table on 1st band or the one specified with --band"),
172
0
           &m_unsetColorTable)
173
0
        .SetMutualExclusionGroup("color-table");
174
175
0
    AddArg("unset-metadata", 0, _("Remove dataset metadata item(s)"),
176
0
           &m_unsetMetadata)
177
0
        .SetMetaVar("<KEY>");
178
179
0
    AddArg("unset-metadata-domain", 0, _("Remove dataset metadata domain(s)"),
180
0
           &m_unsetMetadataDomain)
181
0
        .SetMetaVar("<DOMAIN>");
182
183
0
    AddArg("gcp", 0,
184
0
           _("Add ground control point, formatted as "
185
0
             "pixel,line,easting,northing[,elevation], or @filename"),
186
0
           &m_gcps)
187
0
        .SetPackedValuesAllowed(false)
188
0
        .AddValidationAction(
189
0
            [this]()
190
0
            {
191
0
                if (GetGCPFilename(m_gcps).empty())
192
0
                {
193
0
                    for (const std::string &gcp : m_gcps)
194
0
                    {
195
0
                        const CPLStringList aosTokens(
196
0
                            CSLTokenizeString2(gcp.c_str(), ",", 0));
197
0
                        if (aosTokens.size() != 4 && aosTokens.size() != 5)
198
0
                        {
199
0
                            ReportError(CE_Failure, CPLE_IllegalArg,
200
0
                                        "Bad format for %s", gcp.c_str());
201
0
                            return false;
202
0
                        }
203
0
                        for (int i = 0; i < aosTokens.size(); ++i)
204
0
                        {
205
0
                            if (CPLGetValueType(aosTokens[i]) ==
206
0
                                CPL_VALUE_STRING)
207
0
                            {
208
0
                                ReportError(CE_Failure, CPLE_IllegalArg,
209
0
                                            "Bad format for %s", gcp.c_str());
210
0
                                return false;
211
0
                            }
212
0
                        }
213
0
                    }
214
0
                }
215
0
                return true;
216
0
            });
217
218
0
    if (standaloneStep)
219
0
    {
220
0
        AddArg("stats", 0, _("Compute statistics, using all pixels"), &m_stats)
221
0
            .SetMutualExclusionGroup("stats");
222
0
        AddArg("approx-stats", 0,
223
0
               _("Compute statistics, using a subset of pixels"),
224
0
               &m_approxStats)
225
0
            .SetMutualExclusionGroup("stats");
226
0
        AddArg("hist", 0, _("Compute histogram"), &m_hist);
227
0
    }
228
0
}
229
230
/************************************************************************/
231
/*         GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm()          */
232
/************************************************************************/
233
234
0
GDALRasterEditAlgorithm::~GDALRasterEditAlgorithm() = default;
235
236
/************************************************************************/
237
/*                             ParseGCPs()                              */
238
/************************************************************************/
239
240
std::vector<gdal::GCP> GDALRasterEditAlgorithm::ParseGCPs() const
241
0
{
242
0
    std::vector<gdal::GCP> ret;
243
0
    const std::string osGCPFilename = GetGCPFilename(m_gcps);
244
0
    if (!osGCPFilename.empty())
245
0
    {
246
0
        auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
247
0
            osGCPFilename.c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR));
248
0
        if (!poDS)
249
0
            return ret;
250
0
        if (poDS->GetLayerCount() != 1)
251
0
        {
252
0
            ReportError(CE_Failure, CPLE_AppDefined,
253
0
                        "GCPs can only be specified for single-layer datasets");
254
0
            return ret;
255
0
        }
256
0
        auto poLayer = poDS->GetLayer(0);
257
0
        const auto poLayerDefn = poLayer->GetLayerDefn();
258
0
        int nIdIdx = -1, nInfoIdx = -1, nColIdx = -1, nLineIdx = -1, nXIdx = -1,
259
0
            nYIdx = -1, nZIdx = -1;
260
261
0
        const struct
262
0
        {
263
0
            int &idx;
264
0
            const char *name;
265
0
            bool required;
266
0
        } aFields[] = {
267
0
            {nIdIdx, "id", false},     {nInfoIdx, "info", false},
268
0
            {nColIdx, "column", true}, {nLineIdx, "line", true},
269
0
            {nXIdx, "x", true},        {nYIdx, "y", true},
270
0
            {nZIdx, "z", false},
271
0
        };
272
273
0
        for (auto &field : aFields)
274
0
        {
275
0
            field.idx = poLayerDefn->GetFieldIndex(field.name);
276
0
            if (field.idx < 0 && field.required)
277
0
            {
278
0
                ReportError(CE_Failure, CPLE_AppDefined,
279
0
                            "Field '%s' cannot be found in '%s'", field.name,
280
0
                            poDS->GetDescription());
281
0
                return ret;
282
0
            }
283
0
        }
284
0
        for (auto &&poFeature : poLayer)
285
0
        {
286
0
            gdal::GCP gcp;
287
0
            if (nIdIdx >= 0)
288
0
                gcp.SetId(poFeature->GetFieldAsString(nIdIdx));
289
0
            if (nInfoIdx >= 0)
290
0
                gcp.SetInfo(poFeature->GetFieldAsString(nInfoIdx));
291
0
            gcp.Pixel() = poFeature->GetFieldAsDouble(nColIdx);
292
0
            gcp.Line() = poFeature->GetFieldAsDouble(nLineIdx);
293
0
            gcp.X() = poFeature->GetFieldAsDouble(nXIdx);
294
0
            gcp.Y() = poFeature->GetFieldAsDouble(nYIdx);
295
0
            if (nZIdx >= 0 && poFeature->IsFieldSetAndNotNull(nZIdx))
296
0
                gcp.Z() = poFeature->GetFieldAsDouble(nZIdx);
297
0
            ret.push_back(std::move(gcp));
298
0
        }
299
0
    }
300
0
    else
301
0
    {
302
0
        for (const std::string &gcpStr : m_gcps)
303
0
        {
304
0
            const CPLStringList aosTokens(
305
0
                CSLTokenizeString2(gcpStr.c_str(), ",", 0));
306
            // Verified by validation action
307
0
            CPLAssert(aosTokens.size() == 4 || aosTokens.size() == 5);
308
0
            gdal::GCP gcp;
309
0
            gcp.Pixel() = CPLAtof(aosTokens[0]);
310
0
            gcp.Line() = CPLAtof(aosTokens[1]);
311
0
            gcp.X() = CPLAtof(aosTokens[2]);
312
0
            gcp.Y() = CPLAtof(aosTokens[3]);
313
0
            if (aosTokens.size() == 5)
314
0
                gcp.Z() = CPLAtof(aosTokens[4]);
315
0
            ret.push_back(std::move(gcp));
316
0
        }
317
0
    }
318
0
    return ret;
319
0
}
320
321
/************************************************************************/
322
/*                  GDALRasterEditAlgorithm::RunStep()                  */
323
/************************************************************************/
324
325
bool GDALRasterEditAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
326
0
{
327
0
    GDALDataset *poDS = m_dataset.GetDatasetRef();
328
0
    if (poDS)
329
0
    {
330
        // Standalone mode
331
0
        if (poDS->GetAccess() != GA_Update && !m_readOnly)
332
0
        {
333
0
            ReportError(CE_Failure, CPLE_AppDefined,
334
0
                        "Dataset should be opened in update mode unless "
335
0
                        "--auxiliary is set");
336
0
            return false;
337
0
        }
338
0
    }
339
0
    else
340
0
    {
341
        // Pipeline mode
342
0
        const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
343
0
        CPLAssert(poSrcDS);
344
0
        CPLAssert(m_outputDataset.GetName().empty());
345
0
        CPLAssert(!m_outputDataset.GetDatasetRef());
346
0
        auto poDriver = poSrcDS->GetDriver();
347
0
        if (poDriver && EQUAL(poDriver->GetDescription(), "VRT") &&
348
0
            poSrcDS->GetDescription()[0] == '\0')
349
0
        {
350
            // We can directly edit an anonymous input VRT file
351
            // and we actually need to do that since the generic code path
352
            // in the other branch will try to serialize and deserialize a XML
353
            // file pointing to an anonymous source.
354
0
            m_outputDataset.Set(poSrcDS);
355
0
        }
356
0
        else
357
0
        {
358
            // Create a in-memory VRT to avoid modifying the source dataset.
359
0
            CPLStringList aosOptions;
360
0
            aosOptions.push_back("-of");
361
0
            aosOptions.push_back("VRT");
362
0
            GDALTranslateOptions *psOptions =
363
0
                GDALTranslateOptionsNew(aosOptions.List(), nullptr);
364
0
            GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
365
0
            auto poRetDS = GDALDataset::FromHandle(
366
0
                GDALTranslate("", hSrcDS, psOptions, nullptr));
367
0
            GDALTranslateOptionsFree(psOptions);
368
0
            m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
369
0
        }
370
0
        poDS = m_outputDataset.GetDatasetRef();
371
0
    }
372
373
0
    bool ret = poDS != nullptr;
374
375
0
    if (poDS)
376
0
    {
377
0
        if (m_overrideCrs == "null" || m_overrideCrs == "none")
378
0
        {
379
0
            if (poDS->SetSpatialRef(nullptr) != CE_None)
380
0
            {
381
0
                ReportError(CE_Failure, CPLE_AppDefined,
382
0
                            "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
383
0
                return false;
384
0
            }
385
0
        }
386
0
        else if (!m_overrideCrs.empty() && m_gcps.empty())
387
0
        {
388
0
            OGRSpatialReference oSRS;
389
0
            oSRS.SetFromUserInput(m_overrideCrs.c_str());
390
0
            oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
391
0
            if (poDS->SetSpatialRef(&oSRS) != CE_None)
392
0
            {
393
0
                ReportError(CE_Failure, CPLE_AppDefined,
394
0
                            "SetSpatialRef(%s) failed", m_overrideCrs.c_str());
395
0
                return false;
396
0
            }
397
0
        }
398
399
0
        if (!m_bbox.empty())
400
0
        {
401
0
            if (poDS->GetRasterXSize() == 0 || poDS->GetRasterYSize() == 0)
402
0
            {
403
0
                ReportError(CE_Failure, CPLE_AppDefined,
404
0
                            "Cannot set extent because one of dataset height "
405
0
                            "or width is null");
406
0
                return false;
407
0
            }
408
0
            GDALGeoTransform gt;
409
0
            gt.xorig = m_bbox[0];
410
0
            gt.xscale = (m_bbox[2] - m_bbox[0]) / poDS->GetRasterXSize();
411
0
            gt.xrot = 0;
412
0
            gt.yorig = m_bbox[3];
413
0
            gt.yrot = 0;
414
0
            gt.yscale = -(m_bbox[3] - m_bbox[1]) / poDS->GetRasterYSize();
415
0
            if (poDS->SetGeoTransform(gt) != CE_None)
416
0
            {
417
0
                ReportError(CE_Failure, CPLE_AppDefined,
418
0
                            "Setting extent failed");
419
0
                return false;
420
0
            }
421
0
        }
422
423
0
        if (!m_nodata.empty())
424
0
        {
425
0
            for (int i = 0; i < poDS->GetRasterCount(); ++i)
426
0
            {
427
0
                if (EQUAL(m_nodata.c_str(), "none"))
428
0
                    poDS->GetRasterBand(i + 1)->DeleteNoDataValue();
429
0
                else
430
0
                    poDS->GetRasterBand(i + 1)->SetNoDataValue(
431
0
                        CPLAtof(m_nodata.c_str()));
432
0
            }
433
0
        }
434
435
0
        if (!m_colorMap.empty())
436
0
        {
437
0
            std::unique_ptr<GDALDataset> poAuxDS;
438
0
            {
439
0
                CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
440
0
                poAuxDS = std::unique_ptr<GDALDataset>(
441
0
                    GDALDataset::Open(m_colorMap.c_str(), GDAL_OF_RASTER));
442
0
            }
443
0
            if (m_band == 0)
444
0
                m_band = 1;
445
0
            if (poDS->GetRasterCount() < m_band)
446
0
            {
447
0
                ReportError(CE_Failure, CPLE_AppDefined,
448
0
                            "Band %d is not valid for %s", m_band,
449
0
                            poDS->GetDescription());
450
0
                return false;
451
0
            }
452
0
            if (poAuxDS)
453
0
            {
454
0
                if (poAuxDS->GetRasterCount() == 0)
455
0
                {
456
0
                    ReportError(CE_Failure, CPLE_AppDefined,
457
0
                                "%s has no raster band", m_colorMap.c_str());
458
0
                    return false;
459
0
                }
460
0
                const auto poCT = poAuxDS->GetRasterBand(1)->GetColorTable();
461
0
                if (!poCT)
462
0
                {
463
0
                    ReportError(CE_Failure, CPLE_AppDefined,
464
0
                                "%s has no color table", m_colorMap.c_str());
465
0
                    return false;
466
0
                }
467
0
                if (poDS->GetRasterBand(m_band)->SetColorTable(poCT) != CE_None)
468
0
                {
469
0
                    return false;
470
0
                }
471
0
                if (m_colorInterpretation.empty())
472
0
                {
473
0
                    poDS->GetRasterBand(m_band)->SetColorInterpretation(
474
0
                        GCI_PaletteIndex);
475
0
                }
476
0
            }
477
0
            else
478
0
            {
479
0
                std::vector<GDALColorAssociation> asColors =
480
0
                    GDALLoadTextColorMap(m_colorMap.c_str(),
481
0
                                         poDS->GetRasterBand(m_band));
482
0
                if (asColors.empty())
483
0
                {
484
0
                    ReportError(CE_Failure, CPLE_AppDefined,
485
0
                                "%s has no color table", m_colorMap.c_str());
486
0
                    return false;
487
0
                }
488
0
                GDALColorTable oCT;
489
0
                for (const auto &sColor : asColors)
490
0
                {
491
0
                    if (!(sColor.dfVal >= 0 && sColor.dfVal < 65536 &&
492
0
                          static_cast<int>(sColor.dfVal) == sColor.dfVal))
493
0
                    {
494
0
                        ReportError(CE_Failure, CPLE_AppDefined,
495
0
                                    "Value %f of color map is not compatible "
496
0
                                    "with an integer index",
497
0
                                    sColor.dfVal);
498
0
                        return false;
499
0
                    }
500
0
                    GDALColorEntry entry;
501
0
                    entry.c1 = static_cast<short>(sColor.nR);
502
0
                    entry.c2 = static_cast<short>(sColor.nG);
503
0
                    entry.c3 = static_cast<short>(sColor.nB);
504
0
                    entry.c4 = static_cast<short>(sColor.nA);
505
0
                    oCT.SetColorEntry(static_cast<int>(sColor.dfVal), &entry);
506
0
                }
507
0
                if (poDS->GetRasterBand(m_band)->SetColorTable(&oCT) != CE_None)
508
0
                {
509
0
                    return false;
510
0
                }
511
0
                if (m_colorInterpretation.empty())
512
0
                {
513
0
                    poDS->GetRasterBand(m_band)->SetColorInterpretation(
514
0
                        GCI_PaletteIndex);
515
0
                }
516
0
            }
517
0
        }
518
0
        else if (m_unsetColorTable)
519
0
        {
520
0
            if (m_band == 0)
521
0
                m_band = 1;
522
0
            if (poDS->GetRasterCount() < m_band)
523
0
            {
524
0
                ReportError(CE_Failure, CPLE_AppDefined,
525
0
                            "Band %d is not valid for %s", m_band,
526
0
                            poDS->GetDescription());
527
0
                return false;
528
0
            }
529
0
            if (poDS->GetRasterBand(m_band)->SetColorTable(nullptr) != CE_None)
530
0
            {
531
0
                return false;
532
0
            }
533
0
            if (m_colorInterpretation.empty())
534
0
            {
535
0
                poDS->GetRasterBand(m_band)->SetColorInterpretation(
536
0
                    GCI_Undefined);
537
0
            }
538
0
        }
539
540
0
        if (!m_colorInterpretation.empty())
541
0
        {
542
0
            const auto GetColorInterp =
543
0
                [this](const char *pszStr) -> std::optional<GDALColorInterp>
544
0
            {
545
0
                if (EQUAL(pszStr, "undefined"))
546
0
                    return GCI_Undefined;
547
0
                const GDALColorInterp eInterp =
548
0
                    GDALGetColorInterpretationByName(pszStr);
549
0
                if (eInterp != GCI_Undefined)
550
0
                    return eInterp;
551
0
                ReportError(CE_Failure, CPLE_NotSupported,
552
0
                            "Unsupported color interpretation: %s", pszStr);
553
0
                return {};
554
0
            };
555
556
0
            if (m_colorInterpretation.size() == 1 &&
557
0
                poDS->GetRasterCount() > 1 &&
558
0
                !cpl::starts_with(m_colorInterpretation[0], "all="))
559
0
            {
560
0
                ReportError(
561
0
                    CE_Failure, CPLE_NotSupported,
562
0
                    "With several bands, specify as many color interpretation "
563
0
                    "as bands, one or many values of the form "
564
0
                    "<band_number>=<color> or a single value all=<color>");
565
0
                return false;
566
0
            }
567
0
            else
568
0
            {
569
0
                int nBandIter = 0;
570
0
                bool bSyntaxAll = false;
571
0
                bool bSyntaxExplicitBand = false;
572
0
                bool bSyntaxImplicitBand = false;
573
0
                for (const std::string &token : m_colorInterpretation)
574
0
                {
575
0
                    const CPLStringList aosTokens(
576
0
                        CSLTokenizeString2(token.c_str(), "=", 0));
577
0
                    if (aosTokens.size() == 2 && EQUAL(aosTokens[0], "all"))
578
0
                    {
579
0
                        bSyntaxAll = true;
580
0
                        const auto eColorInterp = GetColorInterp(aosTokens[1]);
581
0
                        if (!eColorInterp)
582
0
                        {
583
0
                            return false;
584
0
                        }
585
0
                        else
586
0
                        {
587
0
                            for (int i = 0; i < poDS->GetRasterCount(); ++i)
588
0
                            {
589
0
                                if (poDS->GetRasterBand(i + 1)
590
0
                                        ->SetColorInterpretation(
591
0
                                            *eColorInterp) != CE_None)
592
0
                                    return false;
593
0
                            }
594
0
                        }
595
0
                    }
596
0
                    else if (aosTokens.size() == 2)
597
0
                    {
598
0
                        bSyntaxExplicitBand = true;
599
0
                        const int nBand = atoi(aosTokens[0]);
600
0
                        if (nBand <= 0 || nBand > poDS->GetRasterCount())
601
0
                        {
602
0
                            ReportError(CE_Failure, CPLE_NotSupported,
603
0
                                        "Invalid band number '%s' in '%s'",
604
0
                                        aosTokens[0], token.c_str());
605
0
                            return false;
606
0
                        }
607
0
                        const auto eColorInterp = GetColorInterp(aosTokens[1]);
608
0
                        if (!eColorInterp)
609
0
                        {
610
0
                            return false;
611
0
                        }
612
0
                        else if (poDS->GetRasterBand(nBand)
613
0
                                     ->SetColorInterpretation(*eColorInterp) !=
614
0
                                 CE_None)
615
0
                        {
616
0
                            return false;
617
0
                        }
618
0
                    }
619
0
                    else
620
0
                    {
621
0
                        bSyntaxImplicitBand = true;
622
0
                        ++nBandIter;
623
0
                        if (nBandIter > poDS->GetRasterCount())
624
0
                        {
625
0
                            ReportError(CE_Failure, CPLE_IllegalArg,
626
0
                                        "More color interpretation values "
627
0
                                        "specified than bands in the dataset");
628
0
                            return false;
629
0
                        }
630
0
                        const auto eColorInterp = GetColorInterp(token.c_str());
631
0
                        if (!eColorInterp)
632
0
                        {
633
0
                            return false;
634
0
                        }
635
0
                        else if (poDS->GetRasterBand(nBandIter)
636
0
                                     ->SetColorInterpretation(*eColorInterp) !=
637
0
                                 CE_None)
638
0
                        {
639
0
                            return false;
640
0
                        }
641
0
                    }
642
0
                }
643
0
                if ((bSyntaxAll ? 1 : 0) + (bSyntaxExplicitBand ? 1 : 0) +
644
0
                        (bSyntaxImplicitBand ? 1 : 0) !=
645
0
                    1)
646
0
                {
647
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
648
0
                                "Mix of different syntaxes to specify color "
649
0
                                "interpretation");
650
0
                    return false;
651
0
                }
652
0
                if (bSyntaxImplicitBand && nBandIter != poDS->GetRasterCount())
653
0
                {
654
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
655
0
                                "Less color interpretation values specified "
656
0
                                "than bands in the dataset");
657
0
                    return false;
658
0
                }
659
0
            }
660
0
        }
661
662
0
        const auto ScaleOffsetSetterLambda =
663
0
            [this, poDS](const char *argName,
664
0
                         const std::vector<std::string> &values,
665
0
                         CPLErr (GDALRasterBand::*Setter)(double))
666
0
        {
667
0
            if (values.size() == 1 && values[0].find('=') == std::string::npos)
668
0
            {
669
0
                const double dfScale = CPLAtof(values[0].c_str());
670
0
                for (int i = 0; i < poDS->GetRasterCount(); ++i)
671
0
                {
672
0
                    if ((poDS->GetRasterBand(i + 1)->*Setter)(dfScale) !=
673
0
                        CE_None)
674
0
                        return false;
675
0
                }
676
0
            }
677
0
            else
678
0
            {
679
0
                int nBandIter = 0;
680
0
                bool bSyntaxExplicitBand = false;
681
0
                bool bSyntaxImplicitBand = false;
682
0
                for (const std::string &token : values)
683
0
                {
684
0
                    const CPLStringList aosTokens(
685
0
                        CSLTokenizeString2(token.c_str(), "=", 0));
686
0
                    if (aosTokens.size() == 2)
687
0
                    {
688
0
                        bSyntaxExplicitBand = true;
689
0
                        const int nBand = atoi(aosTokens[0]);
690
0
                        if (nBand <= 0 || nBand > poDS->GetRasterCount())
691
0
                        {
692
0
                            ReportError(CE_Failure, CPLE_NotSupported,
693
0
                                        "Invalid band number '%s' in '%s'",
694
0
                                        aosTokens[0], token.c_str());
695
0
                            return false;
696
0
                        }
697
0
                        const double dfScale = CPLAtof(aosTokens[1]);
698
0
                        if ((poDS->GetRasterBand(nBand)->*Setter)(dfScale) !=
699
0
                            CE_None)
700
0
                        {
701
0
                            return false;
702
0
                        }
703
0
                    }
704
0
                    else
705
0
                    {
706
0
                        bSyntaxImplicitBand = true;
707
0
                        ++nBandIter;
708
0
                        if (nBandIter > poDS->GetRasterCount())
709
0
                        {
710
0
                            ReportError(CE_Failure, CPLE_IllegalArg,
711
0
                                        "More %s values "
712
0
                                        "specified than bands in the dataset",
713
0
                                        argName);
714
0
                            return false;
715
0
                        }
716
0
                        const double dfScale = CPLAtof(token.c_str());
717
0
                        if ((poDS->GetRasterBand(nBandIter)->*Setter)(
718
0
                                dfScale) != CE_None)
719
0
                        {
720
0
                            return false;
721
0
                        }
722
0
                    }
723
0
                }
724
0
                if (((bSyntaxExplicitBand ? 1 : 0) +
725
0
                     (bSyntaxImplicitBand ? 1 : 0)) != 1)
726
0
                {
727
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
728
0
                                "Mix of different syntaxes to specify %s",
729
0
                                argName);
730
0
                    return false;
731
0
                }
732
0
                if (bSyntaxImplicitBand && nBandIter != poDS->GetRasterCount())
733
0
                {
734
0
                    ReportError(CE_Failure, CPLE_IllegalArg,
735
0
                                "Less %s values specified "
736
0
                                "than bands in the dataset",
737
0
                                argName);
738
0
                    return false;
739
0
                }
740
0
            }
741
742
0
            return true;
743
0
        };
744
745
0
        if (!m_scale.empty())
746
0
        {
747
0
            if (!ScaleOffsetSetterLambda("scale", m_scale,
748
0
                                         &GDALRasterBand::SetScale))
749
0
                return false;
750
0
        }
751
752
0
        if (!m_offset.empty())
753
0
        {
754
0
            if (!ScaleOffsetSetterLambda("offset", m_offset,
755
0
                                         &GDALRasterBand::SetOffset))
756
0
                return false;
757
0
        }
758
759
0
        const CPLStringList aosMD(m_metadata);
760
0
        for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
761
0
        {
762
0
            if (poDS->SetMetadataItem(key, value) != CE_None)
763
0
            {
764
0
                ReportError(CE_Failure, CPLE_AppDefined,
765
0
                            "SetMetadataItem('%s', '%s') failed", key, value);
766
0
                return false;
767
0
            }
768
0
        }
769
770
0
        for (const std::string &key : m_unsetMetadata)
771
0
        {
772
0
            if (poDS->SetMetadataItem(key.c_str(), nullptr) != CE_None)
773
0
            {
774
0
                ReportError(CE_Failure, CPLE_AppDefined,
775
0
                            "SetMetadataItem('%s', NULL) failed", key.c_str());
776
0
                return false;
777
0
            }
778
0
        }
779
780
0
        for (const std::string &domain : m_unsetMetadataDomain)
781
0
        {
782
0
            if (poDS->SetMetadata(nullptr, domain.c_str()) != CE_None)
783
0
            {
784
0
                ReportError(CE_Failure, CPLE_AppDefined,
785
0
                            "SetMetadata(NULL, '%s') failed", domain.c_str());
786
0
                return false;
787
0
            }
788
0
        }
789
790
0
        if (!m_gcps.empty())
791
0
        {
792
0
            const auto gcps = ParseGCPs();
793
0
            if (gcps.empty())
794
0
                return false;  // error already emitted by ParseGCPs()
795
796
0
            OGRSpatialReference oSRS;
797
0
            if (!m_overrideCrs.empty())
798
0
            {
799
0
                oSRS.SetFromUserInput(m_overrideCrs.c_str());
800
0
                oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
801
0
            }
802
803
0
            if (poDS->SetGCPs(static_cast<int>(gcps.size()), gcps[0].c_ptr(),
804
0
                              oSRS.IsEmpty() ? nullptr : &oSRS) != CE_None)
805
0
            {
806
0
                ReportError(CE_Failure, CPLE_AppDefined, "Setting GCPs failed");
807
0
                return false;
808
0
            }
809
0
        }
810
811
0
        const int nBands = poDS->GetRasterCount();
812
0
        int nCurProgress = 0;
813
0
        const double dfTotalProgress =
814
0
            ((m_stats || m_approxStats) ? nBands : 0) + (m_hist ? nBands : 0);
815
0
        if (m_stats || m_approxStats)
816
0
        {
817
0
            for (int i = 0; (i < nBands) && ret; ++i)
818
0
            {
819
0
                void *pScaledProgress = GDALCreateScaledProgress(
820
0
                    nCurProgress / dfTotalProgress,
821
0
                    (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
822
0
                    ctxt.m_pProgressData);
823
0
                ++nCurProgress;
824
0
                double dfMin = 0.0;
825
0
                double dfMax = 0.0;
826
0
                double dfMean = 0.0;
827
0
                double dfStdDev = 0.0;
828
0
                ret = poDS->GetRasterBand(i + 1)->ComputeStatistics(
829
0
                          m_approxStats, &dfMin, &dfMax, &dfMean, &dfStdDev,
830
0
                          pScaledProgress ? GDALScaledProgress : nullptr,
831
0
                          pScaledProgress, nullptr) == CE_None;
832
0
                GDALDestroyScaledProgress(pScaledProgress);
833
0
            }
834
0
        }
835
836
0
        if (m_hist)
837
0
        {
838
0
            for (int i = 0; (i < nBands) && ret; ++i)
839
0
            {
840
0
                void *pScaledProgress = GDALCreateScaledProgress(
841
0
                    nCurProgress / dfTotalProgress,
842
0
                    (nCurProgress + 1) / dfTotalProgress, ctxt.m_pfnProgress,
843
0
                    ctxt.m_pProgressData);
844
0
                ++nCurProgress;
845
0
                double dfMin = 0.0;
846
0
                double dfMax = 0.0;
847
0
                int nBucketCount = 0;
848
0
                GUIntBig *panHistogram = nullptr;
849
0
                ret = poDS->GetRasterBand(i + 1)->GetDefaultHistogram(
850
0
                          &dfMin, &dfMax, &nBucketCount, &panHistogram, TRUE,
851
0
                          pScaledProgress ? GDALScaledProgress : nullptr,
852
0
                          pScaledProgress) == CE_None;
853
0
                if (ret)
854
0
                {
855
0
                    ret = poDS->GetRasterBand(i + 1)->SetDefaultHistogram(
856
0
                              dfMin, dfMax, nBucketCount, panHistogram) ==
857
0
                          CE_None;
858
0
                }
859
0
                CPLFree(panHistogram);
860
0
                GDALDestroyScaledProgress(pScaledProgress);
861
0
            }
862
0
        }
863
0
    }
864
865
0
    return poDS != nullptr;
866
0
}
867
868
GDALRasterEditAlgorithmStandalone::~GDALRasterEditAlgorithmStandalone() =
869
    default;
870
871
//! @endcond