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