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