Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_select.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "select" step of "raster pipeline"
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_raster_select.h"
14
15
#include "gdal_priv.h"
16
#include "gdal_utils.h"
17
18
#include <map>
19
#include <set>
20
21
//! @cond Doxygen_Suppress
22
23
#ifndef _
24
0
#define _(x) (x)
25
#endif
26
27
static std::optional<std::vector<int>> ParseBandRange(const std::string &v,
28
                                                      int nBands)
29
0
{
30
0
    CPLStringList bandSel = cpl::tokenize_string(v, ":", CSLT_ALLOWEMPTYTOKENS);
31
0
    if (bandSel.Count() < 2 || bandSel.Count() > 3)
32
0
    {
33
0
        CPLError(CE_Failure, CPLE_IllegalArg, "Invalid value for --band: %s",
34
0
                 v.c_str());
35
0
        return std::nullopt;
36
0
    }
37
0
    int nFirst = 1;
38
0
    const auto osvFirst = cpl::trim(bandSel[0]);
39
0
    if (!osvFirst.empty())
40
0
    {
41
0
        const auto maybeStart = cpl::strict_parse<int>(osvFirst);
42
0
        if (maybeStart.has_value())
43
0
        {
44
0
            nFirst = maybeStart.value();
45
0
            if (nFirst < 0)
46
0
            {
47
0
                nFirst += nBands + 1;
48
0
            }
49
0
            if (nFirst > nBands || nFirst <= 0)
50
0
            {
51
0
                CPLError(CE_Failure, CPLE_IllegalArg, "Invalid band: %s",
52
0
                         bandSel[0]);
53
0
                return std::nullopt;
54
0
            }
55
0
        }
56
0
        else
57
0
        {
58
0
            CPLError(CE_Failure, CPLE_IllegalArg,
59
0
                     "Failed to parse start value of --band range: %s",
60
0
                     bandSel[0]);
61
0
            return std::nullopt;
62
0
        }
63
0
    }
64
0
    int nLast = nBands;
65
0
    const auto osvLast = cpl::trim(bandSel[1]);
66
0
    if (!osvLast.empty())
67
0
    {
68
0
        const auto maybeLast = cpl::strict_parse<int>(osvLast);
69
0
        if (maybeLast.has_value())
70
0
        {
71
0
            nLast = maybeLast.value();
72
0
            if (nLast < 0)
73
0
            {
74
0
                nLast += nBands + 1;
75
0
            }
76
0
            if (nLast > nBands || nLast <= 0)
77
0
            {
78
0
                CPLError(CE_Failure, CPLE_IllegalArg, "Invalid band: %s",
79
0
                         bandSel[1]);
80
0
                return std::nullopt;
81
0
            }
82
0
        }
83
0
        else
84
0
        {
85
0
            CPLError(CE_Failure, CPLE_IllegalArg,
86
0
                     "Failed to parse stop value of --band range: %s",
87
0
                     bandSel[1]);
88
0
            return std::nullopt;
89
0
        }
90
0
    }
91
0
    int nStep = nFirst < nLast ? 1 : -1;
92
0
    if (bandSel.Count() == 3)
93
0
    {
94
0
        const auto maybeStep = cpl::strict_parse<int>(bandSel[2]);
95
0
        if (maybeStep.has_value())
96
0
        {
97
0
            nStep = maybeStep.value();
98
0
        }
99
0
        else
100
0
        {
101
0
            CPLError(CE_Failure, CPLE_IllegalArg,
102
0
                     "Failed to parse step value of --band range: %s",
103
0
                     bandSel[2]);
104
0
            return std::nullopt;
105
0
        }
106
0
    }
107
108
0
    if (nFirst < nLast && nStep <= 0)
109
0
    {
110
0
        CPLError(CE_Failure, CPLE_AppDefined, "Step value must be positive");
111
0
        return std::nullopt;
112
0
    }
113
0
    if (nFirst > nLast && nStep >= 0)
114
0
    {
115
0
        CPLError(CE_Failure, CPLE_AppDefined, "Step value must be negative");
116
0
        return std::nullopt;
117
0
    }
118
119
0
    std::vector<int> ret;
120
121
0
    for (int iBand = nFirst; nStep > 0 ? iBand <= nLast : iBand >= nLast;
122
0
         iBand += nStep)
123
0
    {
124
0
        if (iBand < 1 || iBand > nBands)
125
0
        {
126
0
            CPLError(CE_Failure, CPLE_IllegalArg, "Invalid band: %d", iBand);
127
0
            return std::nullopt;
128
0
        }
129
130
0
        ret.push_back(iBand);
131
0
    }
132
133
0
    return ret;
134
0
}
135
136
/************************************************************************/
137
/*        GDALRasterSelectAlgorithm::GDALRasterSelectAlgorithm()        */
138
/************************************************************************/
139
140
GDALRasterSelectAlgorithm::GDALRasterSelectAlgorithm(bool standaloneStep)
141
0
    : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
142
0
                                      standaloneStep)
143
0
{
144
0
    {
145
0
        auto &arg = AddArg("band", 'b',
146
0
                           _("Band(s) (1-based index, 'mask', 'mask:<band>' or "
147
0
                             "color interpretation such as 'red')"),
148
0
                           &m_bands)
149
0
                        .SetPositional()
150
0
                        .SetRequired()
151
0
                        .SetMinCount(1);
152
0
        arg.SetAutoCompleteFunction(
153
0
            [this](const std::string &)
154
0
            {
155
0
                std::vector<std::string> ret;
156
0
                std::unique_ptr<GDALDataset> poSrcDSTmp;
157
0
                GDALDataset *poSrcDS = m_inputDataset.empty()
158
0
                                           ? nullptr
159
0
                                           : m_inputDataset[0].GetDatasetRef();
160
0
                if (!poSrcDS && !m_inputDataset.empty())
161
0
                {
162
0
                    CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
163
0
                    poSrcDSTmp.reset(GDALDataset::Open(
164
0
                        m_inputDataset[0].GetName().c_str(), GDAL_OF_RASTER));
165
0
                    poSrcDS = poSrcDSTmp.get();
166
0
                }
167
0
                if (poSrcDS)
168
0
                {
169
0
                    std::set<GDALColorInterp> oSetColorInterp;
170
0
                    for (int i = 1; i <= poSrcDS->GetRasterCount(); ++i)
171
0
                    {
172
0
                        ret.push_back(std::to_string(i));
173
0
                        oSetColorInterp.insert(poSrcDS->GetRasterBand(i)
174
0
                                                   ->GetColorInterpretation());
175
0
                    }
176
0
                    ret.push_back("mask");
177
0
                    for (const auto eColorInterp : oSetColorInterp)
178
0
                    {
179
0
                        ret.push_back(CPLString(GDALGetColorInterpretationName(
180
0
                                                    eColorInterp))
181
0
                                          .tolower());
182
0
                    }
183
0
                }
184
0
                return ret;
185
0
            });
186
0
        arg.AddValidationAction(
187
0
            [&arg]()
188
0
            {
189
0
                int nColorInterpretations = 0;
190
0
                const auto paeColorInterp =
191
0
                    GDALGetColorInterpretationList(&nColorInterpretations);
192
0
                std::set<std::string> oSetValidColorInterp;
193
0
                for (int i = 0; i < nColorInterpretations; ++i)
194
0
                    oSetValidColorInterp.insert(
195
0
                        CPLString(
196
0
                            GDALGetColorInterpretationName(paeColorInterp[i]))
197
0
                            .tolower());
198
199
0
                const auto &val = arg.Get<std::vector<std::string>>();
200
0
                for (const auto &v : val)
201
0
                {
202
0
                    if (!STARTS_WITH(v.c_str(), "mask") &&
203
0
                        v.find(":") == std::string::npos &&
204
0
                        CPLGetValueType(v.c_str()) != CPL_VALUE_INTEGER &&
205
0
                        !cpl::contains(oSetValidColorInterp,
206
0
                                       CPLString(v).tolower()))
207
0
                    {
208
0
                        CPLError(CE_Failure, CPLE_AppDefined,
209
0
                                 "Invalid band specification.");
210
0
                        return false;
211
0
                    }
212
0
                }
213
0
                return true;
214
0
            });
215
0
    }
216
217
0
    AddArg("exclude", 0, _("Exclude specified bands"), &m_exclude);
218
219
0
    {
220
0
        auto &arg = AddArg(
221
0
            "mask", 0,
222
0
            _("Mask band (1-based index, 'mask', 'mask:<band>' or 'none')"),
223
0
            &m_mask);
224
0
        arg.AddValidationAction(
225
0
            [&arg]()
226
0
            {
227
0
                const auto &v = arg.Get<std::string>();
228
0
                if (!STARTS_WITH(v.c_str(), "mask") &&
229
0
                    !EQUAL(v.c_str(), "none") &&
230
0
                    !(CPLGetValueType(v.c_str()) == CPL_VALUE_INTEGER &&
231
0
                      atoi(v.c_str()) >= 1))
232
0
                {
233
0
                    CPLError(CE_Failure, CPLE_AppDefined,
234
0
                             "Invalid mask band specification.");
235
0
                    return false;
236
0
                }
237
0
                return true;
238
0
            });
239
0
    }
240
0
}
241
242
/************************************************************************/
243
/*                 GDALRasterSelectAlgorithm::RunStep()                 */
244
/************************************************************************/
245
246
bool GDALRasterSelectAlgorithm::RunStep(GDALPipelineStepRunContext &)
247
0
{
248
0
    const auto poSrcDS = m_inputDataset[0].GetDatasetRef();
249
0
    CPLAssert(poSrcDS);
250
0
    CPLAssert(m_outputDataset.GetName().empty());
251
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
252
253
0
    std::map<GDALColorInterp, std::vector<int>> oMapColorInterpToBands;
254
0
    for (int i = 1; i <= poSrcDS->GetRasterCount(); ++i)
255
0
    {
256
0
        oMapColorInterpToBands[poSrcDS->GetRasterBand(i)
257
0
                                   ->GetColorInterpretation()]
258
0
            .push_back(i);
259
0
    }
260
261
0
    CPLStringList aosOptions;
262
0
    aosOptions.AddString("-of");
263
0
    aosOptions.AddString("VRT");
264
0
    if (m_exclude)
265
0
    {
266
0
        if (m_bands.size() >= static_cast<size_t>(poSrcDS->GetRasterCount()))
267
0
        {
268
0
            ReportError(CE_Failure, CPLE_AppDefined,
269
0
                        "Cannot exclude all input bands");
270
0
            return false;
271
0
        }
272
273
0
        std::set<int> excludedBandsFromColor;
274
0
        for (const std::string &v : m_bands)
275
0
        {
276
0
            const auto eColorInterp =
277
0
                GDALGetColorInterpretationByName(v.c_str());
278
0
            if (v == "undefined" || eColorInterp != GCI_Undefined)
279
0
            {
280
0
                const auto iter = oMapColorInterpToBands.find(eColorInterp);
281
0
                if (iter != oMapColorInterpToBands.end())
282
0
                {
283
0
                    for (const int iBand : iter->second)
284
0
                    {
285
0
                        excludedBandsFromColor.insert(iBand);
286
0
                    }
287
0
                }
288
                // We don't emit a warning if there are no bands matching
289
                // the color interpretation, because a potential use case
290
                // could be to run on a set of input files that might have or
291
                // might not have an alpha band, and remove it.
292
0
            }
293
0
        }
294
295
0
        for (int i = 1; i <= poSrcDS->GetRasterCount(); ++i)
296
0
        {
297
0
            const std::string iStr = std::to_string(i);
298
0
            if (std::find(m_bands.begin(), m_bands.end(), iStr) ==
299
0
                    m_bands.end() &&
300
0
                !cpl::contains(excludedBandsFromColor, i))
301
0
            {
302
0
                aosOptions.AddString("-b");
303
0
                aosOptions.AddString(iStr);
304
0
            }
305
0
        }
306
0
    }
307
0
    else
308
0
    {
309
0
        for (const std::string &v : m_bands)
310
0
        {
311
0
            const auto eColorInterp =
312
0
                GDALGetColorInterpretationByName(v.c_str());
313
0
            if (v == "undefined" || eColorInterp != GCI_Undefined)
314
0
            {
315
0
                const auto iter = oMapColorInterpToBands.find(eColorInterp);
316
0
                if (iter == oMapColorInterpToBands.end())
317
0
                {
318
0
                    ReportError(CE_Failure, CPLE_AppDefined,
319
0
                                "No band has color interpretation %s",
320
0
                                v.c_str());
321
0
                    return false;
322
0
                }
323
0
                for (const int iBand : iter->second)
324
0
                {
325
0
                    aosOptions.AddString("-b");
326
0
                    aosOptions.AddString(std::to_string(iBand));
327
0
                }
328
0
            }
329
0
            else if (v.find(':') != std::string::npos)
330
0
            {
331
0
                const auto &aiBands =
332
0
                    ParseBandRange(v, poSrcDS->GetRasterCount());
333
0
                if (!aiBands.has_value())
334
0
                {
335
0
                    return false;
336
0
                }
337
0
                for (int iBand : aiBands.value())
338
0
                {
339
0
                    aosOptions.AddString("-b");
340
0
                    aosOptions.AddString(std::to_string(iBand));
341
0
                }
342
0
            }
343
0
            else if (cpl::equals_ci(v, "mask"))
344
0
            {
345
0
                aosOptions.AddString("-b");
346
0
                aosOptions.AddString(v);
347
0
            }
348
0
            else
349
0
            {
350
0
                const auto maybeBand = cpl::strict_parse<int>(v);
351
0
                if (!maybeBand)
352
0
                {
353
0
                    CPLError(CE_Failure, CPLE_IllegalArg, "Invalid band: %s",
354
0
                             v.c_str());
355
0
                    return false;
356
0
                }
357
0
                const int nBands = poSrcDS->GetRasterCount();
358
0
                int iBand = maybeBand.value();
359
0
                if (iBand < 0)
360
0
                {
361
0
                    iBand += nBands + 1;
362
0
                }
363
364
0
                if (iBand > nBands || iBand < 1)
365
0
                {
366
0
                    CPLError(CE_Failure, CPLE_IllegalArg, "Invalid band: %s",
367
0
                             v.c_str());
368
0
                    return false;
369
0
                }
370
371
0
                aosOptions.AddString("-b");
372
0
                aosOptions.AddString(std::to_string(iBand));
373
0
            }
374
0
        }
375
0
    }
376
0
    if (!m_mask.empty())
377
0
    {
378
0
        aosOptions.AddString("-mask");
379
0
        aosOptions.AddString(CPLString(m_mask).replaceAll(':', ',').c_str());
380
0
    }
381
382
0
    GDALTranslateOptions *psOptions =
383
0
        GDALTranslateOptionsNew(aosOptions.List(), nullptr);
384
385
0
    auto poOutDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
386
0
        GDALTranslate("", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
387
0
    GDALTranslateOptionsFree(psOptions);
388
0
    const bool bRet = poOutDS != nullptr;
389
0
    if (poOutDS)
390
0
    {
391
0
        m_outputDataset.Set(std::move(poOutDS));
392
0
    }
393
394
0
    return bRet;
395
0
}
396
397
0
GDALRasterSelectAlgorithmStandalone::~GDALRasterSelectAlgorithmStandalone() =
398
    default;
399
400
//! @endcond