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_mosaic_stack_common.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Common code of "raster mosaic" and "raster stack"
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_mosaic_stack_common.h"
14
#include "gdalalg_raster_write.h"
15
16
#include "cpl_conv.h"
17
#include "cpl_vsi_virtual.h"
18
19
#include "gdal_priv.h"
20
#include "gdal_utils.h"
21
22
//! @cond Doxygen_Suppress
23
24
#ifndef _
25
0
#define _(x) (x)
26
#endif
27
28
/************************************************************************/
29
/*                       GetConstructorOptions()                        */
30
/************************************************************************/
31
32
/* static */ GDALRasterMosaicStackCommonAlgorithm::ConstructorOptions
33
GDALRasterMosaicStackCommonAlgorithm::GetConstructorOptions(bool standaloneStep)
34
0
{
35
0
    ConstructorOptions opts;
36
0
    opts.SetStandaloneStep(standaloneStep);
37
0
    opts.SetAutoOpenInputDatasets(false);
38
0
    opts.SetInputDatasetHelpMsg(
39
0
        _("Input raster datasets (or specify a @<filename> to point to a "
40
0
          "file containing filenames)"));
41
0
    opts.SetAddDefaultArguments(false);
42
0
    opts.SetInputDatasetMetaVar("INPUTS");
43
0
    opts.SetInputDatasetMaxCount(INT_MAX);
44
0
    return opts;
45
0
}
46
47
/************************************************************************/
48
/*                GDALRasterMosaicStackCommonAlgorithm()                */
49
/************************************************************************/
50
51
GDALRasterMosaicStackCommonAlgorithm::GDALRasterMosaicStackCommonAlgorithm(
52
    const std::string &name, const std::string &description,
53
    const std::string &helpURL, bool bStandalone)
54
0
    : GDALRasterPipelineStepAlgorithm(name, description, helpURL,
55
0
                                      GetConstructorOptions(bStandalone))
56
0
{
57
0
    AddRasterInputArgs(/* openForMixedRasterVector = */ false,
58
0
                       /* hiddenForCLI = */ false);
59
0
    if (bStandalone)
60
0
    {
61
0
        AddProgressArg();
62
0
        AddRasterOutputArgs(false);
63
0
    }
64
65
0
    AddBandArg(&m_bands);
66
0
    AddAbsolutePathArg(
67
0
        &m_writeAbsolutePaths,
68
0
        _("Whether the path to the input datasets should be stored as an "
69
0
          "absolute path"));
70
71
0
    auto &resArg =
72
0
        AddArg("resolution", 0,
73
0
               _("Target resolution (in destination CRS units)"), &m_resolution)
74
0
            .SetDefault("same")
75
0
            .SetMetaVar("<xres>,<yres>|same|average|common|highest|lowest");
76
0
    resArg.AddValidationAction(
77
0
        [this, &resArg]()
78
0
        {
79
0
            const std::string val = resArg.Get<std::string>();
80
0
            if (val != "average" && val != "highest" && val != "lowest" &&
81
0
                val != "same" && val != "common")
82
0
            {
83
0
                const auto aosTokens =
84
0
                    CPLStringList(CSLTokenizeString2(val.c_str(), ",", 0));
85
0
                if (aosTokens.size() != 2 ||
86
0
                    CPLGetValueType(aosTokens[0]) == CPL_VALUE_STRING ||
87
0
                    CPLGetValueType(aosTokens[1]) == CPL_VALUE_STRING ||
88
0
                    CPLAtof(aosTokens[0]) <= 0 || CPLAtof(aosTokens[1]) <= 0)
89
0
                {
90
0
                    ReportError(CE_Failure, CPLE_AppDefined,
91
0
                                "resolution: two comma separated positive "
92
0
                                "values should be provided, or 'same', "
93
0
                                "'average', 'common', 'highest' or 'lowest'");
94
0
                    return false;
95
0
                }
96
0
            }
97
0
            return true;
98
0
        });
99
100
0
    AddBBOXArg(&m_bbox, _("Target bounding box as xmin,ymin,xmax,ymax (in "
101
0
                          "destination CRS units)"));
102
0
    auto &tapArg = AddArg("target-aligned-pixels", 0,
103
0
                          _("Round target extent to target resolution"),
104
0
                          &m_targetAlignedPixels)
105
0
                       .AddHiddenAlias("tap");
106
0
    AddArg("src-nodata", 0, _("Set nodata values for input bands."),
107
0
           &m_srcNoData)
108
0
        .SetMinCount(1)
109
0
        .SetRepeatedArgAllowed(false);
110
0
    AddArg("dst-nodata", 0,
111
0
           _("Set nodata values at the destination band level."), &m_dstNoData)
112
0
        .SetMinCount(1)
113
0
        .SetRepeatedArgAllowed(false);
114
0
    AddArg("hide-nodata", 0,
115
0
           _("Makes the destination band not report the NoData."),
116
0
           &m_hideNoData);
117
118
0
    AddValidationAction(
119
0
        [this, &resArg, &tapArg]()
120
0
        {
121
0
            if (tapArg.IsExplicitlySet() && !resArg.IsExplicitlySet())
122
0
            {
123
0
                ReportError(
124
0
                    CE_Failure, CPLE_IllegalArg,
125
0
                    "Argument 'target-aligned-pixels' can only be specified if "
126
0
                    "argument 'resolution' is also specified.");
127
0
                return false;
128
0
            }
129
0
            return true;
130
0
        });
131
0
}
132
133
/************************************************************************/
134
/*     GDALRasterMosaicStackCommonAlgorithm::GetInputDatasetNames()     */
135
/************************************************************************/
136
137
bool GDALRasterMosaicStackCommonAlgorithm::GetInputDatasetNames(
138
    GDALPipelineStepRunContext &ctxt,
139
    std::vector<GDALDatasetH> &ahInputDatasets,
140
    CPLStringList &aosInputDatasetNames, bool &foundByName)
141
0
{
142
0
    bool foundByRef = false;
143
0
    for (auto &ds : m_inputDataset)
144
0
    {
145
0
        if (ds.GetDatasetRef())
146
0
        {
147
0
            foundByRef = true;
148
0
            ahInputDatasets.push_back(
149
0
                GDALDataset::ToHandle(ds.GetDatasetRef()));
150
0
        }
151
0
        else if (!ds.GetName().empty())
152
0
        {
153
0
            foundByName = true;
154
0
            if (ds.GetName()[0] == '@')
155
0
            {
156
0
                auto f = VSIVirtualHandleUniquePtr(
157
0
                    VSIFOpenL(ds.GetName().c_str() + 1, "r"));
158
0
                if (!f)
159
0
                {
160
0
                    ReportError(CE_Failure, CPLE_FileIO, "Cannot open %s",
161
0
                                ds.GetName().c_str() + 1);
162
0
                    return false;
163
0
                }
164
0
                while (const char *filename = CPLReadLineL(f.get()))
165
0
                {
166
0
                    aosInputDatasetNames.push_back(filename);
167
0
                }
168
0
            }
169
0
            else if (ds.GetName().find_first_of("*?[") != std::string::npos)
170
0
            {
171
0
                CPLStringList aosMatches(VSIGlob(ds.GetName().c_str(), nullptr,
172
0
                                                 ctxt.m_pfnProgress,
173
0
                                                 ctxt.m_pProgressData));
174
0
                for (const char *pszStr : aosMatches)
175
0
                {
176
0
                    aosInputDatasetNames.push_back(pszStr);
177
0
                }
178
0
            }
179
0
            else
180
0
            {
181
0
                std::string osDatasetName = ds.GetName();
182
0
                if (!GetReferencePathForRelativePaths().empty())
183
0
                {
184
0
                    osDatasetName = GDALDataset::BuildFilename(
185
0
                        osDatasetName.c_str(),
186
0
                        GetReferencePathForRelativePaths().c_str(), true);
187
0
                }
188
0
                aosInputDatasetNames.push_back(osDatasetName.c_str());
189
0
            }
190
0
        }
191
0
    }
192
0
    if (foundByName && foundByRef)
193
0
    {
194
0
        ReportError(CE_Failure, CPLE_NotSupported,
195
0
                    "Input datasets should be provided either all by reference "
196
0
                    "or all by name");
197
0
        return false;
198
0
    }
199
200
0
    return true;
201
0
}
202
203
/************************************************************************/
204
/*      GDALRasterMosaicStackCommonAlgorithm::SetBuildVRTOptions()      */
205
/************************************************************************/
206
207
void GDALRasterMosaicStackCommonAlgorithm::SetBuildVRTOptions(
208
    CPLStringList &aosOptions)
209
0
{
210
0
    const auto aosTokens =
211
0
        CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0));
212
0
    if (aosTokens.size() == 2)
213
0
    {
214
0
        aosOptions.push_back("-tr");
215
0
        aosOptions.push_back(aosTokens[0]);
216
0
        aosOptions.push_back(aosTokens[1]);
217
0
    }
218
0
    else
219
0
    {
220
0
        aosOptions.push_back("-resolution");
221
0
        aosOptions.push_back(m_resolution);
222
0
    }
223
224
0
    if (!m_bbox.empty())
225
0
    {
226
0
        aosOptions.push_back("-te");
227
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[0]));
228
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[1]));
229
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[2]));
230
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[3]));
231
0
    }
232
0
    if (m_targetAlignedPixels)
233
0
    {
234
0
        aosOptions.push_back("-tap");
235
0
    }
236
0
    if (!m_srcNoData.empty())
237
0
    {
238
0
        aosOptions.push_back("-srcnodata");
239
0
        std::string s;
240
0
        for (double v : m_srcNoData)
241
0
        {
242
0
            if (!s.empty())
243
0
                s += " ";
244
0
            s += CPLSPrintf("%.17g", v);
245
0
        }
246
0
        aosOptions.push_back(s);
247
0
    }
248
0
    if (!m_dstNoData.empty())
249
0
    {
250
0
        aosOptions.push_back("-vrtnodata");
251
0
        std::string s;
252
0
        for (double v : m_dstNoData)
253
0
        {
254
0
            if (!s.empty())
255
0
                s += " ";
256
0
            s += CPLSPrintf("%.17g", v);
257
0
        }
258
0
        aosOptions.push_back(s);
259
0
    }
260
0
    for (const int b : m_bands)
261
0
    {
262
0
        aosOptions.push_back("-b");
263
0
        aosOptions.push_back(CPLSPrintf("%d", b));
264
0
    }
265
0
    if (m_hideNoData)
266
0
    {
267
0
        aosOptions.push_back("-hidenodata");
268
0
    }
269
0
    if (m_writeAbsolutePaths)
270
0
    {
271
0
        aosOptions.push_back("-write_absolute_path");
272
0
    }
273
0
}
274
275
/************************************************************************/
276
/*           GDALRasterMosaicStackCommonAlgorithm::RunImpl()            */
277
/************************************************************************/
278
279
bool GDALRasterMosaicStackCommonAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
280
                                                   void *pProgressData)
281
0
{
282
0
    if (m_standaloneStep)
283
0
    {
284
0
        GDALRasterWriteAlgorithm writeAlg;
285
0
        for (auto &arg : writeAlg.GetArgs())
286
0
        {
287
0
            if (!arg->IsHidden())
288
0
            {
289
0
                auto stepArg = GetArg(arg->GetName());
290
0
                if (stepArg && stepArg->IsExplicitlySet())
291
0
                {
292
0
                    arg->SetSkipIfAlreadySet(true);
293
0
                    arg->SetFrom(*stepArg);
294
0
                }
295
0
            }
296
0
        }
297
298
        // Already checked by GDALAlgorithm::Run()
299
0
        CPLAssert(!m_executionForStreamOutput ||
300
0
                  EQUAL(m_format.c_str(), "stream"));
301
302
0
        m_standaloneStep = false;
303
0
        bool ret = Run(pfnProgress, pProgressData);
304
0
        m_standaloneStep = true;
305
0
        if (ret)
306
0
        {
307
0
            if (m_format == "stream")
308
0
            {
309
0
                ret = true;
310
0
            }
311
0
            else
312
0
            {
313
0
                std::vector<GDALArgDatasetValue> inputDataset(1);
314
0
                inputDataset[0].Set(m_outputDataset.GetDatasetRef());
315
0
                auto inputArg = writeAlg.GetArg(GDAL_ARG_NAME_INPUT);
316
0
                CPLAssert(inputArg);
317
0
                inputArg->Set(std::move(inputDataset));
318
0
                if (writeAlg.Run(pfnProgress, pProgressData))
319
0
                {
320
0
                    m_outputDataset.Set(
321
0
                        writeAlg.m_outputDataset.GetDatasetRef());
322
0
                    ret = true;
323
0
                }
324
0
            }
325
0
        }
326
327
0
        return ret;
328
0
    }
329
0
    else
330
0
    {
331
0
        GDALPipelineStepRunContext stepCtxt;
332
0
        stepCtxt.m_pfnProgress = pfnProgress;
333
0
        stepCtxt.m_pProgressData = pProgressData;
334
0
        return RunStep(stepCtxt);
335
0
    }
336
0
}
337
338
//! @endcond