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