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