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