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 145 : for (const auto &stepName : GetStepRegistry().GetNames())
90 : {
91 284 : auto alg = GetStepAlg(stepName);
92 142 : if (alg && alg->CanBeFirstStep() && stepName != "read")
93 : {
94 24 : setFirstStepNames.insert(CPLString(stepName)
95 24 : .replaceAll(RASTER_SUFFIX, "")
96 12 : .replaceAll(VECTOR_SUFFIX, ""));
97 : }
98 : }
99 15 : std::vector<std::string> firstStepNames{"read"};
100 14 : for (const std::string &s : setFirstStepNames)
101 11 : firstStepNames.push_back(s);
102 :
103 3 : std::string msg = "First step should be ";
104 17 : for (size_t i = 0; i < firstStepNames.size(); ++i)
105 : {
106 14 : if (i == firstStepNames.size() - 1)
107 3 : msg += " or ";
108 11 : else if (i > 0)
109 8 : msg += ", ";
110 14 : msg += '\'';
111 14 : msg += firstStepNames[i];
112 14 : 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 2718 : GDALAbstractPipelineAlgorithm::GetStepAlg(const std::string &name) const
223 : {
224 5436 : auto alg = GetStepRegistry().Instantiate(name);
225 : return std::unique_ptr<GDALPipelineStepAlgorithm>(
226 5436 : 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 113 : for (size_t i = (m_bExpectReadStep ? 1 : 0);
1220 243 : !forAutoComplete && i < steps.size(); ++i)
1221 : {
1222 259 : if (!steps[i].alreadyChangedType && !steps[i].isSubAlgorithm &&
1223 259 : GetStepAlg(steps[i].alg->GetName()) == nullptr)
1224 : {
1225 88 : auto newAlg = GetStepAlg(steps[i].alg->GetName() +
1226 : (nLastStepOutputType == GDAL_OF_RASTER
1227 : ? RASTER_SUFFIX
1228 88 : : VECTOR_SUFFIX));
1229 88 : CPLAssert(newAlg);
1230 :
1231 88 : if (steps[i].alg->GetName() ==
1232 : GDALTeeStepAlgorithmAbstract::NAME)
1233 : {
1234 : const auto poSrcTeeAlg =
1235 18 : dynamic_cast<const GDALTeeStepAlgorithmAbstract *>(
1236 36 : steps[i].alg.get());
1237 : auto poDstTeeAlg =
1238 18 : dynamic_cast<GDALTeeStepAlgorithmAbstract *>(
1239 36 : newAlg.get());
1240 18 : CPLAssert(poSrcTeeAlg);
1241 18 : CPLAssert(poDstTeeAlg);
1242 18 : poDstTeeAlg->CopyFilenameBindingsFrom(poSrcTeeAlg);
1243 : }
1244 :
1245 88 : steps[i].alg = std::move(newAlg);
1246 :
1247 159 : if (i == steps.size() - 1 &&
1248 71 : m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE)
1249 : {
1250 70 : SetWriteArgFromPipeline();
1251 : }
1252 :
1253 88 : steps[i].alg->m_inputDatasetCanBeOmitted =
1254 88 : i > 0 || !m_bExpectReadStep;
1255 88 : steps[i].alg->m_skipValidationInParseCommandLine = true;
1256 88 : if (!steps[i].alg->ParseCommandLineArguments(steps[i].args))
1257 1 : return false;
1258 174 : steps[i].alg->SetCallPath({steps[i].alg->GetName()});
1259 87 : steps[i].alg->SetReferencePathForRelativePaths(
1260 : GetReferencePathForRelativePaths());
1261 87 : if (IsCalledFromCommandLine())
1262 24 : steps[i].alg->SetCalledFromCommandLine();
1263 87 : steps[i].alreadyChangedType = true;
1264 : }
1265 88 : else if (i > 0 &&
1266 42 : steps[i].alg->GetInputType() != nLastStepOutputType)
1267 : {
1268 6 : bool emitError = true;
1269 :
1270 : // Check if a dataset argument, which has as value the
1271 : // placeholder value, has the same dataset type as the output
1272 : // of the last step
1273 65 : for (const auto &arg : steps[i].alg->GetArgs())
1274 : {
1275 180 : if (!arg->IsOutput() &&
1276 118 : (arg->GetType() == GAAT_DATASET ||
1277 57 : arg->GetType() == GAAT_DATASET_LIST))
1278 : {
1279 10 : if (arg->GetType() == GAAT_DATASET)
1280 : {
1281 4 : if (arg->Get<GDALArgDatasetValue>().GetName() ==
1282 : GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
1283 : {
1284 3 : if ((arg->GetDatasetType() &
1285 3 : nLastStepOutputType) != 0)
1286 : {
1287 3 : emitError = false;
1288 3 : break;
1289 : }
1290 : }
1291 : }
1292 : else
1293 : {
1294 6 : CPLAssert(arg->GetType() == GAAT_DATASET_LIST);
1295 : auto &val =
1296 6 : arg->Get<std::vector<GDALArgDatasetValue>>();
1297 9 : if (val.size() == 1 &&
1298 3 : val[0].GetName() ==
1299 : GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
1300 : {
1301 1 : if ((arg->GetDatasetType() &
1302 1 : nLastStepOutputType) != 0)
1303 : {
1304 0 : emitError = false;
1305 0 : break;
1306 : }
1307 : }
1308 : }
1309 : }
1310 : }
1311 6 : if (emitError)
1312 : {
1313 14 : ReportError(CE_Failure, CPLE_AppDefined,
1314 : "Step '%s' expects a %s input dataset, but "
1315 : "previous step '%s' "
1316 : "generates a %s output dataset",
1317 3 : steps[i].alg->GetName().c_str(),
1318 3 : steps[i].alg->GetInputType() == GDAL_OF_RASTER
1319 : ? "raster"
1320 1 : : steps[i].alg->GetInputType() == GDAL_OF_VECTOR
1321 1 : ? "vector"
1322 : : "unknown",
1323 3 : steps[i - 1].alg->GetName().c_str(),
1324 : nLastStepOutputType == GDAL_OF_RASTER ? "raster"
1325 : : nLastStepOutputType == GDAL_OF_VECTOR
1326 2 : ? "vector"
1327 : : "unknown");
1328 3 : return false;
1329 : }
1330 : }
1331 130 : nLastStepOutputType = steps[i].alg->GetOutputType();
1332 : }
1333 : }
1334 :
1335 895 : for (const auto &step : steps)
1336 : {
1337 629 : if (!step.alg->ValidateArguments() && !forAutoComplete)
1338 9 : return false;
1339 : }
1340 :
1341 878 : for (auto &step : steps)
1342 612 : m_steps.push_back(std::move(step.alg));
1343 :
1344 266 : return true;
1345 : }
1346 :
1347 : /************************************************************************/
1348 : /* GDALAbstractPipelineAlgorithm::BuildNestedPipeline() */
1349 : /************************************************************************/
1350 :
1351 33 : std::string GDALAbstractPipelineAlgorithm::BuildNestedPipeline(
1352 : GDALPipelineStepAlgorithm *curAlg,
1353 : std::vector<std::string> &nestedPipelineArgs, bool forAutoComplete)
1354 : {
1355 33 : std::string datasetNameOut;
1356 33 : CPLAssert(curAlg);
1357 :
1358 66 : auto nestedPipeline = CreateNestedPipeline();
1359 33 : if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
1360 25 : nestedPipeline->m_bExpectReadStep = false;
1361 : else
1362 8 : nestedPipeline->m_eLastStepAsWrite = StepConstraint::CAN_NOT_BE;
1363 33 : nestedPipeline->m_executionForStreamOutput = m_executionForStreamOutput;
1364 33 : nestedPipeline->SetReferencePathForRelativePaths(
1365 : GetReferencePathForRelativePaths());
1366 :
1367 66 : std::string argsStr = OPEN_NESTED_PIPELINE;
1368 125 : for (const std::string &str : nestedPipelineArgs)
1369 : {
1370 92 : argsStr += ' ';
1371 92 : argsStr += GDALAlgorithmArg::GetEscapedString(str);
1372 : }
1373 33 : argsStr += ' ';
1374 33 : argsStr += CLOSE_NESTED_PIPELINE;
1375 :
1376 33 : if (curAlg->GetName() != GDALTeeStepAlgorithmAbstract::NAME)
1377 : {
1378 8 : if (!nestedPipeline->ParseCommandLineArguments(nestedPipelineArgs,
1379 12 : forAutoComplete) ||
1380 4 : (!forAutoComplete && !nestedPipeline->Run()))
1381 : {
1382 5 : return datasetNameOut;
1383 : }
1384 3 : auto poDS = nestedPipeline->GetOutputDataset().GetDatasetRef();
1385 3 : if (!poDS)
1386 : {
1387 : // That shouldn't happen normally for well-behaved algorithms, but
1388 : // it doesn't hurt checking.
1389 0 : ReportError(CE_Failure, CPLE_AppDefined,
1390 : "Nested pipeline does not generate an output dataset");
1391 0 : return datasetNameOut;
1392 : }
1393 : datasetNameOut =
1394 3 : CPLSPrintf("$$nested_pipeline_%p$$", nestedPipeline.get());
1395 3 : curAlg->m_oMapDatasetNameToDataset[datasetNameOut] = poDS;
1396 :
1397 3 : poDS->SetDescription(argsStr.c_str());
1398 : }
1399 :
1400 28 : m_apoNestedPipelines.emplace_back(std::move(nestedPipeline));
1401 :
1402 28 : if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
1403 : {
1404 25 : auto teeAlg = dynamic_cast<GDALTeeStepAlgorithmAbstract *>(curAlg);
1405 25 : if (teeAlg)
1406 : {
1407 25 : datasetNameOut = std::move(argsStr);
1408 25 : if (!teeAlg->BindFilename(datasetNameOut,
1409 25 : m_apoNestedPipelines.back().get(),
1410 : nestedPipelineArgs))
1411 : {
1412 1 : ReportError(CE_Failure, CPLE_AppDefined,
1413 : "Another identical nested pipeline exists");
1414 1 : datasetNameOut.clear();
1415 : }
1416 : }
1417 : }
1418 :
1419 28 : nestedPipelineArgs.clear();
1420 :
1421 28 : return datasetNameOut;
1422 : }
1423 :
1424 : /************************************************************************/
1425 : /* GDALAbstractPipelineAlgorithm::GetAutoComplete() */
1426 : /************************************************************************/
1427 :
1428 : std::vector<std::string>
1429 49 : GDALAbstractPipelineAlgorithm::GetAutoComplete(std::vector<std::string> &args,
1430 : bool lastWordIsComplete,
1431 : bool showAllOptions)
1432 : {
1433 : {
1434 98 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1435 49 : ParseCommandLineArguments(args, /*forAutoComplete=*/true);
1436 : }
1437 : VSIStatBufL sStat;
1438 55 : if (!m_pipeline.empty() && VSIStatL(m_pipeline.c_str(), &sStat) == 0 &&
1439 55 : !m_steps.empty() && !args.empty())
1440 : {
1441 12 : std::map<std::string, std::vector<GDALAlgorithm *>> mapSteps;
1442 26 : for (const auto &step : m_steps)
1443 : {
1444 20 : mapSteps[step->GetName()].push_back(step.get());
1445 : }
1446 :
1447 12 : std::vector<std::string> ret;
1448 6 : const auto &lastArg = args.back();
1449 18 : if (!lastArg.empty() && lastArg[0] == '-' &&
1450 18 : lastArg.find('=') == std::string::npos && !lastWordIsComplete)
1451 : {
1452 13 : for (const auto &step : m_steps)
1453 : {
1454 : const int iterCount =
1455 10 : static_cast<int>(mapSteps[step->GetName()].size());
1456 22 : for (int i = 0; i < iterCount; ++i)
1457 : {
1458 158 : for (const auto &arg : step->GetArgs())
1459 : {
1460 271 : if (!arg->IsHiddenForCLI() &&
1461 125 : arg->GetCategory() != GAAC_COMMON)
1462 : {
1463 178 : std::string s = std::string("--");
1464 178 : if (!((step->GetName() == "read" &&
1465 11 : IsReadSpecificArgument(
1466 11 : arg->GetName().c_str())) ||
1467 78 : (step->GetName() == "write" &&
1468 31 : IsWriteSpecificArgument(
1469 31 : arg->GetName().c_str()))))
1470 : {
1471 55 : s += step->GetName();
1472 55 : if (iterCount > 1)
1473 : {
1474 32 : s += '[';
1475 32 : s += std::to_string(i);
1476 32 : s += ']';
1477 : }
1478 55 : s += '.';
1479 : }
1480 89 : s += arg->GetName();
1481 89 : if (arg->GetType() == GAAT_BOOLEAN)
1482 21 : ret.push_back(std::move(s));
1483 : else
1484 68 : ret.push_back(s + "=");
1485 : }
1486 : }
1487 : }
1488 : }
1489 : }
1490 6 : else if (cpl::starts_with(lastArg, "--") &&
1491 6 : lastArg.find('=') != std::string::npos && !lastWordIsComplete)
1492 : {
1493 3 : const auto nDotPos = lastArg.find('.');
1494 6 : std::string stepName;
1495 6 : std::string argName;
1496 3 : int idx = 0;
1497 3 : if (nDotPos != std::string::npos)
1498 : {
1499 1 : stepName = lastArg.substr(strlen("--"), nDotPos - strlen("--"));
1500 1 : const auto nBracketPos = stepName.find('[');
1501 1 : if (nBracketPos != std::string::npos)
1502 : {
1503 1 : idx = atoi(stepName.c_str() + nBracketPos + 1);
1504 1 : stepName.resize(nBracketPos);
1505 : }
1506 1 : argName = "--" + lastArg.substr(nDotPos + 1);
1507 : }
1508 : else
1509 : {
1510 2 : argName = lastArg;
1511 7 : for (const char *prefix : apszReadParametersPrefixOmitted)
1512 : {
1513 6 : if (cpl::starts_with(lastArg.substr(strlen("--")),
1514 12 : std::string(prefix) + "="))
1515 : {
1516 1 : stepName = "read";
1517 1 : break;
1518 : }
1519 : }
1520 :
1521 13 : for (const char *prefix : apszWriteParametersPrefixOmitted)
1522 : {
1523 12 : if (cpl::starts_with(lastArg.substr(strlen("--")),
1524 24 : std::string(prefix) + "="))
1525 : {
1526 1 : stepName = "write";
1527 1 : break;
1528 : }
1529 : }
1530 : }
1531 :
1532 3 : auto iter = mapSteps.find(stepName);
1533 6 : if (iter != mapSteps.end() && idx >= 0 &&
1534 3 : static_cast<size_t>(idx) < iter->second.size())
1535 : {
1536 3 : auto &step = iter->second[idx];
1537 3 : std::vector<std::string> subArgs;
1538 33 : for (const auto &arg : step->GetArgs())
1539 : {
1540 60 : std::string strArg;
1541 34 : if (arg->IsExplicitlySet() &&
1542 4 : arg->Serialize(strArg, /* absolutePath=*/false))
1543 : {
1544 4 : subArgs.push_back(std::move(strArg));
1545 : }
1546 : }
1547 3 : subArgs.push_back(std::move(argName));
1548 3 : ret = step->GetAutoComplete(subArgs, lastWordIsComplete,
1549 3 : showAllOptions);
1550 : }
1551 : }
1552 6 : return ret;
1553 : }
1554 : else
1555 : {
1556 86 : std::vector<std::string> ret;
1557 86 : std::set<std::string> setSuggestions;
1558 43 : if (args.size() <= 1)
1559 : {
1560 366 : for (const std::string &name : GetStepRegistry().GetNames())
1561 : {
1562 360 : auto alg = GetStepRegistry().Instantiate(name);
1563 : auto stepAlg =
1564 360 : dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
1565 360 : if (stepAlg && stepAlg->CanBeFirstStep())
1566 : {
1567 : std::string suggestionName =
1568 39 : CPLString(name)
1569 78 : .replaceAll(RASTER_SUFFIX, "")
1570 78 : .replaceAll(VECTOR_SUFFIX, "");
1571 39 : if (!cpl::contains(setSuggestions, suggestionName))
1572 : {
1573 37 : if (!args.empty() && suggestionName == args[0])
1574 3 : return {};
1575 55 : if (args.empty() ||
1576 21 : cpl::starts_with(suggestionName, args[0]))
1577 : {
1578 16 : setSuggestions.insert(suggestionName);
1579 16 : ret.push_back(std::move(suggestionName));
1580 : }
1581 : }
1582 : }
1583 : }
1584 : }
1585 : else
1586 : {
1587 34 : int nDatasetType = GetInputType();
1588 34 : constexpr int MIXED_TYPE = GDAL_OF_RASTER | GDAL_OF_VECTOR;
1589 34 : const bool isMixedTypePipeline = nDatasetType == MIXED_TYPE;
1590 34 : std::string lastStep = args[0];
1591 34 : std::vector<std::string> lastArgs;
1592 34 : bool firstStep = true;
1593 34 : bool foundSlowStep = false;
1594 121 : for (size_t i = 1; i < args.size(); ++i)
1595 : {
1596 59 : if (firstStep && isMixedTypePipeline &&
1597 167 : nDatasetType == MIXED_TYPE && !args[i].empty() &&
1598 21 : args[i][0] != '-')
1599 : {
1600 38 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1601 : auto poDS = std::unique_ptr<GDALDataset>(
1602 38 : GDALDataset::Open(args[i].c_str()));
1603 24 : if (poDS && poDS->GetLayerCount() > 0 &&
1604 5 : poDS->GetRasterCount() == 0)
1605 : {
1606 5 : nDatasetType = GDAL_OF_VECTOR;
1607 : }
1608 17 : else if (poDS && poDS->GetLayerCount() == 0 &&
1609 3 : (poDS->GetRasterCount() > 0 ||
1610 0 : poDS->GetMetadata("SUBDATASETS") != nullptr))
1611 : {
1612 3 : nDatasetType = GDAL_OF_RASTER;
1613 : }
1614 : }
1615 87 : lastArgs.push_back(args[i]);
1616 87 : if (i + 1 < args.size() && args[i] == "!")
1617 : {
1618 25 : firstStep = false;
1619 25 : ++i;
1620 25 : lastArgs.clear();
1621 25 : lastStep = args[i];
1622 50 : auto curAlg = GetStepAlg(lastStep);
1623 25 : if (isMixedTypePipeline && !curAlg)
1624 : {
1625 10 : if (nDatasetType == GDAL_OF_RASTER)
1626 1 : curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
1627 9 : else if (nDatasetType == GDAL_OF_VECTOR)
1628 3 : curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
1629 : }
1630 25 : if (curAlg)
1631 : {
1632 16 : foundSlowStep =
1633 30 : foundSlowStep ||
1634 14 : !curAlg->IsNativelyStreamingCompatible();
1635 16 : nDatasetType = curAlg->GetOutputType();
1636 : }
1637 : }
1638 : }
1639 :
1640 64 : if (args.back() == "!" ||
1641 64 : (args[args.size() - 2] == "!" && !GetStepAlg(args.back()) &&
1642 37 : !GetStepAlg(args.back() + RASTER_SUFFIX) &&
1643 37 : !GetStepAlg(args.back() + VECTOR_SUFFIX)))
1644 : {
1645 511 : for (const std::string &name : GetStepRegistry().GetNames())
1646 : {
1647 501 : auto alg = GetStepRegistry().Instantiate(name);
1648 : auto stepAlg =
1649 501 : dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
1650 501 : if (stepAlg && isMixedTypePipeline &&
1651 1002 : nDatasetType != MIXED_TYPE &&
1652 150 : stepAlg->GetInputType() != nDatasetType)
1653 : {
1654 75 : continue;
1655 : }
1656 426 : if (stepAlg && !stepAlg->CanBeFirstStep())
1657 : {
1658 : std::string suggestionName =
1659 384 : CPLString(name)
1660 768 : .replaceAll(RASTER_SUFFIX, "")
1661 1152 : .replaceAll(VECTOR_SUFFIX, "");
1662 384 : if (!cpl::contains(setSuggestions, suggestionName))
1663 : {
1664 366 : setSuggestions.insert(suggestionName);
1665 366 : ret.push_back(std::move(suggestionName));
1666 : }
1667 : }
1668 : }
1669 : }
1670 : else
1671 : {
1672 24 : if (!foundSlowStep)
1673 : {
1674 : // Try to run the pipeline so that the last step gets its
1675 : // input dataset.
1676 20 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1677 20 : GDALPipelineStepRunContext ctxt;
1678 20 : RunStep(ctxt);
1679 32 : if (!m_steps.empty() &&
1680 12 : m_steps.back()->GetName() == lastStep)
1681 : {
1682 12 : return m_steps.back()->GetAutoComplete(
1683 : lastArgs, lastWordIsComplete,
1684 12 : /* showAllOptions = */ false);
1685 : }
1686 : }
1687 :
1688 24 : auto curAlg = GetStepAlg(lastStep);
1689 12 : if (isMixedTypePipeline && !curAlg)
1690 : {
1691 3 : if (nDatasetType == GDAL_OF_RASTER)
1692 0 : curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
1693 3 : else if (nDatasetType == GDAL_OF_VECTOR)
1694 0 : curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
1695 : else
1696 : {
1697 6 : for (const char *suffix :
1698 9 : {RASTER_SUFFIX, VECTOR_SUFFIX})
1699 : {
1700 6 : curAlg = GetStepAlg(lastStep + suffix);
1701 6 : if (curAlg)
1702 : {
1703 39 : for (const auto &v : curAlg->GetAutoComplete(
1704 : lastArgs, lastWordIsComplete,
1705 72 : /* showAllOptions = */ false))
1706 : {
1707 33 : if (!cpl::contains(setSuggestions, v))
1708 : {
1709 25 : setSuggestions.insert(v);
1710 25 : ret.push_back(std::move(v));
1711 : }
1712 : }
1713 : }
1714 : }
1715 3 : curAlg.reset();
1716 : }
1717 : }
1718 12 : if (curAlg)
1719 : {
1720 18 : ret = curAlg->GetAutoComplete(lastArgs, lastWordIsComplete,
1721 9 : /* showAllOptions = */ false);
1722 : }
1723 : }
1724 : }
1725 28 : return ret;
1726 : }
1727 : }
1728 :
1729 : /************************************************************************/
1730 : /* GDALAbstractPipelineAlgorithm::SaveGDALGFile() */
1731 : /************************************************************************/
1732 :
1733 12 : bool GDALAbstractPipelineAlgorithm::SaveGDALGFile(
1734 : const std::string &outFilename, std::string &outString) const
1735 : {
1736 24 : std::string osCommandLine;
1737 :
1738 44 : for (const auto &path : GDALAlgorithm::m_callPath)
1739 : {
1740 32 : if (!osCommandLine.empty())
1741 20 : osCommandLine += ' ';
1742 32 : osCommandLine += path;
1743 : }
1744 :
1745 : // Do not include the last step
1746 32 : for (size_t i = 0; i + 1 < m_steps.size(); ++i)
1747 : {
1748 21 : const auto &step = m_steps[i];
1749 21 : if (!step->IsNativelyStreamingCompatible())
1750 : {
1751 3 : GDALAlgorithm::ReportError(
1752 : CE_Warning, CPLE_AppDefined,
1753 : "Step %s is not natively streaming compatible, and "
1754 : "may cause significant processing time at opening",
1755 3 : step->GDALAlgorithm::GetName().c_str());
1756 : }
1757 :
1758 21 : if (i > 0)
1759 9 : osCommandLine += " !";
1760 42 : for (const auto &path : step->GDALAlgorithm::m_callPath)
1761 : {
1762 21 : if (!osCommandLine.empty())
1763 21 : osCommandLine += ' ';
1764 21 : osCommandLine += path;
1765 : }
1766 :
1767 228 : for (const auto &arg : step->GetArgs())
1768 : {
1769 208 : if (arg->IsExplicitlySet())
1770 : {
1771 20 : osCommandLine += ' ';
1772 20 : std::string strArg;
1773 20 : if (!arg->Serialize(strArg, /* absolutePath=*/false))
1774 : {
1775 1 : CPLError(CE_Failure, CPLE_AppDefined,
1776 : "Cannot serialize argument %s",
1777 1 : arg->GetName().c_str());
1778 1 : return false;
1779 : }
1780 19 : osCommandLine += strArg;
1781 : }
1782 : }
1783 : }
1784 :
1785 11 : return GDALAlgorithm::SaveGDALG(outFilename, outString, osCommandLine);
1786 : }
1787 :
1788 : /************************************************************************/
1789 : /* GDALAbstractPipelineAlgorithm::RunStep() */
1790 : /************************************************************************/
1791 :
1792 297 : bool GDALAbstractPipelineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
1793 : {
1794 297 : if (m_stepOnWhichHelpIsRequested)
1795 : {
1796 4 : printf(
1797 : "%s",
1798 8 : m_stepOnWhichHelpIsRequested->GetUsageForCLI(false).c_str()); /*ok*/
1799 4 : return true;
1800 : }
1801 :
1802 293 : if (m_steps.empty())
1803 : {
1804 : // If invoked programmatically, not from the command line.
1805 :
1806 157 : if (m_pipeline.empty())
1807 : {
1808 10 : ReportError(CE_Failure, CPLE_AppDefined,
1809 : "'pipeline' argument not set");
1810 36 : return false;
1811 : }
1812 :
1813 147 : const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
1814 147 : if (!ParseCommandLineArguments(aosTokens))
1815 26 : return false;
1816 : }
1817 :
1818 : // Handle output to GDALG file
1819 257 : if (!m_steps.empty() && m_steps.back()->GetName() == "write")
1820 : {
1821 157 : if (m_steps.back()->IsGDALGOutput())
1822 : {
1823 11 : const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
1824 : const auto &filename =
1825 11 : outputArg->Get<GDALArgDatasetValue>().GetName();
1826 11 : const char *pszType = "";
1827 11 : if (GDALDoesFileOrDatasetExist(filename.c_str(), &pszType))
1828 : {
1829 : const auto overwriteArg =
1830 1 : m_steps.back()->GetArg(GDAL_ARG_NAME_OVERWRITE);
1831 1 : if (overwriteArg && overwriteArg->GetType() == GAAT_BOOLEAN)
1832 : {
1833 1 : if (!overwriteArg->Get<bool>())
1834 : {
1835 0 : CPLError(CE_Failure, CPLE_AppDefined,
1836 : "%s '%s' already exists. Specify the "
1837 : "--overwrite option to overwrite it.",
1838 : pszType, filename.c_str());
1839 0 : return false;
1840 : }
1841 : }
1842 : }
1843 :
1844 22 : std::string outStringUnused;
1845 11 : return SaveGDALGFile(filename, outStringUnused);
1846 : }
1847 :
1848 : const auto outputFormatArg =
1849 146 : m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT);
1850 146 : const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
1851 292 : if (outputArg && outputArg->GetType() == GAAT_DATASET &&
1852 146 : outputArg->IsExplicitlySet())
1853 : {
1854 146 : const auto &outputFile = outputArg->Get<GDALArgDatasetValue>();
1855 : bool isVRTOutput;
1856 292 : if (outputFormatArg && outputFormatArg->GetType() == GAAT_STRING &&
1857 146 : outputFormatArg->IsExplicitlySet())
1858 : {
1859 54 : const auto &val = outputFormatArg->Get<std::string>();
1860 54 : isVRTOutput = EQUAL(val.c_str(), "vrt");
1861 : }
1862 : else
1863 : {
1864 92 : isVRTOutput = EQUAL(
1865 : CPLGetExtensionSafe(outputFile.GetName().c_str()).c_str(),
1866 : "vrt");
1867 : }
1868 159 : if (isVRTOutput && !outputFile.GetName().empty() &&
1869 13 : m_steps.size() > 3)
1870 : {
1871 1 : ReportError(
1872 : CE_Failure, CPLE_NotSupported,
1873 : "VRT output is not supported when there are more than 3 "
1874 : "steps. Consider using the GDALG driver (files with "
1875 : ".gdalg.json extension)");
1876 1 : return false;
1877 : }
1878 145 : if (isVRTOutput)
1879 : {
1880 24 : for (const auto &step : m_steps)
1881 : {
1882 24 : if (!step->m_outputVRTCompatible)
1883 : {
1884 12 : step->ReportError(
1885 : CE_Failure, CPLE_NotSupported,
1886 : "VRT output is not supported. Consider using the "
1887 : "GDALG driver instead (files with .gdalg.json "
1888 : "extension)");
1889 12 : return false;
1890 : }
1891 : }
1892 : }
1893 : }
1894 : }
1895 :
1896 274 : if (m_executionForStreamOutput &&
1897 41 : !CPLTestBool(
1898 : CPLGetConfigOption("GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM", "NO")))
1899 : {
1900 : // For security reasons, to avoid that reading a .gdalg.json file writes
1901 : // a file on the file system.
1902 91 : for (const auto &step : m_steps)
1903 : {
1904 57 : if (step->GetName() == "write")
1905 : {
1906 3 : if (!EQUAL(step->m_format.c_str(), "stream"))
1907 : {
1908 2 : ReportError(CE_Failure, CPLE_AppDefined,
1909 : "in streamed execution, --format "
1910 : "stream should be used");
1911 5 : return false;
1912 : }
1913 : }
1914 54 : else if (step->GeneratesFilesFromUserInput())
1915 : {
1916 3 : ReportError(CE_Failure, CPLE_AppDefined,
1917 : "Step '%s' not allowed in stream execution, unless "
1918 : "the GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM "
1919 : "configuration option is set.",
1920 3 : step->GetName().c_str());
1921 3 : return false;
1922 : }
1923 : }
1924 : }
1925 :
1926 : // Because of multiprocessing in gdal raster tile, make sure that all
1927 : // steps before it are serialized in a .gdal.json file
1928 191 : if (m_steps.size() >= 2 && m_steps.back()->SupportsInputMultiThreading() &&
1929 3 : m_steps.back()
1930 3 : ->GetArg(GDAL_ARG_NAME_NUM_THREADS_INT_HIDDEN)
1931 425 : ->Get<int>() > 1 &&
1932 6 : !(m_steps.size() == 2 && m_steps[0]->GetName() == "read"))
1933 : {
1934 2 : bool ret = false;
1935 2 : auto poSrcDS = m_inputDataset.size() == 1
1936 2 : ? m_inputDataset[0].GetDatasetRef()
1937 2 : : nullptr;
1938 2 : if (poSrcDS)
1939 : {
1940 1 : auto poSrcDriver = poSrcDS->GetDriver();
1941 1 : if (!poSrcDriver || EQUAL(poSrcDriver->GetDescription(), "MEM"))
1942 : {
1943 1 : ReportError(
1944 : CE_Failure, CPLE_AppDefined,
1945 : "Cannot execute this pipeline in parallel mode due to "
1946 : "input dataset being a non-materialized dataset. "
1947 : "Materialize it first, or add '-j 1' to the last step "
1948 : "'tile'");
1949 1 : return false;
1950 : }
1951 : }
1952 1 : std::string outString;
1953 1 : if (SaveGDALGFile(std::string(), outString))
1954 : {
1955 1 : const char *const apszAllowedDrivers[] = {"GDALG", nullptr};
1956 1 : auto poCurDS = GDALDataset::Open(
1957 : outString.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1958 : apszAllowedDrivers);
1959 1 : if (poCurDS)
1960 : {
1961 1 : auto &tileAlg = m_steps.back();
1962 1 : tileAlg->m_inputDataset.clear();
1963 1 : tileAlg->m_inputDataset.resize(1);
1964 1 : tileAlg->m_inputDataset[0].Set(poCurDS);
1965 1 : tileAlg->m_inputDataset[0].SetDatasetOpenedByAlgorithm();
1966 1 : poCurDS->Release();
1967 1 : ret = tileAlg->RunStep(ctxt);
1968 1 : tileAlg->m_inputDataset[0].Close();
1969 : }
1970 : }
1971 1 : return ret;
1972 : }
1973 :
1974 226 : int countPipelinesWithProgress = 0;
1975 514 : for (size_t i = (m_bExpectReadStep ? 0 : 1); i < m_steps.size(); ++i)
1976 : {
1977 : const bool bCanHandleNextStep =
1978 484 : i < m_steps.size() - 1 &&
1979 196 : !m_steps[i]->CanHandleNextStep(m_steps[i + 1].get());
1980 484 : if (bCanHandleNextStep &&
1981 196 : !m_steps[i + 1]->IsNativelyStreamingCompatible())
1982 117 : ++countPipelinesWithProgress;
1983 171 : else if (!m_steps[i]->IsNativelyStreamingCompatible())
1984 65 : ++countPipelinesWithProgress;
1985 288 : if (bCanHandleNextStep)
1986 196 : ++i;
1987 : }
1988 226 : if (countPipelinesWithProgress == 0)
1989 71 : countPipelinesWithProgress = 1;
1990 :
1991 226 : bool ret = true;
1992 226 : GDALDataset *poCurDS = nullptr;
1993 226 : int iCurStepWithProgress = 0;
1994 :
1995 226 : if (!m_bExpectReadStep)
1996 : {
1997 15 : CPLAssert(m_inputDataset.size() == 1);
1998 15 : poCurDS = m_inputDataset[0].GetDatasetRef();
1999 15 : CPLAssert(poCurDS);
2000 : }
2001 :
2002 226 : GDALProgressFunc pfnProgress = ctxt.m_pfnProgress;
2003 226 : void *pProgressData = ctxt.m_pProgressData;
2004 226 : if (IsCalledFromCommandLine() && HasOutputString())
2005 : {
2006 6 : pfnProgress = nullptr;
2007 6 : pProgressData = nullptr;
2008 : }
2009 :
2010 681 : for (size_t i = 0; i < m_steps.size(); ++i)
2011 : {
2012 481 : auto &step = m_steps[i];
2013 481 : if (i > 0 || poCurDS)
2014 : {
2015 270 : bool prevStepOutputSetToThisStep = false;
2016 3895 : for (auto &arg : step->GetArgs())
2017 : {
2018 7055 : if (!arg->IsOutput() && (arg->GetType() == GAAT_DATASET ||
2019 3430 : arg->GetType() == GAAT_DATASET_LIST))
2020 : {
2021 319 : if (arg->GetType() == GAAT_DATASET)
2022 : {
2023 31 : if ((arg->GetName() == GDAL_ARG_NAME_INPUT &&
2024 62 : !arg->IsExplicitlySet()) ||
2025 31 : arg->Get<GDALArgDatasetValue>().GetName() ==
2026 : GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
2027 : {
2028 5 : auto &val = arg->Get<GDALArgDatasetValue>();
2029 5 : if (val.GetDatasetRef())
2030 : {
2031 : // Shouldn't happen
2032 0 : ReportError(CE_Failure, CPLE_AppDefined,
2033 : "Step nr %d (%s) has already an "
2034 : "input dataset for argument %s",
2035 : static_cast<int>(i),
2036 0 : step->GetName().c_str(),
2037 0 : arg->GetName().c_str());
2038 0 : return false;
2039 : }
2040 5 : prevStepOutputSetToThisStep = true;
2041 5 : val.Set(poCurDS);
2042 5 : arg->NotifyValueSet();
2043 : }
2044 : }
2045 : else
2046 : {
2047 288 : CPLAssert(arg->GetType() == GAAT_DATASET_LIST);
2048 : auto &val =
2049 288 : arg->Get<std::vector<GDALArgDatasetValue>>();
2050 288 : if ((arg->GetName() == GDAL_ARG_NAME_INPUT &&
2051 330 : !arg->IsExplicitlySet()) ||
2052 42 : (val.size() == 1 &&
2053 18 : val[0].GetName() ==
2054 : GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE))
2055 : {
2056 267 : if (val.size() == 1 && val[0].GetDatasetRef())
2057 : {
2058 : // Shouldn't happen
2059 0 : ReportError(CE_Failure, CPLE_AppDefined,
2060 : "Step nr %d (%s) has already an "
2061 : "input dataset for argument %s",
2062 : static_cast<int>(i),
2063 0 : step->GetName().c_str(),
2064 0 : arg->GetName().c_str());
2065 0 : return false;
2066 : }
2067 267 : prevStepOutputSetToThisStep = true;
2068 267 : val.clear();
2069 267 : val.resize(1);
2070 267 : val[0].Set(poCurDS);
2071 267 : arg->NotifyValueSet();
2072 : }
2073 : }
2074 : }
2075 : }
2076 270 : if (!prevStepOutputSetToThisStep)
2077 : {
2078 0 : ReportError(CE_Failure, CPLE_AppDefined,
2079 : "Step nr %d (%s) does not use input dataset from "
2080 : "previous step",
2081 0 : static_cast<int>(i), step->GetName().c_str());
2082 0 : return false;
2083 : }
2084 : }
2085 :
2086 485 : if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef() &&
2087 4 : !step->OutputDatasetAllowedBeforeRunningStep())
2088 : {
2089 : // Shouldn't happen
2090 2 : ReportError(CE_Failure, CPLE_AppDefined,
2091 : "Step nr %d (%s) has already an output dataset",
2092 2 : static_cast<int>(i), step->GetName().c_str());
2093 2 : return false;
2094 : }
2095 :
2096 : const bool bCanHandleNextStep =
2097 750 : i < m_steps.size() - 1 &&
2098 271 : step->CanHandleNextStep(m_steps[i + 1].get());
2099 :
2100 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
2101 479 : nullptr, GDALDestroyScaledProgress);
2102 479 : GDALPipelineStepRunContext stepCtxt;
2103 486 : if ((bCanHandleNextStep &&
2104 958 : m_steps[i + 1]->IsNativelyStreamingCompatible()) ||
2105 479 : !step->IsNativelyStreamingCompatible())
2106 : {
2107 178 : pScaledData.reset(GDALCreateScaledProgress(
2108 : iCurStepWithProgress /
2109 178 : static_cast<double>(countPipelinesWithProgress),
2110 178 : (iCurStepWithProgress + 1) /
2111 178 : static_cast<double>(countPipelinesWithProgress),
2112 : pfnProgress, pProgressData));
2113 178 : ++iCurStepWithProgress;
2114 178 : stepCtxt.m_pfnProgress = pScaledData ? GDALScaledProgress : nullptr;
2115 178 : stepCtxt.m_pProgressData = pScaledData.get();
2116 : }
2117 479 : if (bCanHandleNextStep)
2118 : {
2119 7 : stepCtxt.m_poNextUsableStep = m_steps[i + 1].get();
2120 : }
2121 496 : if (i + 1 == m_steps.size() && m_stdout &&
2122 496 : step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr)
2123 : {
2124 4 : step->m_stdout = true;
2125 : }
2126 479 : step->m_inputDatasetCanBeOmitted = false;
2127 479 : if (!step->ValidateArguments() || !step->RunStep(stepCtxt))
2128 : {
2129 24 : ret = false;
2130 24 : break;
2131 : }
2132 455 : poCurDS = step->m_outputDataset.GetDatasetRef();
2133 483 : if (!poCurDS && !(i + 1 == m_steps.size() &&
2134 21 : (!step->m_output.empty() ||
2135 462 : step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr ||
2136 3 : step->GetName() == "compare")))
2137 : {
2138 0 : ReportError(CE_Failure, CPLE_AppDefined,
2139 : "Step nr %d (%s) failed to produce an output dataset",
2140 0 : static_cast<int>(i), step->GetName().c_str());
2141 0 : return false;
2142 : }
2143 :
2144 455 : m_output += step->GetOutputString();
2145 :
2146 455 : if (bCanHandleNextStep)
2147 : {
2148 7 : ++i;
2149 : }
2150 : }
2151 :
2152 224 : if (pfnProgress && m_output.empty())
2153 16 : pfnProgress(1.0, "", pProgressData);
2154 :
2155 224 : if (!m_output.empty())
2156 : {
2157 17 : auto outputStringArg = GetArg(GDAL_ARG_NAME_OUTPUT_STRING);
2158 17 : if (outputStringArg && outputStringArg->GetType() == GAAT_STRING)
2159 17 : outputStringArg->Set(m_output);
2160 : }
2161 :
2162 224 : if (ret && poCurDS && !m_outputDataset.GetDatasetRef())
2163 : {
2164 178 : m_outputDataset.Set(poCurDS);
2165 : }
2166 :
2167 224 : return ret;
2168 : }
2169 :
2170 : /************************************************************************/
2171 : /* GDALAbstractPipelineAlgorithm::HasOutputString() */
2172 : /************************************************************************/
2173 :
2174 32 : bool GDALAbstractPipelineAlgorithm::HasOutputString() const
2175 : {
2176 93 : for (const auto &step : m_steps)
2177 : {
2178 67 : if (step->HasOutputString())
2179 6 : return true;
2180 : }
2181 26 : return false;
2182 : }
2183 :
2184 : /************************************************************************/
2185 : /* GDALAbstractPipelineAlgorithm::Finalize() */
2186 : /************************************************************************/
2187 :
2188 169 : bool GDALAbstractPipelineAlgorithm::Finalize()
2189 : {
2190 169 : bool ret = GDALPipelineStepAlgorithm::Finalize();
2191 524 : for (auto &step : m_steps)
2192 : {
2193 355 : ret = step->Finalize() && ret;
2194 : }
2195 169 : return ret;
2196 : }
2197 :
2198 : /************************************************************************/
2199 : /* GDALAbstractPipelineAlgorithm::GetUsageAsJSON() */
2200 : /************************************************************************/
2201 :
2202 8 : std::string GDALAbstractPipelineAlgorithm::GetUsageAsJSON() const
2203 : {
2204 16 : CPLJSONDocument oDoc;
2205 8 : CPL_IGNORE_RET_VAL(oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON()));
2206 :
2207 16 : CPLJSONArray jPipelineSteps;
2208 322 : for (const std::string &name : GetStepRegistry().GetNames())
2209 : {
2210 628 : auto alg = GetStepAlg(name);
2211 314 : if (!alg->IsHidden())
2212 : {
2213 314 : CPLJSONDocument oStepDoc;
2214 314 : CPL_IGNORE_RET_VAL(oStepDoc.LoadMemory(alg->GetUsageAsJSON()));
2215 314 : jPipelineSteps.Add(oStepDoc.GetRoot());
2216 : }
2217 : }
2218 8 : oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
2219 :
2220 16 : return oDoc.SaveAsString();
2221 : }
2222 :
2223 : //! @endcond
|