Coverage Report

Created: 2026-07-14 06:21

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("input-nodata", 0, _("Set nodata values for input bands."),
107
0
           &m_srcNoData)
108
0
        .SetMinCount(1)
109
0
        .AddHiddenAlias("src-nodata")
110
0
        .SetRepeatedArgAllowed(false);
111
0
    AddArg("output-nodata", 0,
112
0
           _("Set nodata values at the destination band level."), &m_dstNoData)
113
0
        .SetMinCount(1)
114
0
        .AddHiddenAlias("dst-nodata")
115
0
        .SetRepeatedArgAllowed(false);
116
0
    AddArg("hide-nodata", 0,
117
0
           _("Makes the destination band not report the NoData."),
118
0
           &m_hideNoData);
119
120
0
    AddValidationAction(
121
0
        [this, &resArg, &tapArg]()
122
0
        {
123
0
            if (tapArg.IsExplicitlySet() && !resArg.IsExplicitlySet())
124
0
            {
125
0
                ReportError(
126
0
                    CE_Failure, CPLE_IllegalArg,
127
0
                    "Argument 'target-aligned-pixels' can only be specified if "
128
0
                    "argument 'resolution' is also specified.");
129
0
                return false;
130
0
            }
131
0
            return true;
132
0
        });
133
0
}
134
135
/************************************************************************/
136
/*     GDALRasterMosaicStackCommonAlgorithm::GetInputDatasetNames()     */
137
/************************************************************************/
138
139
bool GDALRasterMosaicStackCommonAlgorithm::GetInputDatasetNames(
140
    GDALPipelineStepRunContext &ctxt,
141
    std::vector<GDALDatasetH> &ahInputDatasets,
142
    CPLStringList &aosInputDatasetNames, bool &foundByName)
143
0
{
144
0
    bool foundByRef = false;
145
0
    for (auto &ds : m_inputDataset)
146
0
    {
147
0
        if (ds.GetDatasetRef())
148
0
        {
149
0
            foundByRef = true;
150
0
            ahInputDatasets.push_back(
151
0
                GDALDataset::ToHandle(ds.GetDatasetRef()));
152
0
        }
153
0
        else if (!ds.GetName().empty())
154
0
        {
155
0
            foundByName = true;
156
0
            if (ds.GetName()[0] == '@')
157
0
            {
158
0
                auto f = VSIVirtualHandleUniquePtr(
159
0
                    VSIFOpenL(ds.GetName().c_str() + 1, "r"));
160
0
                if (!f)
161
0
                {
162
0
                    ReportError(CE_Failure, CPLE_FileIO, "Cannot open %s",
163
0
                                ds.GetName().c_str() + 1);
164
0
                    return false;
165
0
                }
166
0
                while (const char *filename = CPLReadLineL(f.get()))
167
0
                {
168
0
                    aosInputDatasetNames.push_back(filename);
169
0
                }
170
0
            }
171
0
            else if (ds.GetName().find_first_of("*?[") != std::string::npos)
172
0
            {
173
0
                CPLStringList aosMatches(VSIGlob(ds.GetName().c_str(), nullptr,
174
0
                                                 ctxt.m_pfnProgress,
175
0
                                                 ctxt.m_pProgressData));
176
0
                for (const char *pszStr : aosMatches)
177
0
                {
178
0
                    aosInputDatasetNames.push_back(pszStr);
179
0
                }
180
0
            }
181
0
            else
182
0
            {
183
0
                std::string osDatasetName = ds.GetName();
184
0
                if (!GetReferencePathForRelativePaths().empty())
185
0
                {
186
0
                    osDatasetName = GDALDataset::BuildFilename(
187
0
                        osDatasetName.c_str(),
188
0
                        GetReferencePathForRelativePaths().c_str(), true);
189
0
                }
190
0
                aosInputDatasetNames.push_back(osDatasetName.c_str());
191
0
            }
192
0
        }
193
0
    }
194
0
    if (foundByName && foundByRef)
195
0
    {
196
0
        ReportError(CE_Failure, CPLE_NotSupported,
197
0
                    "Input datasets should be provided either all by reference "
198
0
                    "or all by name");
199
0
        return false;
200
0
    }
201
202
0
    return true;
203
0
}
204
205
/************************************************************************/
206
/*      GDALRasterMosaicStackCommonAlgorithm::SetBuildVRTOptions()      */
207
/************************************************************************/
208
209
void GDALRasterMosaicStackCommonAlgorithm::SetBuildVRTOptions(
210
    CPLStringList &aosOptions)
211
0
{
212
0
    const auto aosTokens =
213
0
        CPLStringList(CSLTokenizeString2(m_resolution.c_str(), ",", 0));
214
0
    if (aosTokens.size() == 2)
215
0
    {
216
0
        aosOptions.push_back("-tr");
217
0
        aosOptions.push_back(aosTokens[0]);
218
0
        aosOptions.push_back(aosTokens[1]);
219
0
    }
220
0
    else
221
0
    {
222
0
        aosOptions.push_back("-resolution");
223
0
        aosOptions.push_back(m_resolution);
224
0
    }
225
226
0
    if (!m_bbox.empty())
227
0
    {
228
0
        aosOptions.push_back("-te");
229
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[0]));
230
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[1]));
231
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[2]));
232
0
        aosOptions.push_back(CPLSPrintf("%.17g", m_bbox[3]));
233
0
    }
234
0
    if (m_targetAlignedPixels)
235
0
    {
236
0
        aosOptions.push_back("-tap");
237
0
    }
238
0
    if (!m_srcNoData.empty())
239
0
    {
240
0
        aosOptions.push_back("-srcnodata");
241
0
        std::string s;
242
0
        for (double v : m_srcNoData)
243
0
        {
244
0
            if (!s.empty())
245
0
                s += " ";
246
0
            s += CPLSPrintf("%.17g", v);
247
0
        }
248
0
        aosOptions.push_back(s);
249
0
    }
250
0
    if (!m_dstNoData.empty())
251
0
    {
252
0
        aosOptions.push_back("-vrtnodata");
253
0
        std::string s;
254
0
        for (double v : m_dstNoData)
255
0
        {
256
0
            if (!s.empty())
257
0
                s += " ";
258
0
            s += CPLSPrintf("%.17g", v);
259
0
        }
260
0
        aosOptions.push_back(s);
261
0
    }
262
0
    for (const int b : m_bands)
263
0
    {
264
0
        aosOptions.push_back("-b");
265
0
        aosOptions.push_back(CPLSPrintf("%d", b));
266
0
    }
267
0
    if (m_hideNoData)
268
0
    {
269
0
        aosOptions.push_back("-hidenodata");
270
0
    }
271
0
    if (m_writeAbsolutePaths)
272
0
    {
273
0
        aosOptions.push_back("-write_absolute_path");
274
0
    }
275
0
}
276
277
/************************************************************************/
278
/*           GDALRasterMosaicStackCommonAlgorithm::RunImpl()            */
279
/************************************************************************/
280
281
bool GDALRasterMosaicStackCommonAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
282
                                                   void *pProgressData)
283
0
{
284
0
    if (m_standaloneStep)
285
0
    {
286
0
        GDALRasterWriteAlgorithm writeAlg;
287
0
        for (auto &arg : writeAlg.GetArgs())
288
0
        {
289
0
            if (!arg->IsHidden())
290
0
            {
291
0
                auto stepArg = GetArg(arg->GetName());
292
0
                if (stepArg && stepArg->IsExplicitlySet())
293
0
                {
294
0
                    arg->SetSkipIfAlreadySet(true);
295
0
                    arg->SetFrom(*stepArg);
296
0
                }
297
0
            }
298
0
        }
299
300
        // Already checked by GDALAlgorithm::Run()
301
0
        CPLAssert(!m_executionForStreamOutput ||
302
0
                  EQUAL(m_format.c_str(), "stream"));
303
304
0
        m_standaloneStep = false;
305
0
        m_alreadyRun = false;
306
0
        bool ret = Run(pfnProgress, pProgressData);
307
0
        m_standaloneStep = true;
308
0
        if (ret)
309
0
        {
310
0
            if (m_format == "stream")
311
0
            {
312
0
                ret = true;
313
0
            }
314
0
            else
315
0
            {
316
0
                std::vector<GDALArgDatasetValue> inputDataset(1);
317
0
                inputDataset[0].Set(m_outputDataset.GetDatasetRef());
318
0
                auto inputArg = writeAlg.GetArg(GDAL_ARG_NAME_INPUT);
319
0
                CPLAssert(inputArg);
320
0
                inputArg->Set(std::move(inputDataset));
321
0
                if (writeAlg.Run(pfnProgress, pProgressData))
322
0
                {
323
0
                    m_outputDataset.Set(
324
0
                        writeAlg.m_outputDataset.GetDatasetRef());
325
0
                    ret = true;
326
0
                }
327
0
            }
328
0
        }
329
330
0
        return ret;
331
0
    }
332
0
    else
333
0
    {
334
0
        GDALPipelineStepRunContext stepCtxt;
335
0
        stepCtxt.m_pfnProgress = pfnProgress;
336
0
        stepCtxt.m_pProgressData = pProgressData;
337
0
        return RunStep(stepCtxt);
338
0
    }
339
0
}
340
341
//! @endcond