Coverage Report

Created: 2026-04-01 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_abstract_pipeline.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster/vector pipeline" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2024-2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_conv.h"
14
#include "cpl_error_internal.h"
15
#include "cpl_json.h"
16
17
#include "gdalalg_abstract_pipeline.h"
18
#include "gdalalg_raster_read.h"
19
#include "gdalalg_raster_write.h"
20
#include "gdalalg_vector_read.h"
21
#include "gdalalg_tee.h"
22
23
#include <algorithm>
24
#include <cassert>
25
26
//! @cond Doxygen_Suppress
27
28
/* clang-format off */
29
constexpr const char *const apszReadParametersPrefixOmitted[] = {
30
    GDAL_ARG_NAME_INPUT,
31
    GDAL_ARG_NAME_INPUT_FORMAT,
32
    GDAL_ARG_NAME_OPEN_OPTION,
33
    GDAL_ARG_NAME_INPUT_LAYER};
34
35
constexpr const char *const apszWriteParametersPrefixOmitted[] = {
36
    GDAL_ARG_NAME_OUTPUT,
37
    GDAL_ARG_NAME_OUTPUT_FORMAT,
38
    GDAL_ARG_NAME_CREATION_OPTION,
39
    GDAL_ARG_NAME_OUTPUT_LAYER,
40
    GDAL_ARG_NAME_LAYER_CREATION_OPTION,
41
    GDAL_ARG_NAME_UPDATE,
42
    GDAL_ARG_NAME_OVERWRITE,
43
    GDAL_ARG_NAME_APPEND,
44
    GDAL_ARG_NAME_OVERWRITE_LAYER};
45
46
/* clang-format on */
47
48
/************************************************************************/
49
/*                       IsReadSpecificArgument()                       */
50
/************************************************************************/
51
52
/* static */
53
bool GDALAbstractPipelineAlgorithm::IsReadSpecificArgument(
54
    const char *pszArgName)
55
0
{
56
0
    return std::find_if(std::begin(apszReadParametersPrefixOmitted),
57
0
                        std::end(apszReadParametersPrefixOmitted),
58
0
                        [pszArgName](const char *pszStr)
59
0
                        { return strcmp(pszStr, pszArgName) == 0; }) !=
60
0
           std::end(apszReadParametersPrefixOmitted);
61
0
}
62
63
/************************************************************************/
64
/*                      IsWriteSpecificArgument()                       */
65
/************************************************************************/
66
67
/* static */
68
bool GDALAbstractPipelineAlgorithm::IsWriteSpecificArgument(
69
    const char *pszArgName)
70
0
{
71
0
    return std::find_if(std::begin(apszWriteParametersPrefixOmitted),
72
0
                        std::end(apszWriteParametersPrefixOmitted),
73
0
                        [pszArgName](const char *pszStr)
74
0
                        { return strcmp(pszStr, pszArgName) == 0; }) !=
75
0
           std::end(apszWriteParametersPrefixOmitted);
76
0
}
77
78
/************************************************************************/
79
/*        GDALAbstractPipelineAlgorithm::CheckFirstAndLastStep()        */
80
/************************************************************************/
81
82
bool GDALAbstractPipelineAlgorithm::CheckFirstAndLastStep(
83
    const std::vector<GDALPipelineStepAlgorithm *> &steps,
84
    bool forAutoComplete) const
85
0
{
86
0
    if (m_bExpectReadStep && !steps.front()->CanBeFirstStep())
87
0
    {
88
0
        std::set<CPLString> setFirstStepNames;
89
0
        for (const auto &stepName : GetStepRegistry().GetNames())
90
0
        {
91
0
            auto alg = GetStepAlg(stepName);
92
0
            if (alg && alg->CanBeFirstStep() && stepName != "read")
93
0
            {
94
0
                setFirstStepNames.insert(CPLString(stepName)
95
0
                                             .replaceAll(RASTER_SUFFIX, "")
96
0
                                             .replaceAll(VECTOR_SUFFIX, ""));
97
0
            }
98
0
        }
99
0
        std::vector<std::string> firstStepNames{"read"};
100
0
        for (const std::string &s : setFirstStepNames)
101
0
            firstStepNames.push_back(s);
102
103
0
        std::string msg = "First step should be ";
104
0
        for (size_t i = 0; i < firstStepNames.size(); ++i)
105
0
        {
106
0
            if (i == firstStepNames.size() - 1)
107
0
                msg += " or ";
108
0
            else if (i > 0)
109
0
                msg += ", ";
110
0
            msg += '\'';
111
0
            msg += firstStepNames[i];
112
0
            msg += '\'';
113
0
        }
114
115
0
        ReportError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
116
0
        return false;
117
0
    }
118
119
0
    if (!m_bExpectReadStep)
120
0
    {
121
0
        if (steps.front()->CanBeFirstStep())
122
0
        {
123
0
            ReportError(CE_Failure, CPLE_AppDefined,
124
0
                        "No read-like step like '%s' is allowed",
125
0
                        steps.front()->GetName().c_str());
126
0
            return false;
127
0
        }
128
0
    }
129
130
0
    if (forAutoComplete)
131
0
        return true;
132
133
0
    if (m_eLastStepAsWrite == StepConstraint::CAN_NOT_BE)
134
0
    {
135
0
        if (steps.back()->CanBeLastStep() && !steps.back()->CanBeMiddleStep())
136
0
        {
137
0
            ReportError(CE_Failure, CPLE_AppDefined,
138
0
                        "No write-like step like '%s' is allowed",
139
0
                        steps.back()->GetName().c_str());
140
0
            return false;
141
0
        }
142
0
    }
143
144
0
    for (size_t i = 1; i < steps.size() - 1; ++i)
145
0
    {
146
0
        if (!steps[i]->CanBeMiddleStep())
147
0
        {
148
0
            if (steps[i]->CanBeFirstStep() && m_bExpectReadStep)
149
0
            {
150
0
                ReportError(CE_Failure, CPLE_AppDefined,
151
0
                            "Only first step can be '%s'",
152
0
                            steps[i]->GetName().c_str());
153
0
            }
154
0
            else if (steps[i]->CanBeLastStep() &&
155
0
                     m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE)
156
0
            {
157
0
                ReportError(CE_Failure, CPLE_AppDefined,
158
0
                            "Only last step can be '%s'",
159
0
                            steps[i]->GetName().c_str());
160
0
            }
161
0
            else
162
0
            {
163
0
                ReportError(CE_Failure, CPLE_AppDefined,
164
0
                            "'%s' is not allowed as an intermediate step",
165
0
                            steps[i]->GetName().c_str());
166
0
                return false;
167
0
            }
168
0
        }
169
0
    }
170
171
0
    if (steps.size() >= 2 && steps.back()->CanBeFirstStep() &&
172
0
        !steps.back()->CanBeLastStep())
173
0
    {
174
0
        ReportError(CE_Failure, CPLE_AppDefined,
175
0
                    "'%s' is only allowed as a first step",
176
0
                    steps.back()->GetName().c_str());
177
0
        return false;
178
0
    }
179
180
0
    if (m_eLastStepAsWrite == StepConstraint::MUST_BE &&
181
0
        !steps.back()->CanBeLastStep())
182
0
    {
183
0
        std::set<CPLString> setLastStepNames;
184
0
        for (const auto &stepName : GetStepRegistry().GetNames())
185
0
        {
186
0
            auto alg = GetStepAlg(stepName);
187
0
            if (alg && alg->CanBeLastStep() && stepName != "write")
188
0
            {
189
0
                setLastStepNames.insert(CPLString(stepName)
190
0
                                            .replaceAll(RASTER_SUFFIX, "")
191
0
                                            .replaceAll(VECTOR_SUFFIX, ""));
192
0
            }
193
0
        }
194
0
        std::vector<std::string> lastStepNames{"write"};
195
0
        for (const std::string &s : setLastStepNames)
196
0
            lastStepNames.push_back(s);
197
198
0
        std::string msg = "Last step should be ";
199
0
        for (size_t i = 0; i < lastStepNames.size(); ++i)
200
0
        {
201
0
            if (i == lastStepNames.size() - 1)
202
0
                msg += " or ";
203
0
            else if (i > 0)
204
0
                msg += ", ";
205
0
            msg += '\'';
206
0
            msg += lastStepNames[i];
207
0
            msg += '\'';
208
0
        }
209
210
0
        ReportError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
211
0
        return false;
212
0
    }
213
214
0
    return true;
215
0
}
216
217
/************************************************************************/
218
/*             GDALAbstractPipelineAlgorithm::GetStepAlg()              */
219
/************************************************************************/
220
221
std::unique_ptr<GDALPipelineStepAlgorithm>
222
GDALAbstractPipelineAlgorithm::GetStepAlg(const std::string &name) const
223
0
{
224
0
    auto alg = GetStepRegistry().Instantiate(name);
225
0
    return std::unique_ptr<GDALPipelineStepAlgorithm>(
226
0
        cpl::down_cast<GDALPipelineStepAlgorithm *>(alg.release()));
227
0
}
228
229
/************************************************************************/
230
/*      GDALAbstractPipelineAlgorithm::ParseCommandLineArguments()      */
231
/************************************************************************/
232
233
bool GDALAbstractPipelineAlgorithm::ParseCommandLineArguments(
234
    const std::vector<std::string> &argsIn)
235
0
{
236
0
    return ParseCommandLineArguments(argsIn, /*forAutoComplete=*/false);
237
0
}
238
239
bool GDALAbstractPipelineAlgorithm::ParseCommandLineArguments(
240
    const std::vector<std::string> &argsIn, bool forAutoComplete)
241
0
{
242
0
    std::vector<std::string> args = argsIn;
243
244
0
    if (IsCalledFromCommandLine())
245
0
    {
246
0
        m_eLastStepAsWrite = StepConstraint::MUST_BE;
247
0
    }
248
249
0
    if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
250
0
                             args[0] == "help" || args[0] == "--json-usage"))
251
0
    {
252
0
        return GDALAlgorithm::ParseCommandLineArguments(args);
253
0
    }
254
0
    else if (args.size() == 1 && STARTS_WITH(args[0].c_str(), "--help-doc="))
255
0
    {
256
0
        m_helpDocCategory = args[0].substr(strlen("--help-doc="));
257
0
        return GDALAlgorithm::ParseCommandLineArguments({"--help-doc"});
258
0
    }
259
260
0
    bool foundStepMarker = false;
261
262
0
    for (size_t i = 0; i < args.size(); ++i)
263
0
    {
264
0
        const auto &arg = args[i];
265
0
        if (arg == "--pipeline")
266
0
        {
267
0
            if (i + 1 < args.size() &&
268
0
                CPLString(args[i + 1]).ifind(".json") != std::string::npos)
269
0
                break;
270
0
            return GDALAlgorithm::ParseCommandLineArguments(args);
271
0
        }
272
273
0
        else if (cpl::starts_with(arg, "--pipeline="))
274
0
        {
275
0
            if (CPLString(arg).ifind(".json") != std::string::npos)
276
0
                break;
277
0
            return GDALAlgorithm::ParseCommandLineArguments(args);
278
0
        }
279
280
        // gdal pipeline [--quiet] "read poly.gpkg ..."
281
0
        if (arg.find("read ") == 0)
282
0
            return GDALAlgorithm::ParseCommandLineArguments(args);
283
284
0
        if (arg == "!")
285
0
            foundStepMarker = true;
286
0
    }
287
288
0
    bool runExistingPipeline = false;
289
0
    if (!foundStepMarker && !m_executionForStreamOutput)
290
0
    {
291
0
        std::string osCommandLine;
292
0
        for (const auto &arg : args)
293
0
        {
294
0
            if (((!arg.empty() && arg[0] != '-') ||
295
0
                 cpl::starts_with(arg, "--pipeline=")) &&
296
0
                CPLString(arg).ifind(".json") != std::string::npos)
297
0
            {
298
0
                bool ret;
299
0
                if (m_pipeline == arg)
300
0
                    ret = true;
301
0
                else
302
0
                {
303
0
                    const std::string filename =
304
0
                        cpl::starts_with(arg, "--pipeline=")
305
0
                            ? arg.substr(strlen("--pipeline="))
306
0
                            : arg;
307
0
                    if (forAutoComplete)
308
0
                    {
309
0
                        SetParseForAutoCompletion();
310
0
                    }
311
0
                    ret = GDALAlgorithm::ParseCommandLineArguments(args) ||
312
0
                          forAutoComplete;
313
0
                    if (ret)
314
0
                    {
315
0
                        ret = m_pipeline == filename;
316
0
                    }
317
0
                }
318
0
                if (ret)
319
0
                {
320
0
                    CPLJSONDocument oDoc;
321
0
                    ret = oDoc.Load(m_pipeline);
322
0
                    if (ret)
323
0
                    {
324
0
                        osCommandLine =
325
0
                            oDoc.GetRoot().GetString("command_line");
326
0
                        if (osCommandLine.empty())
327
0
                        {
328
0
                            ReportError(CE_Failure, CPLE_AppDefined,
329
0
                                        "command_line missing in %s",
330
0
                                        m_pipeline.c_str());
331
0
                            return false;
332
0
                        }
333
334
0
                        for (const char *prefix :
335
0
                             {"gdal pipeline ", "gdal raster pipeline ",
336
0
                              "gdal vector pipeline "})
337
0
                        {
338
0
                            if (cpl::starts_with(osCommandLine, prefix))
339
0
                                osCommandLine =
340
0
                                    osCommandLine.substr(strlen(prefix));
341
0
                        }
342
343
0
                        if (oDoc.GetRoot().GetBool(
344
0
                                "relative_paths_relative_to_this_file", true))
345
0
                        {
346
0
                            SetReferencePathForRelativePaths(
347
0
                                CPLGetPathSafe(m_pipeline.c_str()).c_str());
348
0
                        }
349
350
0
                        runExistingPipeline = true;
351
0
                    }
352
0
                }
353
0
                if (ret)
354
0
                    break;
355
0
                else
356
0
                    return false;
357
0
            }
358
0
        }
359
0
        if (runExistingPipeline)
360
0
        {
361
0
            const CPLStringList aosArgs(
362
0
                CSLTokenizeString(osCommandLine.c_str()));
363
364
0
            args = aosArgs;
365
0
        }
366
0
    }
367
368
0
    if (!m_steps.empty())
369
0
    {
370
0
        ReportError(CE_Failure, CPLE_AppDefined,
371
0
                    "ParseCommandLineArguments() can only be called once per "
372
0
                    "instance.");
373
0
        return false;
374
0
    }
375
376
0
    const bool bIsGenericPipeline =
377
0
        (GetInputType() == (GDAL_OF_RASTER | GDAL_OF_VECTOR));
378
379
0
    struct Step
380
0
    {
381
0
        std::unique_ptr<GDALPipelineStepAlgorithm> alg{};
382
0
        std::vector<std::string> args{};
383
0
        bool alreadyChangedType = false;
384
0
        bool isSubAlgorithm = false;
385
0
    };
386
387
0
    int nDatasetType = GetInputType();
388
0
    const auto SetCurStepAlg =
389
0
        [this, bIsGenericPipeline, &nDatasetType](
390
0
            Step &curStep, const std::string &algName, bool firstStep)
391
0
    {
392
0
        if (bIsGenericPipeline)
393
0
        {
394
0
            if (algName == "read")
395
0
            {
396
0
                curStep.alg = std::make_unique<GDALRasterReadAlgorithm>(true);
397
0
            }
398
0
            else
399
0
            {
400
0
                if (nDatasetType == GDAL_OF_RASTER)
401
0
                    curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
402
0
                else if (nDatasetType == GDAL_OF_VECTOR)
403
0
                    curStep.alg = GetStepAlg(algName + VECTOR_SUFFIX);
404
0
                if (!curStep.alg)
405
0
                    curStep.alg = GetStepAlg(algName);
406
0
                if (!curStep.alg)
407
0
                    curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
408
0
                if (curStep.alg)
409
0
                    nDatasetType = curStep.alg->GetOutputType();
410
0
            }
411
0
        }
412
0
        else
413
0
        {
414
0
            curStep.alg = GetStepAlg(algName);
415
0
        }
416
0
        if (!curStep.alg)
417
0
        {
418
0
            ReportError(CE_Failure, CPLE_AppDefined, "unknown step name: %s",
419
0
                        algName.c_str());
420
0
            return false;
421
0
        }
422
        // We don't want to accept '_PIPE_' dataset placeholder for the first
423
        // step of a pipeline.
424
0
        curStep.alg->m_inputDatasetCanBeOmitted =
425
0
            !firstStep || !m_bExpectReadStep;
426
0
        curStep.alg->SetCallPath({algName});
427
0
        curStep.alg->SetReferencePathForRelativePaths(
428
0
            GetReferencePathForRelativePaths());
429
0
        return true;
430
0
    };
431
432
0
    std::vector<Step> steps;
433
0
    steps.resize(1);
434
435
0
    int nNestLevel = 0;
436
0
    std::vector<std::string> nestedPipelineArgs;
437
438
0
    for (const auto &argIn : args)
439
0
    {
440
0
        std::string arg(argIn);
441
442
        // If outputting to stdout, automatically turn off progress bar
443
0
        if (arg == "/vsistdout/")
444
0
        {
445
0
            auto quietArg = GetArg(GDAL_ARG_NAME_QUIET);
446
0
            if (quietArg && quietArg->GetType() == GAAT_BOOLEAN)
447
0
                quietArg->Set(true);
448
0
        }
449
450
0
        auto &curStep = steps.back();
451
452
0
        if (nNestLevel > 0)
453
0
        {
454
0
            if (arg == CLOSE_NESTED_PIPELINE)
455
0
            {
456
0
                if ((--nNestLevel) == 0)
457
0
                {
458
0
                    arg = BuildNestedPipeline(
459
0
                        curStep.alg.get(), nestedPipelineArgs, forAutoComplete);
460
0
                    if (arg.empty())
461
0
                    {
462
0
                        return false;
463
0
                    }
464
0
                }
465
0
                else
466
0
                {
467
0
                    nestedPipelineArgs.push_back(std::move(arg));
468
0
                    continue;
469
0
                }
470
0
            }
471
0
            else
472
0
            {
473
0
                if (arg == OPEN_NESTED_PIPELINE)
474
0
                {
475
0
                    if (++nNestLevel == MAX_NESTING_LEVEL)
476
0
                    {
477
0
                        ReportError(CE_Failure, CPLE_AppDefined,
478
0
                                    "Too many nested pipelines");
479
0
                        return false;
480
0
                    }
481
0
                }
482
0
                nestedPipelineArgs.push_back(std::move(arg));
483
0
                continue;
484
0
            }
485
0
        }
486
487
0
        if (arg == "--progress")
488
0
        {
489
0
            m_progressBarRequested = true;
490
0
            continue;
491
0
        }
492
0
        if (arg == "-q" || arg == "--quiet")
493
0
        {
494
0
            m_quiet = true;
495
0
            m_progressBarRequested = false;
496
0
            continue;
497
0
        }
498
499
0
        if (IsCalledFromCommandLine() && (arg == "-h" || arg == "--help"))
500
0
        {
501
0
            if (!steps.back().alg)
502
0
                steps.pop_back();
503
0
            if (steps.empty())
504
0
            {
505
0
                return GDALAlgorithm::ParseCommandLineArguments(args);
506
0
            }
507
0
            else
508
0
            {
509
0
                m_stepOnWhichHelpIsRequested = std::move(steps.back().alg);
510
0
                return true;
511
0
            }
512
0
        }
513
514
0
        if (arg == "!" || arg == "|")
515
0
        {
516
0
            if (curStep.alg)
517
0
            {
518
0
                steps.resize(steps.size() + 1);
519
0
            }
520
0
        }
521
0
        else if (arg == OPEN_NESTED_PIPELINE)
522
0
        {
523
0
            if (!curStep.alg)
524
0
            {
525
0
                ReportError(CE_Failure, CPLE_AppDefined,
526
0
                            "Open bracket must be placed where an input "
527
0
                            "dataset is expected");
528
0
                return false;
529
0
            }
530
0
            ++nNestLevel;
531
0
        }
532
0
        else if (arg == CLOSE_NESTED_PIPELINE)
533
0
        {
534
0
            ReportError(CE_Failure, CPLE_AppDefined,
535
0
                        "Closing bracket found without matching open bracket");
536
0
            return false;
537
0
        }
538
0
#ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
539
0
        else if (arg == "+step")
540
0
        {
541
0
            if (curStep.alg)
542
0
            {
543
0
                steps.resize(steps.size() + 1);
544
0
            }
545
0
        }
546
0
        else if (arg.find("+gdal=") == 0)
547
0
        {
548
0
            const std::string algName = arg.substr(strlen("+gdal="));
549
0
            if (!SetCurStepAlg(curStep, algName, steps.size() == 1))
550
0
                return false;
551
0
        }
552
0
#endif
553
0
        else if (!curStep.alg)
554
0
        {
555
0
            std::string algName = std::move(arg);
556
0
#ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
557
0
            if (!algName.empty() && algName[0] == '+')
558
0
                algName = algName.substr(1);
559
0
#endif
560
0
            if (!SetCurStepAlg(curStep, algName, steps.size() == 1))
561
0
                return false;
562
0
        }
563
0
        else
564
0
        {
565
0
            if (curStep.alg->HasSubAlgorithms())
566
0
            {
567
0
                auto subAlg = std::unique_ptr<GDALPipelineStepAlgorithm>(
568
0
                    cpl::down_cast<GDALPipelineStepAlgorithm *>(
569
0
                        curStep.alg->InstantiateSubAlgorithm(arg).release()));
570
0
                if (!subAlg)
571
0
                {
572
0
                    ReportError(CE_Failure, CPLE_AppDefined,
573
0
                                "'%s' is a unknown sub-algorithm of '%s'",
574
0
                                arg.c_str(), curStep.alg->GetName().c_str());
575
0
                    return false;
576
0
                }
577
0
                curStep.isSubAlgorithm = true;
578
0
                subAlg->m_inputDatasetCanBeOmitted =
579
0
                    steps.size() > 1 || !m_bExpectReadStep;
580
0
                curStep.alg = std::move(subAlg);
581
0
                continue;
582
0
            }
583
584
0
#ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
585
0
            if (!arg.empty() && arg[0] == '+' &&
586
0
                arg.find(' ') == std::string::npos)
587
0
            {
588
0
                curStep.args.push_back("--" + arg.substr(1));
589
0
                continue;
590
0
            }
591
0
#endif
592
0
            curStep.args.push_back(std::move(arg));
593
0
        }
594
0
    }
595
596
0
    if (nNestLevel > 0)
597
0
    {
598
0
        if (forAutoComplete)
599
0
        {
600
0
            BuildNestedPipeline(steps.back().alg.get(), nestedPipelineArgs,
601
0
                                forAutoComplete);
602
0
            return true;
603
0
        }
604
0
        else
605
0
        {
606
0
            ReportError(CE_Failure, CPLE_AppDefined,
607
0
                        "Open bracket has no matching closing bracket");
608
0
            return false;
609
0
        }
610
0
    }
611
612
    // As we initially added a step without alg to bootstrap things, make
613
    // sure to remove it if it hasn't been filled, or the user has terminated
614
    // the pipeline with a '!' separator.
615
0
    if (!steps.back().alg)
616
0
        steps.pop_back();
617
618
0
    if (runExistingPipeline)
619
0
    {
620
        // Add a final "write" step if there is no explicit allowed last step
621
0
        if (!steps.empty() && !steps.back().alg->CanBeLastStep())
622
0
        {
623
0
            steps.resize(steps.size() + 1);
624
0
            steps.back().alg = GetStepAlg(
625
0
                std::string(GDALRasterWriteAlgorithm::NAME)
626
0
                    .append(bIsGenericPipeline ? RASTER_SUFFIX : ""));
627
0
            steps.back().alg->m_inputDatasetCanBeOmitted = true;
628
0
        }
629
630
        // Remove "--output-format=stream" and "streamed_dataset" if found
631
0
        if (steps.back().alg->GetName() == GDALRasterWriteAlgorithm::NAME)
632
0
        {
633
0
            for (auto oIter = steps.back().args.begin();
634
0
                 oIter != steps.back().args.end();)
635
0
            {
636
0
                if (*oIter == std::string("--")
637
0
                                  .append(GDAL_ARG_NAME_OUTPUT_FORMAT)
638
0
                                  .append("=stream") ||
639
0
                    *oIter == std::string("--")
640
0
                                  .append(GDAL_ARG_NAME_OUTPUT)
641
0
                                  .append("=streamed_dataset") ||
642
0
                    *oIter == "streamed_dataset")
643
0
                {
644
0
                    oIter = steps.back().args.erase(oIter);
645
0
                }
646
0
                else
647
0
                {
648
0
                    ++oIter;
649
0
                }
650
0
            }
651
0
        }
652
0
    }
653
654
0
    bool helpRequested = false;
655
0
    if (IsCalledFromCommandLine())
656
0
    {
657
0
        for (auto &step : steps)
658
0
            step.alg->SetCalledFromCommandLine();
659
660
0
        for (const std::string &v : args)
661
0
        {
662
0
            if (cpl::ends_with(v, "=?"))
663
0
                helpRequested = true;
664
0
        }
665
0
    }
666
667
0
    if (m_eLastStepAsWrite == StepConstraint::MUST_BE)
668
0
    {
669
0
        if (!m_bExpectReadStep)
670
0
        {
671
0
            if (steps.empty())
672
0
            {
673
0
                ReportError(
674
0
                    CE_Failure, CPLE_AppDefined,
675
0
                    "At least one step must be provided in %s pipeline.",
676
0
                    m_bInnerPipeline ? "an inner" : "a");
677
0
                return false;
678
0
            }
679
0
        }
680
0
        else if (steps.size() < 2)
681
0
        {
682
0
            if (!steps.empty() && helpRequested)
683
0
            {
684
0
                steps.back().alg->ParseCommandLineArguments(steps.back().args);
685
0
                return false;
686
0
            }
687
688
0
            ReportError(CE_Failure, CPLE_AppDefined,
689
0
                        "At least 2 steps must be provided");
690
0
            return false;
691
0
        }
692
693
0
        if (!steps.back().alg->CanBeLastStep())
694
0
        {
695
0
            if (helpRequested)
696
0
            {
697
0
                steps.back().alg->ParseCommandLineArguments(steps.back().args);
698
0
                return false;
699
0
            }
700
0
        }
701
0
    }
702
0
    else
703
0
    {
704
0
        if (steps.empty())
705
0
        {
706
0
            ReportError(CE_Failure, CPLE_AppDefined,
707
0
                        "At least one step must be provided in %s pipeline.",
708
0
                        m_bInnerPipeline ? "an inner" : "a");
709
0
            return false;
710
0
        }
711
712
0
        if (m_eLastStepAsWrite == StepConstraint::CAN_NOT_BE &&
713
0
            steps.back().alg->CanBeLastStep() &&
714
0
            !steps.back().alg->CanBeMiddleStep())
715
0
        {
716
0
            ReportError(CE_Failure, CPLE_AppDefined,
717
0
                        "Last step in %s pipeline must not be a "
718
0
                        "write-like step.",
719
0
                        m_bInnerPipeline ? "an inner" : "a");
720
0
            return false;
721
0
        }
722
0
    }
723
724
0
    std::vector<GDALPipelineStepAlgorithm *> stepAlgs;
725
0
    for (const auto &step : steps)
726
0
        stepAlgs.push_back(step.alg.get());
727
0
    if (!CheckFirstAndLastStep(stepAlgs, forAutoComplete))
728
0
        return false;  // CheckFirstAndLastStep emits an error
729
730
0
    for (auto &step : steps)
731
0
    {
732
0
        step.alg->SetReferencePathForRelativePaths(
733
0
            GetReferencePathForRelativePaths());
734
0
    }
735
736
    // Propagate input parameters set at the pipeline level to the
737
    // "read" step
738
0
    if (m_bExpectReadStep)
739
0
    {
740
0
        auto &step = steps.front();
741
0
        for (auto &arg : step.alg->GetArgs())
742
0
        {
743
0
            if (!arg->IsHidden())
744
0
            {
745
0
                auto pipelineArg =
746
0
                    const_cast<const GDALAbstractPipelineAlgorithm *>(this)
747
0
                        ->GetArg(arg->GetName());
748
0
                if (pipelineArg && pipelineArg->IsExplicitlySet() &&
749
0
                    pipelineArg->GetType() == arg->GetType())
750
0
                {
751
0
                    arg->SetSkipIfAlreadySet(true);
752
0
                    arg->SetFrom(*pipelineArg);
753
0
                }
754
0
            }
755
0
        }
756
0
    }
757
758
    // Same with "write" step
759
0
    const auto SetWriteArgFromPipeline = [this, &steps]()
760
0
    {
761
0
        auto &step = steps.back();
762
0
        for (auto &arg : step.alg->GetArgs())
763
0
        {
764
0
            if (!arg->IsHidden())
765
0
            {
766
0
                auto pipelineArg =
767
0
                    const_cast<const GDALAbstractPipelineAlgorithm *>(this)
768
0
                        ->GetArg(arg->GetName());
769
0
                if (pipelineArg && pipelineArg->IsExplicitlySet() &&
770
0
                    pipelineArg->GetType() == arg->GetType())
771
0
                {
772
0
                    arg->SetSkipIfAlreadySet(true);
773
0
                    arg->SetFrom(*pipelineArg);
774
0
                }
775
0
            }
776
0
        }
777
0
    };
778
779
0
    if (m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE &&
780
0
        steps.back().alg->CanBeLastStep())
781
0
    {
782
0
        SetWriteArgFromPipeline();
783
0
    }
784
785
0
    if (runExistingPipeline)
786
0
    {
787
0
        std::set<std::pair<Step *, std::string>> alreadyCleanedArgs;
788
789
0
        for (const auto &arg : GetArgs())
790
0
        {
791
0
            if (arg->IsUserProvided() ||
792
0
                ((arg->GetName() == GDAL_ARG_NAME_INPUT ||
793
0
                  arg->GetName() == GDAL_ARG_NAME_INPUT_LAYER ||
794
0
                  arg->GetName() == GDAL_ARG_NAME_OUTPUT ||
795
0
                  arg->GetName() == GDAL_ARG_NAME_OUTPUT_FORMAT) &&
796
0
                 arg->IsExplicitlySet()))
797
0
            {
798
0
                CPLStringList tokens(
799
0
                    CSLTokenizeString2(arg->GetName().c_str(), ".", 0));
800
0
                std::string stepName;
801
0
                std::string stepArgName;
802
0
                if (tokens.size() == 1 && IsReadSpecificArgument(tokens[0]))
803
0
                {
804
0
                    stepName = steps.front().alg->GetName();
805
0
                    stepArgName = tokens[0];
806
0
                }
807
0
                else if (tokens.size() == 1 &&
808
0
                         IsWriteSpecificArgument(tokens[0]))
809
0
                {
810
0
                    stepName = steps.back().alg->GetName();
811
0
                    stepArgName = tokens[0];
812
0
                }
813
0
                else if (tokens.size() == 2)
814
0
                {
815
0
                    stepName = tokens[0];
816
0
                    stepArgName = tokens[1];
817
0
                }
818
0
                else
819
0
                {
820
0
                    if (tokens.size() == 1)
821
0
                    {
822
0
                        const Step *matchingStep = nullptr;
823
0
                        for (auto &step : steps)
824
0
                        {
825
0
                            if (step.alg->GetArg(tokens[0]))
826
0
                            {
827
0
                                if (!matchingStep)
828
0
                                    matchingStep = &step;
829
0
                                else
830
0
                                {
831
0
                                    ReportError(
832
0
                                        CE_Failure, CPLE_AppDefined,
833
0
                                        "Ambiguous argument name '%s', because "
834
0
                                        "it is valid for several steps in the "
835
0
                                        "pipeline. It should be specified with "
836
0
                                        "the form "
837
0
                                        "<algorithm-name>.<argument-name>.",
838
0
                                        tokens[0]);
839
0
                                    return false;
840
0
                                }
841
0
                            }
842
0
                        }
843
0
                        if (!matchingStep)
844
0
                        {
845
0
                            ReportError(CE_Failure, CPLE_AppDefined,
846
0
                                        "No step in the pipeline has an "
847
0
                                        "argument named '%s'",
848
0
                                        tokens[0]);
849
0
                            return false;
850
0
                        }
851
0
                        stepName = matchingStep->alg->GetName();
852
0
                        stepArgName = tokens[0];
853
0
                    }
854
0
                    else
855
0
                    {
856
0
                        ReportError(
857
0
                            CE_Failure, CPLE_AppDefined,
858
0
                            "Invalid argument name '%s'. It should of the "
859
0
                            "form <algorithm-name>.<argument-name>.",
860
0
                            arg->GetName().c_str());
861
0
                        return false;
862
0
                    }
863
0
                }
864
0
                const auto nPosBracket = stepName.find('[');
865
0
                int iRequestedStepIdx = -1;
866
0
                if (nPosBracket != std::string::npos && stepName.back() == ']')
867
0
                {
868
0
                    iRequestedStepIdx =
869
0
                        atoi(stepName.c_str() + nPosBracket + 1);
870
0
                    stepName.resize(nPosBracket);
871
0
                }
872
0
                int iMatchingStepIdx = 0;
873
0
                Step *matchingStep = nullptr;
874
0
                for (auto &step : steps)
875
0
                {
876
0
                    if (step.alg->GetName() == stepName)
877
0
                    {
878
0
                        if (iRequestedStepIdx >= 0)
879
0
                        {
880
0
                            if (iRequestedStepIdx == iMatchingStepIdx)
881
0
                            {
882
0
                                matchingStep = &step;
883
0
                                break;
884
0
                            }
885
0
                            ++iMatchingStepIdx;
886
0
                        }
887
0
                        else if (matchingStep == nullptr)
888
0
                        {
889
0
                            matchingStep = &step;
890
0
                        }
891
0
                        else
892
0
                        {
893
0
                            ReportError(
894
0
                                CE_Failure, CPLE_AppDefined,
895
0
                                "Argument '%s' is ambiguous as there are "
896
0
                                "several '%s' steps in the pipeline. Qualify "
897
0
                                "it as '%s[<zero-based-index>]' to remove "
898
0
                                "ambiguity.",
899
0
                                arg->GetName().c_str(), stepName.c_str(),
900
0
                                stepName.c_str());
901
0
                            return false;
902
0
                        }
903
0
                    }
904
0
                }
905
0
                if (!matchingStep)
906
0
                {
907
0
                    ReportError(CE_Failure, CPLE_AppDefined,
908
0
                                "Argument '%s' refers to a non-existing '%s' "
909
0
                                "step in the pipeline.",
910
0
                                arg->GetName().c_str(), tokens[0]);
911
0
                    return false;
912
0
                }
913
914
0
                auto &step = *matchingStep;
915
0
                std::string stepArgNameDashDash =
916
0
                    std::string("--").append(stepArgName);
917
918
0
                auto oKeyPair = std::make_pair(matchingStep, stepArgName);
919
0
                if (!cpl::contains(alreadyCleanedArgs, oKeyPair))
920
0
                {
921
0
                    alreadyCleanedArgs.insert(std::move(oKeyPair));
922
923
0
                    std::vector<GDALAlgorithmArg *> positionalArgs;
924
0
                    for (auto &stepArg : step.alg->GetArgs())
925
0
                    {
926
0
                        if (stepArg->IsPositional())
927
0
                            positionalArgs.push_back(stepArg.get());
928
0
                    }
929
930
                    // Remove step arguments that match the user override
931
0
                    const std::string stepArgNameDashDashEqual =
932
0
                        stepArgNameDashDash + '=';
933
0
                    size_t idxPositional = 0;
934
0
                    for (auto oIter = step.args.begin();
935
0
                         oIter != step.args.end();)
936
0
                    {
937
0
                        const auto &iterArgName = *oIter;
938
0
                        if (iterArgName == stepArgNameDashDash)
939
0
                        {
940
0
                            oIter = step.args.erase(oIter);
941
0
                            auto stepArg = step.alg->GetArg(stepArgName);
942
0
                            if (stepArg && stepArg->GetType() != GAAT_BOOLEAN)
943
0
                            {
944
0
                                if (oIter != step.args.end())
945
0
                                    oIter = step.args.erase(oIter);
946
0
                            }
947
0
                        }
948
0
                        else if (cpl::starts_with(iterArgName,
949
0
                                                  stepArgNameDashDashEqual))
950
0
                        {
951
0
                            oIter = step.args.erase(oIter);
952
0
                        }
953
0
                        else if (!iterArgName.empty() && iterArgName[0] == '-')
954
0
                        {
955
0
                            const auto equalPos = iterArgName.find('=');
956
0
                            auto stepArg = step.alg->GetArg(
957
0
                                equalPos == std::string::npos
958
0
                                    ? iterArgName
959
0
                                    : iterArgName.substr(0, equalPos));
960
0
                            ++oIter;
961
0
                            if (stepArg && equalPos == std::string::npos &&
962
0
                                stepArg->GetType() != GAAT_BOOLEAN)
963
0
                            {
964
0
                                if (oIter != step.args.end())
965
0
                                    ++oIter;
966
0
                            }
967
0
                        }
968
0
                        else if (idxPositional < positionalArgs.size())
969
0
                        {
970
0
                            if (positionalArgs[idxPositional]->GetName() ==
971
0
                                stepArgName)
972
0
                            {
973
0
                                oIter = step.args.erase(oIter);
974
0
                            }
975
0
                            else
976
0
                            {
977
0
                                ++oIter;
978
0
                            }
979
0
                            ++idxPositional;
980
0
                        }
981
0
                        else
982
0
                        {
983
0
                            ++oIter;
984
0
                        }
985
0
                    }
986
0
                }
987
988
0
                if (arg->IsUserProvided())
989
0
                {
990
                    // Add user override
991
0
                    step.args.push_back(std::move(stepArgNameDashDash));
992
0
                    auto stepArg = step.alg->GetArg(stepArgName);
993
0
                    if (stepArg && stepArg->GetType() != GAAT_BOOLEAN)
994
0
                    {
995
0
                        step.args.push_back(arg->Get<std::string>());
996
0
                    }
997
0
                }
998
0
            }
999
0
        }
1000
0
    }
1001
1002
0
    int nInitialDatasetType = 0;
1003
0
    if (bIsGenericPipeline)
1004
0
    {
1005
0
        if (!m_bExpectReadStep)
1006
0
        {
1007
0
            CPLAssert(m_inputDataset.size() == 1 &&
1008
0
                      m_inputDataset[0].GetDatasetRef());
1009
0
            if (m_inputDataset[0].GetDatasetRef()->GetRasterCount() > 0)
1010
0
            {
1011
0
                nInitialDatasetType = GDAL_OF_RASTER;
1012
0
            }
1013
0
            else if (m_inputDataset[0].GetDatasetRef()->GetLayerCount() > 0)
1014
0
            {
1015
0
                nInitialDatasetType = GDAL_OF_VECTOR;
1016
0
            }
1017
0
        }
1018
1019
        // Parse each step, but without running the validation
1020
0
        nDatasetType = nInitialDatasetType;
1021
0
        bool firstStep = nDatasetType == 0;
1022
1023
0
        for (auto &step : steps)
1024
0
        {
1025
0
            bool ret = false;
1026
0
            CPLErrorAccumulator oAccumulator;
1027
0
            bool hasTriedRaster = false;
1028
0
            if (nDatasetType == 0 || nDatasetType == GDAL_OF_RASTER)
1029
0
            {
1030
0
                hasTriedRaster = true;
1031
0
                [[maybe_unused]] auto context =
1032
0
                    oAccumulator.InstallForCurrentScope();
1033
0
                step.alg->m_skipValidationInParseCommandLine = true;
1034
0
                ret = step.alg->ParseCommandLineArguments(step.args);
1035
0
                if (ret && nDatasetType == 0 && forAutoComplete)
1036
0
                {
1037
0
                    ret = step.alg->ValidateArguments();
1038
0
                    if (ret && firstStep &&
1039
0
                        step.alg->m_inputDataset.size() == 1)
1040
0
                    {
1041
0
                        auto poDS = step.alg->m_inputDataset[0].GetDatasetRef();
1042
0
                        if (poDS && poDS->GetLayerCount() > 0)
1043
0
                            ret = false;
1044
0
                    }
1045
0
                    else if (!ret && firstStep)
1046
0
                        ret = true;
1047
0
                }
1048
0
            }
1049
0
            else if (!m_bExpectReadStep &&
1050
0
                     nDatasetType == step.alg->GetInputType())
1051
0
            {
1052
0
                step.alg->m_skipValidationInParseCommandLine = true;
1053
0
                ret = step.alg->ParseCommandLineArguments(step.args);
1054
0
                if (!ret)
1055
0
                    return false;
1056
0
            }
1057
1058
0
            if (!ret)
1059
0
            {
1060
0
                auto algVector =
1061
0
                    GetStepAlg(step.alg->GetName() + VECTOR_SUFFIX);
1062
0
                if (algVector &&
1063
0
                    (nDatasetType == 0 || nDatasetType == GDAL_OF_VECTOR))
1064
0
                {
1065
0
                    step.alg = std::move(algVector);
1066
0
                    step.alg->m_inputDatasetCanBeOmitted =
1067
0
                        !firstStep || !m_bExpectReadStep;
1068
0
                    step.alg->m_skipValidationInParseCommandLine = true;
1069
0
                    ret = step.alg->ParseCommandLineArguments(step.args);
1070
0
                    if (ret)
1071
0
                    {
1072
0
                        step.alg->SetCallPath({step.alg->GetName()});
1073
0
                        step.alg->SetReferencePathForRelativePaths(
1074
0
                            GetReferencePathForRelativePaths());
1075
0
                        step.alreadyChangedType = true;
1076
0
                    }
1077
0
                    else if (!forAutoComplete)
1078
0
                        return false;
1079
0
                }
1080
0
                if (!ret && hasTriedRaster && !forAutoComplete)
1081
0
                {
1082
0
                    for (const auto &sError : oAccumulator.GetErrors())
1083
0
                    {
1084
0
                        CPLError(sError.type, sError.no, "%s",
1085
0
                                 sError.msg.c_str());
1086
0
                    }
1087
0
                    return false;
1088
0
                }
1089
0
            }
1090
0
            if (ret && forAutoComplete)
1091
0
                nDatasetType = step.alg->GetOutputType();
1092
0
            firstStep = false;
1093
0
        }
1094
0
    }
1095
0
    else
1096
0
    {
1097
0
        for (auto &step : steps)
1098
0
        {
1099
0
            step.alg->m_skipValidationInParseCommandLine = true;
1100
0
            if (!step.alg->ParseCommandLineArguments(step.args) &&
1101
0
                !forAutoComplete)
1102
0
                return false;
1103
0
        }
1104
0
    }
1105
1106
    // Evaluate "input" argument of "read" step, together with the "output"
1107
    // argument of the "write" step, in case they point to the same dataset.
1108
0
    auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
1109
0
    if (inputArg && inputArg->IsExplicitlySet() &&
1110
0
        inputArg->GetType() == GAAT_DATASET_LIST &&
1111
0
        inputArg->Get<std::vector<GDALArgDatasetValue>>().size() == 1)
1112
0
    {
1113
0
        int nCountChangeFieldTypeStepsToBeRemoved = 0;
1114
0
        std::string osTmpJSONFilename;
1115
1116
        // Check if there are steps like change-field-type just after the read
1117
        // step. If so, we can convert them into a OGR_SCHEMA open option for
1118
        // drivers that support it.
1119
0
        auto &inputVals = inputArg->Get<std::vector<GDALArgDatasetValue>>();
1120
0
        if (!inputVals[0].GetDatasetRef() && steps.size() >= 2 &&
1121
0
            steps[0].alg->GetName() == GDALVectorReadAlgorithm::NAME &&
1122
0
            !steps.back().alg->IsGDALGOutput())
1123
0
        {
1124
0
            auto openOptionArgs =
1125
0
                steps.front().alg->GetArg(GDAL_ARG_NAME_OPEN_OPTION);
1126
0
            if (openOptionArgs && !openOptionArgs->IsExplicitlySet() &&
1127
0
                openOptionArgs->GetType() == GAAT_STRING_LIST)
1128
0
            {
1129
0
                const auto &openOptionVals =
1130
0
                    openOptionArgs->Get<std::vector<std::string>>();
1131
0
                if (CPLStringList(openOptionVals)
1132
0
                        .FetchNameValue("OGR_SCHEMA") == nullptr)
1133
0
                {
1134
0
                    CPLJSONArray oLayers;
1135
0
                    for (size_t iStep = 1; iStep < steps.size(); ++iStep)
1136
0
                    {
1137
0
                        auto oObj =
1138
0
                            steps[iStep].alg->Get_OGR_SCHEMA_OpenOption_Layer();
1139
0
                        if (!oObj.IsValid())
1140
0
                            break;
1141
0
                        oLayers.Add(oObj);
1142
0
                        ++nCountChangeFieldTypeStepsToBeRemoved;
1143
0
                    }
1144
1145
0
                    if (nCountChangeFieldTypeStepsToBeRemoved > 0)
1146
0
                    {
1147
0
                        CPLJSONDocument oDoc;
1148
0
                        oDoc.GetRoot().Set("layers", oLayers);
1149
0
                        osTmpJSONFilename =
1150
0
                            VSIMemGenerateHiddenFilename(nullptr);
1151
                        // CPLDebug("GDAL", "OGR_SCHEMA: %s", oDoc.SaveAsString().c_str());
1152
0
                        oDoc.Save(osTmpJSONFilename);
1153
1154
0
                        openOptionArgs->Set(std::vector<std::string>{
1155
0
                            std::string("@OGR_SCHEMA=")
1156
0
                                .append(osTmpJSONFilename)});
1157
0
                    }
1158
0
                }
1159
0
            }
1160
0
        }
1161
1162
0
        const bool bOK = steps.front().alg->ProcessDatasetArg(
1163
0
                             inputArg, steps.back().alg.get()) ||
1164
0
                         forAutoComplete;
1165
1166
0
        if (!osTmpJSONFilename.empty())
1167
0
            VSIUnlink(osTmpJSONFilename.c_str());
1168
1169
0
        if (!bOK)
1170
0
        {
1171
0
            return false;
1172
0
        }
1173
1174
        // Now check if the driver of the input dataset actually supports
1175
        // the OGR_SCHEMA open option. If so, we can remove the steps from
1176
        // the pipeline
1177
0
        if (nCountChangeFieldTypeStepsToBeRemoved)
1178
0
        {
1179
0
            if (auto poDS = inputVals[0].GetDatasetRef())
1180
0
            {
1181
0
                if (auto poDriver = poDS->GetDriver())
1182
0
                {
1183
0
                    const char *pszOpenOptionList =
1184
0
                        poDriver->GetMetadataItem(GDAL_DMD_OPENOPTIONLIST);
1185
0
                    if (pszOpenOptionList &&
1186
0
                        strstr(pszOpenOptionList, "OGR_SCHEMA"))
1187
0
                    {
1188
0
                        CPLDebug("GDAL",
1189
0
                                 "Merging %d step(s) as OGR_SCHEMA open option",
1190
0
                                 nCountChangeFieldTypeStepsToBeRemoved);
1191
0
                        steps.erase(steps.begin() + 1,
1192
0
                                    steps.begin() + 1 +
1193
0
                                        nCountChangeFieldTypeStepsToBeRemoved);
1194
0
                    }
1195
0
                }
1196
0
            }
1197
0
        }
1198
0
    }
1199
1200
0
    if (bIsGenericPipeline)
1201
0
    {
1202
0
        int nLastStepOutputType = nInitialDatasetType;
1203
0
        if (m_bExpectReadStep)
1204
0
        {
1205
0
            nLastStepOutputType = GDAL_OF_VECTOR;
1206
0
            if (steps.front().alg->GetName() !=
1207
0
                    std::string(GDALRasterReadAlgorithm::NAME) &&
1208
0
                steps.front().alg->GetOutputType() == GDAL_OF_RASTER)
1209
0
            {
1210
0
                nLastStepOutputType = GDAL_OF_RASTER;
1211
0
            }
1212
0
            else
1213
0
            {
1214
0
                auto &inputDatasets = steps.front().alg->GetInputDatasets();
1215
0
                if (!inputDatasets.empty())
1216
0
                {
1217
0
                    auto poSrcDS = inputDatasets[0].GetDatasetRef();
1218
0
                    if (poSrcDS)
1219
0
                    {
1220
0
                        if (poSrcDS->GetRasterCount() != 0)
1221
0
                            nLastStepOutputType = GDAL_OF_RASTER;
1222
0
                    }
1223
0
                }
1224
0
            }
1225
0
        }
1226
1227
0
        for (size_t i =
1228
0
                 ((m_bExpectReadStep && steps[0].alg->GetOutputType() != 0)
1229
0
                      ? 1
1230
0
                      : 0);
1231
0
             !forAutoComplete && i < steps.size(); ++i)
1232
0
        {
1233
0
            if (!steps[i].alreadyChangedType && !steps[i].isSubAlgorithm &&
1234
0
                GetStepAlg(steps[i].alg->GetName()) == nullptr)
1235
0
            {
1236
0
                auto newAlg = GetStepAlg(steps[i].alg->GetName() +
1237
0
                                         (nLastStepOutputType == GDAL_OF_RASTER
1238
0
                                              ? RASTER_SUFFIX
1239
0
                                              : VECTOR_SUFFIX));
1240
0
                CPLAssert(newAlg);
1241
1242
0
                if (steps[i].alg->GetName() ==
1243
0
                    GDALTeeStepAlgorithmAbstract::NAME)
1244
0
                {
1245
0
                    const auto poSrcTeeAlg =
1246
0
                        dynamic_cast<const GDALTeeStepAlgorithmAbstract *>(
1247
0
                            steps[i].alg.get());
1248
0
                    auto poDstTeeAlg =
1249
0
                        dynamic_cast<GDALTeeStepAlgorithmAbstract *>(
1250
0
                            newAlg.get());
1251
0
                    CPLAssert(poSrcTeeAlg);
1252
0
                    CPLAssert(poDstTeeAlg);
1253
0
                    poDstTeeAlg->CopyFilenameBindingsFrom(poSrcTeeAlg);
1254
0
                }
1255
1256
0
                steps[i].alg = std::move(newAlg);
1257
1258
0
                if (i == steps.size() - 1 &&
1259
0
                    m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE)
1260
0
                {
1261
0
                    SetWriteArgFromPipeline();
1262
0
                }
1263
1264
0
                steps[i].alg->m_inputDatasetCanBeOmitted =
1265
0
                    i > 0 || !m_bExpectReadStep;
1266
0
                steps[i].alg->m_skipValidationInParseCommandLine = true;
1267
0
                if (!steps[i].alg->ParseCommandLineArguments(steps[i].args))
1268
0
                    return false;
1269
0
                steps[i].alg->SetCallPath({steps[i].alg->GetName()});
1270
0
                steps[i].alg->SetReferencePathForRelativePaths(
1271
0
                    GetReferencePathForRelativePaths());
1272
0
                if (IsCalledFromCommandLine())
1273
0
                    steps[i].alg->SetCalledFromCommandLine();
1274
0
                steps[i].alreadyChangedType = true;
1275
0
            }
1276
1277
0
            if (i > 0)
1278
0
            {
1279
0
                bool emitError =
1280
0
                    (steps[i].alg->GetInputType() != 0 &&
1281
0
                     steps[i].alg->GetInputType() != nLastStepOutputType);
1282
1283
                // Check if a dataset argument, which has as value the
1284
                // placeholder value, has the same dataset type as the output
1285
                // of the last step
1286
0
                for (const auto &arg : steps[i].alg->GetArgs())
1287
0
                {
1288
0
                    if (!arg->IsOutput() &&
1289
0
                        (arg->GetType() == GAAT_DATASET ||
1290
0
                         arg->GetType() == GAAT_DATASET_LIST))
1291
0
                    {
1292
0
                        if (arg->GetType() == GAAT_DATASET)
1293
0
                        {
1294
0
                            if (arg->Get<GDALArgDatasetValue>().GetName() ==
1295
0
                                GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
1296
0
                            {
1297
0
                                if ((arg->GetDatasetType() &
1298
0
                                     nLastStepOutputType) != 0)
1299
0
                                {
1300
0
                                    emitError = false;
1301
0
                                    break;
1302
0
                                }
1303
0
                            }
1304
0
                        }
1305
0
                        else
1306
0
                        {
1307
0
                            CPLAssert(arg->GetType() == GAAT_DATASET_LIST);
1308
0
                            auto &val =
1309
0
                                arg->Get<std::vector<GDALArgDatasetValue>>();
1310
0
                            if (val.size() == 1 &&
1311
0
                                val[0].GetName() ==
1312
0
                                    GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
1313
0
                            {
1314
0
                                if ((arg->GetDatasetType() &
1315
0
                                     nLastStepOutputType) != 0)
1316
0
                                {
1317
0
                                    emitError = false;
1318
0
                                    break;
1319
0
                                }
1320
0
                            }
1321
0
                        }
1322
0
                    }
1323
0
                }
1324
0
                if (emitError)
1325
0
                {
1326
0
                    ReportError(CE_Failure, CPLE_AppDefined,
1327
0
                                "Step '%s' expects a %s input dataset, but "
1328
0
                                "previous step '%s' "
1329
0
                                "generates a %s output dataset",
1330
0
                                steps[i].alg->GetName().c_str(),
1331
0
                                steps[i].alg->GetInputType() == GDAL_OF_RASTER
1332
0
                                    ? "raster"
1333
0
                                : steps[i].alg->GetInputType() == GDAL_OF_VECTOR
1334
0
                                    ? "vector"
1335
0
                                    : "unknown",
1336
0
                                steps[i - 1].alg->GetName().c_str(),
1337
0
                                nLastStepOutputType == GDAL_OF_RASTER ? "raster"
1338
0
                                : nLastStepOutputType == GDAL_OF_VECTOR
1339
0
                                    ? "vector"
1340
0
                                    : "unknown");
1341
0
                    return false;
1342
0
                }
1343
0
            }
1344
1345
0
            nLastStepOutputType = steps[i].alg->GetOutputType();
1346
0
            if (!forAutoComplete && nLastStepOutputType == 0)
1347
0
            {
1348
                // If this step has no precise output dataset (unique instance
1349
                // at time of writing is 'external'), we stop trying to fix
1350
                // the raster/vector nature of ambiguous steps for now, and
1351
                // defer doing that during pipeline execution itself.
1352
0
                m_nFirstStepWithUnknownInputType = static_cast<int>(i + 1);
1353
0
                break;
1354
0
            }
1355
0
        }
1356
0
    }
1357
1358
0
    int iStep = 0;
1359
0
    for (const auto &step : steps)
1360
0
    {
1361
0
        if (iStep == m_nFirstStepWithUnknownInputType)
1362
0
            break;
1363
0
        if (!step.alg->ValidateArguments() && !forAutoComplete)
1364
0
            return false;
1365
0
        ++iStep;
1366
0
    }
1367
1368
0
    for (auto &step : steps)
1369
0
        m_steps.push_back(std::move(step.alg));
1370
1371
0
    return true;
1372
0
}
1373
1374
/************************************************************************/
1375
/*         GDALAbstractPipelineAlgorithm::BuildNestedPipeline()         */
1376
/************************************************************************/
1377
1378
std::string GDALAbstractPipelineAlgorithm::BuildNestedPipeline(
1379
    GDALPipelineStepAlgorithm *curAlg,
1380
    std::vector<std::string> &nestedPipelineArgs, bool forAutoComplete)
1381
0
{
1382
0
    std::string datasetNameOut;
1383
0
    CPLAssert(curAlg);
1384
1385
0
    auto nestedPipeline = CreateNestedPipeline();
1386
0
    if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
1387
0
        nestedPipeline->m_bExpectReadStep = false;
1388
0
    else
1389
0
        nestedPipeline->m_eLastStepAsWrite = StepConstraint::CAN_NOT_BE;
1390
0
    nestedPipeline->m_executionForStreamOutput = m_executionForStreamOutput;
1391
0
    nestedPipeline->SetReferencePathForRelativePaths(
1392
0
        GetReferencePathForRelativePaths());
1393
1394
0
    std::string argsStr = OPEN_NESTED_PIPELINE;
1395
0
    for (const std::string &str : nestedPipelineArgs)
1396
0
    {
1397
0
        argsStr += ' ';
1398
0
        argsStr += GDALAlgorithmArg::GetEscapedString(str);
1399
0
    }
1400
0
    argsStr += ' ';
1401
0
    argsStr += CLOSE_NESTED_PIPELINE;
1402
1403
0
    if (curAlg->GetName() != GDALTeeStepAlgorithmAbstract::NAME)
1404
0
    {
1405
0
        if (!nestedPipeline->ParseCommandLineArguments(nestedPipelineArgs,
1406
0
                                                       forAutoComplete) ||
1407
0
            (!forAutoComplete && !nestedPipeline->Run()))
1408
0
        {
1409
0
            return datasetNameOut;
1410
0
        }
1411
0
        auto poDS = nestedPipeline->GetOutputDataset().GetDatasetRef();
1412
0
        if (!poDS)
1413
0
        {
1414
            // That shouldn't happen normally for well-behaved algorithms, but
1415
            // it doesn't hurt checking.
1416
0
            ReportError(CE_Failure, CPLE_AppDefined,
1417
0
                        "Nested pipeline does not generate an output dataset");
1418
0
            return datasetNameOut;
1419
0
        }
1420
0
        datasetNameOut =
1421
0
            CPLSPrintf("$$nested_pipeline_%p$$", nestedPipeline.get());
1422
0
        curAlg->m_oMapDatasetNameToDataset[datasetNameOut] = poDS;
1423
1424
0
        poDS->SetDescription(argsStr.c_str());
1425
0
    }
1426
1427
0
    m_apoNestedPipelines.emplace_back(std::move(nestedPipeline));
1428
1429
0
    if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
1430
0
    {
1431
0
        auto teeAlg = dynamic_cast<GDALTeeStepAlgorithmAbstract *>(curAlg);
1432
0
        if (teeAlg)
1433
0
        {
1434
0
            datasetNameOut = std::move(argsStr);
1435
0
            if (!teeAlg->BindFilename(datasetNameOut,
1436
0
                                      m_apoNestedPipelines.back().get(),
1437
0
                                      nestedPipelineArgs))
1438
0
            {
1439
0
                ReportError(CE_Failure, CPLE_AppDefined,
1440
0
                            "Another identical nested pipeline exists");
1441
0
                datasetNameOut.clear();
1442
0
            }
1443
0
        }
1444
0
    }
1445
1446
0
    nestedPipelineArgs.clear();
1447
1448
0
    return datasetNameOut;
1449
0
}
1450
1451
/************************************************************************/
1452
/*           GDALAbstractPipelineAlgorithm::GetAutoComplete()           */
1453
/************************************************************************/
1454
1455
std::vector<std::string>
1456
GDALAbstractPipelineAlgorithm::GetAutoComplete(std::vector<std::string> &args,
1457
                                               bool lastWordIsComplete,
1458
                                               bool showAllOptions)
1459
0
{
1460
0
    {
1461
0
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1462
0
        ParseCommandLineArguments(args, /*forAutoComplete=*/true);
1463
0
    }
1464
0
    VSIStatBufL sStat;
1465
0
    if (!m_pipeline.empty() && VSIStatL(m_pipeline.c_str(), &sStat) == 0 &&
1466
0
        !m_steps.empty() && !args.empty())
1467
0
    {
1468
0
        std::map<std::string, std::vector<GDALAlgorithm *>> mapSteps;
1469
0
        for (const auto &step : m_steps)
1470
0
        {
1471
0
            mapSteps[step->GetName()].push_back(step.get());
1472
0
        }
1473
1474
0
        std::vector<std::string> ret;
1475
0
        const auto &lastArg = args.back();
1476
0
        if (!lastArg.empty() && lastArg[0] == '-' &&
1477
0
            lastArg.find('=') == std::string::npos && !lastWordIsComplete)
1478
0
        {
1479
0
            for (const auto &step : m_steps)
1480
0
            {
1481
0
                const int iterCount =
1482
0
                    static_cast<int>(mapSteps[step->GetName()].size());
1483
0
                for (int i = 0; i < iterCount; ++i)
1484
0
                {
1485
0
                    for (const auto &arg : step->GetArgs())
1486
0
                    {
1487
0
                        if (!arg->IsHiddenForCLI() &&
1488
0
                            arg->GetCategory() != GAAC_COMMON)
1489
0
                        {
1490
0
                            std::string s = std::string("--");
1491
0
                            if (!((step->GetName() == "read" &&
1492
0
                                   IsReadSpecificArgument(
1493
0
                                       arg->GetName().c_str())) ||
1494
0
                                  (step->GetName() == "write" &&
1495
0
                                   IsWriteSpecificArgument(
1496
0
                                       arg->GetName().c_str()))))
1497
0
                            {
1498
0
                                s += step->GetName();
1499
0
                                if (iterCount > 1)
1500
0
                                {
1501
0
                                    s += '[';
1502
0
                                    s += std::to_string(i);
1503
0
                                    s += ']';
1504
0
                                }
1505
0
                                s += '.';
1506
0
                            }
1507
0
                            s += arg->GetName();
1508
0
                            if (arg->GetType() == GAAT_BOOLEAN)
1509
0
                                ret.push_back(std::move(s));
1510
0
                            else
1511
0
                                ret.push_back(s + "=");
1512
0
                        }
1513
0
                    }
1514
0
                }
1515
0
            }
1516
0
        }
1517
0
        else if (cpl::starts_with(lastArg, "--") &&
1518
0
                 lastArg.find('=') != std::string::npos && !lastWordIsComplete)
1519
0
        {
1520
0
            const auto nDotPos = lastArg.find('.');
1521
0
            std::string stepName;
1522
0
            std::string argName;
1523
0
            int idx = 0;
1524
0
            if (nDotPos != std::string::npos)
1525
0
            {
1526
0
                stepName = lastArg.substr(strlen("--"), nDotPos - strlen("--"));
1527
0
                const auto nBracketPos = stepName.find('[');
1528
0
                if (nBracketPos != std::string::npos)
1529
0
                {
1530
0
                    idx = atoi(stepName.c_str() + nBracketPos + 1);
1531
0
                    stepName.resize(nBracketPos);
1532
0
                }
1533
0
                argName = "--" + lastArg.substr(nDotPos + 1);
1534
0
            }
1535
0
            else
1536
0
            {
1537
0
                argName = lastArg;
1538
0
                for (const char *prefix : apszReadParametersPrefixOmitted)
1539
0
                {
1540
0
                    if (cpl::starts_with(lastArg.substr(strlen("--")),
1541
0
                                         std::string(prefix) + "="))
1542
0
                    {
1543
0
                        stepName = "read";
1544
0
                        break;
1545
0
                    }
1546
0
                }
1547
1548
0
                for (const char *prefix : apszWriteParametersPrefixOmitted)
1549
0
                {
1550
0
                    if (cpl::starts_with(lastArg.substr(strlen("--")),
1551
0
                                         std::string(prefix) + "="))
1552
0
                    {
1553
0
                        stepName = "write";
1554
0
                        break;
1555
0
                    }
1556
0
                }
1557
0
            }
1558
1559
0
            auto iter = mapSteps.find(stepName);
1560
0
            if (iter != mapSteps.end() && idx >= 0 &&
1561
0
                static_cast<size_t>(idx) < iter->second.size())
1562
0
            {
1563
0
                auto &step = iter->second[idx];
1564
0
                std::vector<std::string> subArgs;
1565
0
                for (const auto &arg : step->GetArgs())
1566
0
                {
1567
0
                    std::string strArg;
1568
0
                    if (arg->IsExplicitlySet() &&
1569
0
                        arg->Serialize(strArg, /* absolutePath=*/false))
1570
0
                    {
1571
0
                        subArgs.push_back(std::move(strArg));
1572
0
                    }
1573
0
                }
1574
0
                subArgs.push_back(std::move(argName));
1575
0
                ret = step->GetAutoComplete(subArgs, lastWordIsComplete,
1576
0
                                            showAllOptions);
1577
0
            }
1578
0
        }
1579
0
        return ret;
1580
0
    }
1581
0
    else
1582
0
    {
1583
0
        std::vector<std::string> ret;
1584
0
        std::set<std::string> setSuggestions;
1585
0
        if (args.size() <= 1)
1586
0
        {
1587
0
            for (const std::string &name : GetStepRegistry().GetNames())
1588
0
            {
1589
0
                auto alg = GetStepRegistry().Instantiate(name);
1590
0
                auto stepAlg =
1591
0
                    dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
1592
0
                if (stepAlg && stepAlg->CanBeFirstStep())
1593
0
                {
1594
0
                    std::string suggestionName =
1595
0
                        CPLString(name)
1596
0
                            .replaceAll(RASTER_SUFFIX, "")
1597
0
                            .replaceAll(VECTOR_SUFFIX, "");
1598
0
                    if (!cpl::contains(setSuggestions, suggestionName))
1599
0
                    {
1600
0
                        if (!args.empty() && suggestionName == args[0])
1601
0
                            return {};
1602
0
                        if (args.empty() ||
1603
0
                            cpl::starts_with(suggestionName, args[0]))
1604
0
                        {
1605
0
                            setSuggestions.insert(suggestionName);
1606
0
                            ret.push_back(std::move(suggestionName));
1607
0
                        }
1608
0
                    }
1609
0
                }
1610
0
            }
1611
0
        }
1612
0
        else
1613
0
        {
1614
0
            int nDatasetType = GetInputType();
1615
0
            constexpr int MIXED_TYPE = GDAL_OF_RASTER | GDAL_OF_VECTOR;
1616
0
            const bool isMixedTypePipeline = nDatasetType == MIXED_TYPE;
1617
0
            std::string lastStep = args[0];
1618
0
            std::vector<std::string> lastArgs;
1619
0
            bool firstStep = true;
1620
0
            bool foundSlowStep = false;
1621
0
            for (size_t i = 1; i < args.size(); ++i)
1622
0
            {
1623
0
                if (firstStep && isMixedTypePipeline &&
1624
0
                    nDatasetType == MIXED_TYPE && !args[i].empty() &&
1625
0
                    args[i][0] != '-')
1626
0
                {
1627
0
                    CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1628
0
                    auto poDS = std::unique_ptr<GDALDataset>(
1629
0
                        GDALDataset::Open(args[i].c_str()));
1630
0
                    if (poDS && poDS->GetLayerCount() > 0 &&
1631
0
                        poDS->GetRasterCount() == 0)
1632
0
                    {
1633
0
                        nDatasetType = GDAL_OF_VECTOR;
1634
0
                    }
1635
0
                    else if (poDS && poDS->GetLayerCount() == 0 &&
1636
0
                             (poDS->GetRasterCount() > 0 ||
1637
0
                              poDS->GetMetadata("SUBDATASETS") != nullptr))
1638
0
                    {
1639
0
                        nDatasetType = GDAL_OF_RASTER;
1640
0
                    }
1641
0
                }
1642
0
                lastArgs.push_back(args[i]);
1643
0
                if (i + 1 < args.size() && args[i] == "!")
1644
0
                {
1645
0
                    firstStep = false;
1646
0
                    ++i;
1647
0
                    lastArgs.clear();
1648
0
                    lastStep = args[i];
1649
0
                    auto curAlg = GetStepAlg(lastStep);
1650
0
                    if (isMixedTypePipeline && !curAlg)
1651
0
                    {
1652
0
                        if (nDatasetType == GDAL_OF_RASTER)
1653
0
                            curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
1654
0
                        else if (nDatasetType == GDAL_OF_VECTOR)
1655
0
                            curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
1656
0
                    }
1657
0
                    if (curAlg)
1658
0
                    {
1659
0
                        foundSlowStep =
1660
0
                            foundSlowStep ||
1661
0
                            !curAlg->IsNativelyStreamingCompatible();
1662
0
                        nDatasetType = curAlg->GetOutputType();
1663
0
                    }
1664
0
                }
1665
0
            }
1666
1667
0
            if (args.back() == "!" ||
1668
0
                (args[args.size() - 2] == "!" && !GetStepAlg(args.back()) &&
1669
0
                 !GetStepAlg(args.back() + RASTER_SUFFIX) &&
1670
0
                 !GetStepAlg(args.back() + VECTOR_SUFFIX)))
1671
0
            {
1672
0
                for (const std::string &name : GetStepRegistry().GetNames())
1673
0
                {
1674
0
                    auto alg = GetStepRegistry().Instantiate(name);
1675
0
                    auto stepAlg =
1676
0
                        dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
1677
0
                    if (stepAlg && isMixedTypePipeline &&
1678
0
                        nDatasetType != MIXED_TYPE &&
1679
0
                        stepAlg->GetInputType() != nDatasetType)
1680
0
                    {
1681
0
                        continue;
1682
0
                    }
1683
0
                    if (stepAlg && !stepAlg->CanBeFirstStep())
1684
0
                    {
1685
0
                        std::string suggestionName =
1686
0
                            CPLString(name)
1687
0
                                .replaceAll(RASTER_SUFFIX, "")
1688
0
                                .replaceAll(VECTOR_SUFFIX, "");
1689
0
                        if (!cpl::contains(setSuggestions, suggestionName))
1690
0
                        {
1691
0
                            setSuggestions.insert(suggestionName);
1692
0
                            ret.push_back(std::move(suggestionName));
1693
0
                        }
1694
0
                    }
1695
0
                }
1696
0
            }
1697
0
            else
1698
0
            {
1699
0
                if (!foundSlowStep)
1700
0
                {
1701
                    // Try to run the pipeline so that the last step gets its
1702
                    // input dataset.
1703
0
                    CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1704
0
                    GDALPipelineStepRunContext ctxt;
1705
0
                    RunStep(ctxt);
1706
0
                    if (!m_steps.empty() &&
1707
0
                        m_steps.back()->GetName() == lastStep)
1708
0
                    {
1709
0
                        return m_steps.back()->GetAutoComplete(
1710
0
                            lastArgs, lastWordIsComplete,
1711
0
                            /* showAllOptions = */ false);
1712
0
                    }
1713
0
                }
1714
1715
0
                auto curAlg = GetStepAlg(lastStep);
1716
0
                if (isMixedTypePipeline && !curAlg)
1717
0
                {
1718
0
                    if (nDatasetType == GDAL_OF_RASTER)
1719
0
                        curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
1720
0
                    else if (nDatasetType == GDAL_OF_VECTOR)
1721
0
                        curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
1722
0
                    else
1723
0
                    {
1724
0
                        for (const char *suffix :
1725
0
                             {RASTER_SUFFIX, VECTOR_SUFFIX})
1726
0
                        {
1727
0
                            curAlg = GetStepAlg(lastStep + suffix);
1728
0
                            if (curAlg)
1729
0
                            {
1730
0
                                for (const auto &v : curAlg->GetAutoComplete(
1731
0
                                         lastArgs, lastWordIsComplete,
1732
0
                                         /* showAllOptions = */ false))
1733
0
                                {
1734
0
                                    if (!cpl::contains(setSuggestions, v))
1735
0
                                    {
1736
0
                                        setSuggestions.insert(v);
1737
0
                                        ret.push_back(std::move(v));
1738
0
                                    }
1739
0
                                }
1740
0
                            }
1741
0
                        }
1742
0
                        curAlg.reset();
1743
0
                    }
1744
0
                }
1745
0
                if (curAlg)
1746
0
                {
1747
0
                    ret = curAlg->GetAutoComplete(lastArgs, lastWordIsComplete,
1748
0
                                                  /* showAllOptions = */ false);
1749
0
                }
1750
0
            }
1751
0
        }
1752
0
        return ret;
1753
0
    }
1754
0
}
1755
1756
/************************************************************************/
1757
/*            GDALAbstractPipelineAlgorithm::SaveGDALGFile()            */
1758
/************************************************************************/
1759
1760
bool GDALAbstractPipelineAlgorithm::SaveGDALGFile(
1761
    const std::string &outFilename, std::string &outString) const
1762
0
{
1763
0
    std::string osCommandLine;
1764
1765
0
    for (const auto &path : GDALAlgorithm::m_callPath)
1766
0
    {
1767
0
        if (!osCommandLine.empty())
1768
0
            osCommandLine += ' ';
1769
0
        osCommandLine += path;
1770
0
    }
1771
1772
    // Do not include the last step
1773
0
    for (size_t i = 0; i + 1 < m_steps.size(); ++i)
1774
0
    {
1775
0
        const auto &step = m_steps[i];
1776
0
        if (!step->IsNativelyStreamingCompatible())
1777
0
        {
1778
0
            GDALAlgorithm::ReportError(
1779
0
                CE_Warning, CPLE_AppDefined,
1780
0
                "Step %s is not natively streaming compatible, and "
1781
0
                "may cause significant processing time at opening",
1782
0
                step->GDALAlgorithm::GetName().c_str());
1783
0
        }
1784
1785
0
        if (i > 0)
1786
0
            osCommandLine += " !";
1787
0
        for (const auto &path : step->GDALAlgorithm::m_callPath)
1788
0
        {
1789
0
            if (!osCommandLine.empty())
1790
0
                osCommandLine += ' ';
1791
0
            osCommandLine += path;
1792
0
        }
1793
1794
0
        for (const auto &arg : step->GetArgs())
1795
0
        {
1796
0
            if (arg->IsExplicitlySet())
1797
0
            {
1798
0
                osCommandLine += ' ';
1799
0
                std::string strArg;
1800
0
                if (!arg->Serialize(strArg, /* absolutePath=*/false))
1801
0
                {
1802
0
                    CPLError(CE_Failure, CPLE_AppDefined,
1803
0
                             "Cannot serialize argument %s",
1804
0
                             arg->GetName().c_str());
1805
0
                    return false;
1806
0
                }
1807
0
                osCommandLine += strArg;
1808
0
            }
1809
0
        }
1810
0
    }
1811
1812
0
    return GDALAlgorithm::SaveGDALG(outFilename, outString, osCommandLine);
1813
0
}
1814
1815
/************************************************************************/
1816
/*               GDALAbstractPipelineAlgorithm::RunStep()               */
1817
/************************************************************************/
1818
1819
bool GDALAbstractPipelineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
1820
0
{
1821
0
    if (m_stepOnWhichHelpIsRequested)
1822
0
    {
1823
0
        printf(
1824
0
            "%s",
1825
0
            m_stepOnWhichHelpIsRequested->GetUsageForCLI(false).c_str()); /*ok*/
1826
0
        return true;
1827
0
    }
1828
1829
0
    if (m_steps.empty())
1830
0
    {
1831
        // If invoked programmatically, not from the command line.
1832
1833
0
        if (m_pipeline.empty())
1834
0
        {
1835
0
            ReportError(CE_Failure, CPLE_AppDefined,
1836
0
                        "'pipeline' argument not set");
1837
0
            return false;
1838
0
        }
1839
1840
0
        const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
1841
0
        if (!ParseCommandLineArguments(aosTokens))
1842
0
            return false;
1843
0
    }
1844
1845
    // Handle output to GDALG file
1846
0
    if (!m_steps.empty() && m_steps.back()->GetName() == "write")
1847
0
    {
1848
0
        if (m_steps.back()->IsGDALGOutput())
1849
0
        {
1850
0
            const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
1851
0
            const auto &filename =
1852
0
                outputArg->Get<GDALArgDatasetValue>().GetName();
1853
0
            const char *pszType = "";
1854
0
            if (GDALDoesFileOrDatasetExist(filename.c_str(), &pszType))
1855
0
            {
1856
0
                const auto overwriteArg =
1857
0
                    m_steps.back()->GetArg(GDAL_ARG_NAME_OVERWRITE);
1858
0
                if (overwriteArg && overwriteArg->GetType() == GAAT_BOOLEAN)
1859
0
                {
1860
0
                    if (!overwriteArg->Get<bool>())
1861
0
                    {
1862
0
                        CPLError(CE_Failure, CPLE_AppDefined,
1863
0
                                 "%s '%s' already exists. Specify the "
1864
0
                                 "--overwrite option to overwrite it.",
1865
0
                                 pszType, filename.c_str());
1866
0
                        return false;
1867
0
                    }
1868
0
                }
1869
0
            }
1870
1871
0
            std::string outStringUnused;
1872
0
            return SaveGDALGFile(filename, outStringUnused);
1873
0
        }
1874
1875
0
        const auto outputFormatArg =
1876
0
            m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT);
1877
0
        const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
1878
0
        if (outputArg && outputArg->GetType() == GAAT_DATASET &&
1879
0
            outputArg->IsExplicitlySet())
1880
0
        {
1881
0
            const auto &outputFile = outputArg->Get<GDALArgDatasetValue>();
1882
0
            bool isVRTOutput;
1883
0
            if (outputFormatArg && outputFormatArg->GetType() == GAAT_STRING &&
1884
0
                outputFormatArg->IsExplicitlySet())
1885
0
            {
1886
0
                const auto &val = outputFormatArg->Get<std::string>();
1887
0
                isVRTOutput = EQUAL(val.c_str(), "vrt");
1888
0
            }
1889
0
            else
1890
0
            {
1891
0
                isVRTOutput = EQUAL(
1892
0
                    CPLGetExtensionSafe(outputFile.GetName().c_str()).c_str(),
1893
0
                    "vrt");
1894
0
            }
1895
0
            if (isVRTOutput && !outputFile.GetName().empty() &&
1896
0
                m_steps.size() > 3)
1897
0
            {
1898
0
                ReportError(
1899
0
                    CE_Failure, CPLE_NotSupported,
1900
0
                    "VRT output is not supported when there are more than 3 "
1901
0
                    "steps. Consider using the GDALG driver (files with "
1902
0
                    ".gdalg.json extension)");
1903
0
                return false;
1904
0
            }
1905
0
            if (isVRTOutput)
1906
0
            {
1907
0
                for (const auto &step : m_steps)
1908
0
                {
1909
0
                    if (!step->m_outputVRTCompatible)
1910
0
                    {
1911
0
                        step->ReportError(
1912
0
                            CE_Failure, CPLE_NotSupported,
1913
0
                            "VRT output is not supported. Consider using the "
1914
0
                            "GDALG driver instead (files with .gdalg.json "
1915
0
                            "extension)");
1916
0
                        return false;
1917
0
                    }
1918
0
                }
1919
0
            }
1920
0
        }
1921
0
    }
1922
1923
0
    if (m_executionForStreamOutput &&
1924
0
        !CPLTestBool(
1925
0
            CPLGetConfigOption("GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM", "NO")))
1926
0
    {
1927
        // For security reasons, to avoid that reading a .gdalg.json file writes
1928
        // a file on the file system.
1929
0
        for (const auto &step : m_steps)
1930
0
        {
1931
0
            if (step->GetName() == "write")
1932
0
            {
1933
0
                if (!EQUAL(step->m_format.c_str(), "stream"))
1934
0
                {
1935
0
                    ReportError(CE_Failure, CPLE_AppDefined,
1936
0
                                "in streamed execution, --format "
1937
0
                                "stream should be used");
1938
0
                    return false;
1939
0
                }
1940
0
            }
1941
0
            else if (step->GeneratesFilesFromUserInput())
1942
0
            {
1943
0
                ReportError(CE_Failure, CPLE_AppDefined,
1944
0
                            "Step '%s' not allowed in stream execution, unless "
1945
0
                            "the GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM "
1946
0
                            "configuration option is set.",
1947
0
                            step->GetName().c_str());
1948
0
                return false;
1949
0
            }
1950
0
        }
1951
0
    }
1952
1953
    // Because of multiprocessing in gdal raster tile, make sure that all
1954
    // steps before it are serialized in a .gdal.json file
1955
0
    if (m_steps.size() >= 2 && m_steps.back()->SupportsInputMultiThreading() &&
1956
0
        m_steps.back()
1957
0
                ->GetArg(GDAL_ARG_NAME_NUM_THREADS_INT_HIDDEN)
1958
0
                ->Get<int>() > 1 &&
1959
0
        !(m_steps.size() == 2 && m_steps[0]->GetName() == "read"))
1960
0
    {
1961
0
        bool ret = false;
1962
0
        auto poSrcDS = m_inputDataset.size() == 1
1963
0
                           ? m_inputDataset[0].GetDatasetRef()
1964
0
                           : nullptr;
1965
0
        if (poSrcDS)
1966
0
        {
1967
0
            auto poSrcDriver = poSrcDS->GetDriver();
1968
0
            if (!poSrcDriver || EQUAL(poSrcDriver->GetDescription(), "MEM"))
1969
0
            {
1970
0
                ReportError(
1971
0
                    CE_Failure, CPLE_AppDefined,
1972
0
                    "Cannot execute this pipeline in parallel mode due to "
1973
0
                    "input dataset being a non-materialized dataset. "
1974
0
                    "Materialize it first, or add '-j 1' to the last step "
1975
0
                    "'tile'");
1976
0
                return false;
1977
0
            }
1978
0
        }
1979
0
        std::string outString;
1980
0
        if (SaveGDALGFile(std::string(), outString))
1981
0
        {
1982
0
            const char *const apszAllowedDrivers[] = {"GDALG", nullptr};
1983
0
            auto poCurDS = GDALDataset::Open(
1984
0
                outString.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1985
0
                apszAllowedDrivers);
1986
0
            if (poCurDS)
1987
0
            {
1988
0
                auto &tileAlg = m_steps.back();
1989
0
                tileAlg->m_inputDataset.clear();
1990
0
                tileAlg->m_inputDataset.resize(1);
1991
0
                tileAlg->m_inputDataset[0].Set(poCurDS);
1992
0
                tileAlg->m_inputDataset[0].SetDatasetOpenedByAlgorithm();
1993
0
                poCurDS->Release();
1994
0
                ret = tileAlg->RunStep(ctxt);
1995
0
                tileAlg->m_inputDataset[0].Close();
1996
0
            }
1997
0
        }
1998
0
        return ret;
1999
0
    }
2000
2001
0
    int countPipelinesWithProgress = 0;
2002
0
    for (size_t i = (m_bExpectReadStep ? 0 : 1); i < m_steps.size(); ++i)
2003
0
    {
2004
0
        const bool bCanHandleNextStep =
2005
0
            i < m_steps.size() - 1 &&
2006
0
            !m_steps[i]->CanHandleNextStep(m_steps[i + 1].get());
2007
0
        if (bCanHandleNextStep &&
2008
0
            !m_steps[i + 1]->IsNativelyStreamingCompatible())
2009
0
            ++countPipelinesWithProgress;
2010
0
        else if (!m_steps[i]->IsNativelyStreamingCompatible())
2011
0
            ++countPipelinesWithProgress;
2012
0
        if (bCanHandleNextStep)
2013
0
            ++i;
2014
0
    }
2015
0
    if (countPipelinesWithProgress == 0)
2016
0
        countPipelinesWithProgress = 1;
2017
2018
0
    bool ret = true;
2019
0
    GDALDataset *poCurDS = nullptr;
2020
0
    int iCurStepWithProgress = 0;
2021
2022
0
    if (!m_bExpectReadStep)
2023
0
    {
2024
0
        CPLAssert(m_inputDataset.size() == 1);
2025
0
        poCurDS = m_inputDataset[0].GetDatasetRef();
2026
0
        CPLAssert(poCurDS);
2027
0
    }
2028
2029
0
    GDALProgressFunc pfnProgress = ctxt.m_pfnProgress;
2030
0
    void *pProgressData = ctxt.m_pProgressData;
2031
0
    if (IsCalledFromCommandLine() && HasOutputString())
2032
0
    {
2033
0
        pfnProgress = nullptr;
2034
0
        pProgressData = nullptr;
2035
0
    }
2036
2037
0
    for (size_t i = 0; i < m_steps.size(); ++i)
2038
0
    {
2039
0
        auto &step = m_steps[i];
2040
2041
0
        if (i > 0 && m_nFirstStepWithUnknownInputType >= 0 &&
2042
0
            i >= static_cast<size_t>(m_nFirstStepWithUnknownInputType) &&
2043
0
            m_steps[i - 1]->GetOutputType() != 0)
2044
0
        {
2045
            // We go here if there was a step such as "external" where at
2046
            // ParseCommandLineArguments() time we could not determine its
2047
            // type of output dataset. Now we must check for steps afterwards
2048
            // such as "write" or "reproject" that exist both as separate raster
2049
            // and vector commands if the one we initially picked is appropriate.
2050
            // If not, then switch to the other type.
2051
0
            if ((step->GetInputType() & m_steps[i - 1]->GetOutputType()) == 0)
2052
0
            {
2053
0
                auto newAlg = GetStepAlg(
2054
0
                    step->GetName() +
2055
0
                    (m_steps[i - 1]->GetOutputType() == GDAL_OF_RASTER
2056
0
                         ? RASTER_SUFFIX
2057
0
                         : VECTOR_SUFFIX));
2058
0
                if (newAlg)
2059
0
                {
2060
0
                    if (newAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
2061
0
                    {
2062
0
                        const auto poSrcTeeAlg =
2063
0
                            dynamic_cast<const GDALTeeStepAlgorithmAbstract *>(
2064
0
                                step.get());
2065
0
                        auto poDstTeeAlg =
2066
0
                            dynamic_cast<GDALTeeStepAlgorithmAbstract *>(
2067
0
                                newAlg.get());
2068
0
                        CPLAssert(poSrcTeeAlg);
2069
0
                        CPLAssert(poDstTeeAlg);
2070
0
                        poDstTeeAlg->CopyFilenameBindingsFrom(poSrcTeeAlg);
2071
0
                    }
2072
2073
0
                    if (i == m_steps.size() - 1 &&
2074
0
                        m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE)
2075
0
                    {
2076
                        // Propagate output parameters set at the pipeline level to the
2077
                        // "write" step
2078
0
                        for (auto &arg : newAlg->GetArgs())
2079
0
                        {
2080
0
                            if (!arg->IsHidden())
2081
0
                            {
2082
0
                                auto pipelineArg =
2083
0
                                    const_cast<
2084
0
                                        const GDALAbstractPipelineAlgorithm *>(
2085
0
                                        this)
2086
0
                                        ->GetArg(arg->GetName());
2087
0
                                if (pipelineArg &&
2088
0
                                    pipelineArg->IsExplicitlySet() &&
2089
0
                                    pipelineArg->GetType() == arg->GetType())
2090
0
                                {
2091
0
                                    arg->SetSkipIfAlreadySet(true);
2092
0
                                    arg->SetFrom(*pipelineArg);
2093
0
                                }
2094
0
                            }
2095
0
                        }
2096
0
                    }
2097
2098
                    // Propagate parameters set on the old algorithm to the new one
2099
0
                    for (auto &arg : step->GetArgs())
2100
0
                    {
2101
0
                        if (!arg->IsHidden() && arg->IsExplicitlySet())
2102
0
                        {
2103
0
                            auto newArg = newAlg->GetArg(arg->GetName());
2104
0
                            if (newArg && newArg->GetType() == arg->GetType())
2105
0
                            {
2106
0
                                newArg->SetSkipIfAlreadySet(true);
2107
0
                                newArg->SetFrom(*arg);
2108
0
                            }
2109
0
                        }
2110
0
                    }
2111
2112
0
                    newAlg->m_inputDatasetCanBeOmitted = true;
2113
0
                    newAlg->m_skipValidationInParseCommandLine = true;
2114
0
                    newAlg->SetCallPath({newAlg->GetName()});
2115
0
                    newAlg->SetReferencePathForRelativePaths(
2116
0
                        GetReferencePathForRelativePaths());
2117
0
                    if (IsCalledFromCommandLine())
2118
0
                        newAlg->SetCalledFromCommandLine();
2119
2120
0
                    step = std::move(newAlg);
2121
0
                }
2122
0
            }
2123
0
            if (!step->ValidateArguments())
2124
0
                return false;
2125
0
        }
2126
2127
0
        if (i > 0 || poCurDS)
2128
0
        {
2129
0
            bool prevStepOutputSetToThisStep = false;
2130
0
            for (auto &arg : step->GetArgs())
2131
0
            {
2132
0
                if (!arg->IsOutput() && (arg->GetType() == GAAT_DATASET ||
2133
0
                                         arg->GetType() == GAAT_DATASET_LIST))
2134
0
                {
2135
0
                    if (arg->GetType() == GAAT_DATASET)
2136
0
                    {
2137
0
                        if ((arg->GetName() == GDAL_ARG_NAME_INPUT &&
2138
0
                             !arg->IsExplicitlySet()) ||
2139
0
                            arg->Get<GDALArgDatasetValue>().GetName() ==
2140
0
                                GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
2141
0
                        {
2142
0
                            auto &val = arg->Get<GDALArgDatasetValue>();
2143
0
                            if (val.GetDatasetRef())
2144
0
                            {
2145
                                // Shouldn't happen
2146
0
                                ReportError(CE_Failure, CPLE_AppDefined,
2147
0
                                            "Step nr %d (%s) has already an "
2148
0
                                            "input dataset for argument %s",
2149
0
                                            static_cast<int>(i),
2150
0
                                            step->GetName().c_str(),
2151
0
                                            arg->GetName().c_str());
2152
0
                                return false;
2153
0
                            }
2154
0
                            prevStepOutputSetToThisStep = true;
2155
0
                            val.Set(poCurDS);
2156
0
                            arg->NotifyValueSet();
2157
0
                        }
2158
0
                    }
2159
0
                    else
2160
0
                    {
2161
0
                        CPLAssert(arg->GetType() == GAAT_DATASET_LIST);
2162
0
                        auto &val =
2163
0
                            arg->Get<std::vector<GDALArgDatasetValue>>();
2164
0
                        if ((arg->GetName() == GDAL_ARG_NAME_INPUT &&
2165
0
                             !arg->IsExplicitlySet()) ||
2166
0
                            (val.size() == 1 &&
2167
0
                             val[0].GetName() ==
2168
0
                                 GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE))
2169
0
                        {
2170
0
                            if (val.size() == 1 && val[0].GetDatasetRef())
2171
0
                            {
2172
                                // Shouldn't happen
2173
0
                                ReportError(CE_Failure, CPLE_AppDefined,
2174
0
                                            "Step nr %d (%s) has already an "
2175
0
                                            "input dataset for argument %s",
2176
0
                                            static_cast<int>(i),
2177
0
                                            step->GetName().c_str(),
2178
0
                                            arg->GetName().c_str());
2179
0
                                return false;
2180
0
                            }
2181
0
                            prevStepOutputSetToThisStep = true;
2182
0
                            val.clear();
2183
0
                            val.resize(1);
2184
0
                            val[0].Set(poCurDS);
2185
0
                            arg->NotifyValueSet();
2186
0
                        }
2187
0
                    }
2188
0
                }
2189
0
            }
2190
0
            if (!prevStepOutputSetToThisStep)
2191
0
            {
2192
0
                ReportError(CE_Failure, CPLE_AppDefined,
2193
0
                            "Step nr %d (%s) does not use input dataset from "
2194
0
                            "previous step",
2195
0
                            static_cast<int>(i), step->GetName().c_str());
2196
0
                return false;
2197
0
            }
2198
0
        }
2199
2200
0
        if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef() &&
2201
0
            !step->OutputDatasetAllowedBeforeRunningStep())
2202
0
        {
2203
            // Shouldn't happen
2204
0
            ReportError(CE_Failure, CPLE_AppDefined,
2205
0
                        "Step nr %d (%s) has already an output dataset",
2206
0
                        static_cast<int>(i), step->GetName().c_str());
2207
0
            return false;
2208
0
        }
2209
2210
0
        const bool bCanHandleNextStep =
2211
0
            i < m_steps.size() - 1 &&
2212
0
            step->CanHandleNextStep(m_steps[i + 1].get());
2213
2214
0
        std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
2215
0
            nullptr, GDALDestroyScaledProgress);
2216
0
        GDALPipelineStepRunContext stepCtxt;
2217
0
        if ((bCanHandleNextStep &&
2218
0
             m_steps[i + 1]->IsNativelyStreamingCompatible()) ||
2219
0
            !step->IsNativelyStreamingCompatible())
2220
0
        {
2221
0
            pScaledData.reset(GDALCreateScaledProgress(
2222
0
                iCurStepWithProgress /
2223
0
                    static_cast<double>(countPipelinesWithProgress),
2224
0
                (iCurStepWithProgress + 1) /
2225
0
                    static_cast<double>(countPipelinesWithProgress),
2226
0
                pfnProgress, pProgressData));
2227
0
            ++iCurStepWithProgress;
2228
0
            stepCtxt.m_pfnProgress = pScaledData ? GDALScaledProgress : nullptr;
2229
0
            stepCtxt.m_pProgressData = pScaledData.get();
2230
0
        }
2231
0
        if (bCanHandleNextStep)
2232
0
        {
2233
0
            stepCtxt.m_poNextUsableStep = m_steps[i + 1].get();
2234
0
        }
2235
0
        if (i + 1 == m_steps.size() && m_stdout &&
2236
0
            step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr)
2237
0
        {
2238
0
            step->m_stdout = true;
2239
0
        }
2240
0
        step->m_inputDatasetCanBeOmitted = false;
2241
0
        step->m_quiet = m_quiet;
2242
0
        if (!step->ValidateArguments() || !step->RunStep(stepCtxt))
2243
0
        {
2244
0
            ret = false;
2245
0
            break;
2246
0
        }
2247
0
        poCurDS = step->m_outputDataset.GetDatasetRef();
2248
0
        if (!poCurDS && !(i + 1 == m_steps.size() &&
2249
0
                          (!step->m_output.empty() ||
2250
0
                           step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr ||
2251
0
                           step->GetOutputType() == 0)))
2252
0
        {
2253
0
            ReportError(CE_Failure, CPLE_AppDefined,
2254
0
                        "Step nr %d (%s) failed to produce an output dataset",
2255
0
                        static_cast<int>(i), step->GetName().c_str());
2256
0
            return false;
2257
0
        }
2258
2259
0
        m_output += step->GetOutputString();
2260
2261
0
        if (bCanHandleNextStep)
2262
0
        {
2263
0
            ++i;
2264
0
        }
2265
0
    }
2266
2267
0
    if (pfnProgress && m_output.empty())
2268
0
        pfnProgress(1.0, "", pProgressData);
2269
2270
0
    if (!m_output.empty())
2271
0
    {
2272
0
        auto outputStringArg = GetArg(GDAL_ARG_NAME_OUTPUT_STRING);
2273
0
        if (outputStringArg && outputStringArg->GetType() == GAAT_STRING)
2274
0
            outputStringArg->Set(m_output);
2275
0
    }
2276
2277
0
    if (ret && poCurDS && !m_outputDataset.GetDatasetRef())
2278
0
    {
2279
0
        m_outputDataset.Set(poCurDS);
2280
0
    }
2281
2282
0
    return ret;
2283
0
}
2284
2285
/************************************************************************/
2286
/*           GDALAbstractPipelineAlgorithm::HasOutputString()           */
2287
/************************************************************************/
2288
2289
bool GDALAbstractPipelineAlgorithm::HasOutputString() const
2290
0
{
2291
0
    for (const auto &step : m_steps)
2292
0
    {
2293
0
        if (step->HasOutputString())
2294
0
            return true;
2295
0
    }
2296
0
    return false;
2297
0
}
2298
2299
/************************************************************************/
2300
/*              GDALAbstractPipelineAlgorithm::Finalize()               */
2301
/************************************************************************/
2302
2303
bool GDALAbstractPipelineAlgorithm::Finalize()
2304
0
{
2305
0
    bool ret = GDALPipelineStepAlgorithm::Finalize();
2306
0
    for (auto &step : m_steps)
2307
0
    {
2308
0
        ret = step->Finalize() && ret;
2309
0
    }
2310
0
    return ret;
2311
0
}
2312
2313
/************************************************************************/
2314
/*           GDALAbstractPipelineAlgorithm::GetUsageAsJSON()            */
2315
/************************************************************************/
2316
2317
std::string GDALAbstractPipelineAlgorithm::GetUsageAsJSON() const
2318
0
{
2319
0
    CPLJSONDocument oDoc;
2320
0
    CPL_IGNORE_RET_VAL(oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON()));
2321
2322
0
    CPLJSONArray jPipelineSteps;
2323
0
    for (const std::string &name : GetStepRegistry().GetNames())
2324
0
    {
2325
0
        auto alg = GetStepAlg(name);
2326
0
        if (!alg->IsHidden())
2327
0
        {
2328
0
            CPLJSONDocument oStepDoc;
2329
0
            CPL_IGNORE_RET_VAL(oStepDoc.LoadMemory(alg->GetUsageAsJSON()));
2330
0
            jPipelineSteps.Add(oStepDoc.GetRoot());
2331
0
        }
2332
0
    }
2333
0
    oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
2334
2335
0
    return oDoc.SaveAsString();
2336
0
}
2337
2338
//! @endcond