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