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