Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "pipeline" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_error.h"
14 : #include "cpl_error_internal.h"
15 : #include "gdalalg_abstract_pipeline.h"
16 : #include "gdal_priv.h"
17 :
18 : #include "gdalalg_raster_read.h"
19 : #include "gdalalg_raster_mosaic.h"
20 : #include "gdalalg_raster_stack.h"
21 : #include "gdalalg_raster_write.h"
22 :
23 : #include "gdalalg_vector_read.h"
24 : #include "gdalalg_vector_concat.h"
25 : #include "gdalalg_vector_sql.h"
26 : #include "gdalalg_vector_write.h"
27 :
28 : #include "gdalalg_raster_contour.h"
29 : #include "gdalalg_raster_footprint.h"
30 : #include "gdalalg_raster_polygonize.h"
31 : #include "gdalalg_vector_grid.h"
32 : #include "gdalalg_vector_rasterize.h"
33 :
34 : #include <algorithm>
35 : #include <cassert>
36 :
37 : //! @cond Doxygen_Suppress
38 :
39 : #ifndef _
40 : #define _(x) (x)
41 : #endif
42 :
43 : /************************************************************************/
44 : /* GDALPipelineStepAlgorithm() */
45 : /************************************************************************/
46 :
47 3465 : GDALPipelineStepAlgorithm::GDALPipelineStepAlgorithm(
48 : const std::string &name, const std::string &description,
49 3465 : const std::string &helpURL, const ConstructorOptions &options)
50 : : GDALAlgorithm(name, description, helpURL),
51 3465 : m_standaloneStep(options.standaloneStep), m_constructorOptions(options)
52 : {
53 3465 : }
54 :
55 : /************************************************************************/
56 : /* GDALPipelineStepAlgorithm::AddRasterHiddenInputDatasetArg() */
57 : /************************************************************************/
58 :
59 900 : void GDALPipelineStepAlgorithm::AddRasterHiddenInputDatasetArg()
60 : {
61 : // Added so that "band" argument validation works, because
62 : // GDALAlgorithm must be able to retrieve the input dataset
63 : AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER,
64 900 : /* positionalAndRequired = */ false)
65 900 : .SetHidden();
66 900 : }
67 :
68 : /************************************************************************/
69 : /* GDALPipelineStepAlgorithm::AddRasterInputArgs() */
70 : /************************************************************************/
71 :
72 1030 : void GDALPipelineStepAlgorithm::AddRasterInputArgs(
73 : bool openForMixedRasterVector, bool hiddenForCLI)
74 : {
75 1030 : AddInputFormatsArg(&m_inputFormats)
76 : .AddMetadataItem(
77 : GAAMDI_REQUIRED_CAPABILITIES,
78 : openForMixedRasterVector
79 3117 : ? std::vector<std::string>{GDAL_DCAP_RASTER, GDAL_DCAP_VECTOR}
80 3063 : : std::vector<std::string>{GDAL_DCAP_RASTER})
81 1030 : .SetHiddenForCLI(hiddenForCLI);
82 1030 : AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
83 : auto &arg =
84 : AddInputDatasetArg(&m_inputDataset,
85 : openForMixedRasterVector
86 : ? (GDAL_OF_RASTER | GDAL_OF_VECTOR)
87 : : GDAL_OF_RASTER,
88 1030 : /* positionalAndRequired = */ !hiddenForCLI,
89 1030 : m_constructorOptions.inputDatasetHelpMsg.c_str())
90 1030 : .SetMinCount(1)
91 1030 : .SetMaxCount(m_constructorOptions.inputDatasetMaxCount)
92 1030 : .SetAutoOpenDataset(m_constructorOptions.autoOpenInputDatasets)
93 1030 : .SetMetaVar(m_constructorOptions.inputDatasetMetaVar)
94 1030 : .SetHiddenForCLI(hiddenForCLI);
95 1030 : if (!m_constructorOptions.inputDatasetAlias.empty())
96 33 : arg.AddAlias(m_constructorOptions.inputDatasetAlias);
97 1030 : }
98 :
99 : /************************************************************************/
100 : /* GDALPipelineStepAlgorithm::AddRasterOutputArgs() */
101 : /************************************************************************/
102 :
103 1005 : void GDALPipelineStepAlgorithm::AddRasterOutputArgs(bool hiddenForCLI)
104 : {
105 1005 : m_outputFormatArg =
106 : &(AddOutputFormatArg(&m_format, /* bStreamAllowed = */ true,
107 1005 : /* bGDALGAllowed = */ true)
108 : .AddMetadataItem(
109 : GAAMDI_REQUIRED_CAPABILITIES,
110 : {GDAL_DCAP_RASTER,
111 4020 : m_constructorOptions.outputFormatCreateCapability.c_str()})
112 1005 : .SetHiddenForCLI(hiddenForCLI));
113 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
114 1005 : /* positionalAndRequired = */ !hiddenForCLI,
115 1005 : m_constructorOptions.outputDatasetHelpMsg.c_str())
116 1005 : .SetHiddenForCLI(hiddenForCLI)
117 1005 : .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
118 1005 : AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
119 1005 : AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
120 1005 : }
121 :
122 : /************************************************************************/
123 : /* GDALPipelineStepAlgorithm::AddVectorInputArgs() */
124 : /************************************************************************/
125 :
126 502 : void GDALPipelineStepAlgorithm::AddVectorInputArgs(bool hiddenForCLI)
127 : {
128 502 : AddInputFormatsArg(&m_inputFormats)
129 1506 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR})
130 502 : .SetHiddenForCLI(hiddenForCLI);
131 502 : AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
132 : auto &datasetArg =
133 : AddInputDatasetArg(&m_inputDataset, GDAL_OF_VECTOR,
134 502 : /* positionalAndRequired = */ !hiddenForCLI)
135 502 : .SetMinCount(1)
136 502 : .SetMaxCount(m_constructorOptions.inputDatasetMaxCount)
137 502 : .SetHiddenForCLI(hiddenForCLI);
138 502 : if (m_constructorOptions.addInputLayerNameArgument)
139 : {
140 : auto &layerArg = AddArg("input-layer", 'l', _("Input layer name(s)"),
141 976 : &m_inputLayerNames)
142 976 : .AddAlias("layer")
143 488 : .SetHiddenForCLI(hiddenForCLI);
144 488 : SetAutoCompleteFunctionForLayerName(layerArg, datasetArg);
145 : }
146 502 : }
147 :
148 : /************************************************************************/
149 : /* GDALPipelineStepAlgorithm::AddVectorOutputArgs() */
150 : /************************************************************************/
151 :
152 509 : void GDALPipelineStepAlgorithm::AddVectorOutputArgs(
153 : bool hiddenForCLI, bool shortNameOutputLayerAllowed)
154 : {
155 : AddOutputFormatArg(&m_format, /* bStreamAllowed = */ true,
156 509 : /* bGDALGAllowed = */ true)
157 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
158 2036 : {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE})
159 509 : .SetHiddenForCLI(hiddenForCLI);
160 : auto &outputDatasetArg =
161 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR,
162 509 : /* positionalAndRequired = */ false)
163 509 : .SetHiddenForCLI(hiddenForCLI)
164 509 : .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
165 509 : if (!hiddenForCLI)
166 431 : outputDatasetArg.SetPositional();
167 509 : if (!hiddenForCLI && m_constructorOptions.outputDatasetRequired)
168 417 : outputDatasetArg.SetRequired();
169 509 : if (!m_constructorOptions.outputDatasetMutualExclusionGroup.empty())
170 : {
171 : outputDatasetArg.SetMutualExclusionGroup(
172 14 : m_constructorOptions.outputDatasetMutualExclusionGroup);
173 : }
174 509 : AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
175 509 : AddLayerCreationOptionsArg(&m_layerCreationOptions)
176 509 : .SetHiddenForCLI(hiddenForCLI);
177 509 : AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
178 509 : auto &updateArg = AddUpdateArg(&m_update).SetHiddenForCLI(hiddenForCLI);
179 509 : if (!m_constructorOptions.updateMutualExclusionGroup.empty())
180 : {
181 : updateArg.SetMutualExclusionGroup(
182 14 : m_constructorOptions.updateMutualExclusionGroup);
183 : }
184 509 : AddOverwriteLayerArg(&m_overwriteLayer).SetHiddenForCLI(hiddenForCLI);
185 509 : AddAppendLayerArg(&m_appendLayer).SetHiddenForCLI(hiddenForCLI);
186 1004 : if (GetName() != GDALVectorSQLAlgorithm::NAME &&
187 495 : GetName() != GDALVectorConcatAlgorithm::NAME)
188 : {
189 : AddArg("output-layer", shortNameOutputLayerAllowed ? 'l' : 0,
190 936 : _("Output layer name"), &m_outputLayerName)
191 936 : .AddHiddenAlias("nln") // For ogr2ogr nostalgic people
192 468 : .SetHiddenForCLI(hiddenForCLI);
193 : }
194 509 : }
195 :
196 : /************************************************************************/
197 : /* GDALPipelineStepAlgorithm::RunImpl() */
198 : /************************************************************************/
199 :
200 963 : bool GDALPipelineStepAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
201 : void *pProgressData)
202 : {
203 963 : if (m_standaloneStep)
204 : {
205 388 : std::unique_ptr<GDALPipelineStepAlgorithm> readAlg;
206 388 : if (GetInputType() == GDAL_OF_RASTER)
207 256 : readAlg = std::make_unique<GDALRasterReadAlgorithm>();
208 : else
209 132 : readAlg = std::make_unique<GDALVectorReadAlgorithm>();
210 3236 : for (auto &arg : readAlg->GetArgs())
211 : {
212 2848 : auto stepArg = GetArg(arg->GetName());
213 2848 : if (stepArg && stepArg->IsExplicitlySet())
214 : {
215 388 : arg->SetSkipIfAlreadySet(true);
216 388 : arg->SetFrom(*stepArg);
217 : }
218 : }
219 :
220 0 : std::unique_ptr<GDALPipelineStepAlgorithm> writeAlg;
221 388 : if (GetOutputType() == GDAL_OF_RASTER)
222 256 : writeAlg = std::make_unique<GDALRasterWriteAlgorithm>();
223 : else
224 132 : writeAlg = std::make_unique<GDALVectorWriteAlgorithm>();
225 4408 : for (auto &arg : writeAlg->GetArgs())
226 : {
227 4020 : auto stepArg = GetArg(arg->GetName());
228 4020 : if (stepArg && stepArg->IsExplicitlySet())
229 : {
230 960 : arg->SetSkipIfAlreadySet(true);
231 960 : arg->SetFrom(*stepArg);
232 : }
233 : }
234 :
235 388 : const bool bIsStreaming = m_format == "stream";
236 :
237 : // Already checked by GDALAlgorithm::Run()
238 388 : CPLAssert(!m_executionForStreamOutput || bIsStreaming);
239 :
240 388 : bool ret = false;
241 443 : if (!m_outputVRTCompatible &&
242 110 : (EQUAL(m_format.c_str(), "VRT") ||
243 55 : (m_format.empty() &&
244 388 : EQUAL(CPLGetExtensionSafe(m_outputDataset.GetName().c_str())
245 : .c_str(),
246 : "VRT"))))
247 : {
248 0 : ReportError(CE_Failure, CPLE_NotSupported,
249 : "VRT output is not supported. Consider using the "
250 : "GDALG driver instead (files with .gdalg.json "
251 : "extension)");
252 : }
253 388 : else if (readAlg->Run())
254 : {
255 : const bool bOutputSpecified =
256 388 : GetArg(GDAL_ARG_NAME_OUTPUT)->IsExplicitlySet();
257 :
258 388 : m_inputDataset.clear();
259 388 : m_inputDataset.resize(1);
260 388 : m_inputDataset[0].Set(readAlg->m_outputDataset.GetDatasetRef());
261 388 : if (bOutputSpecified)
262 385 : m_outputDataset.Set(nullptr);
263 :
264 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
265 776 : pScaledData(nullptr, GDALDestroyScaledProgress);
266 :
267 : const bool bCanHandleNextStep =
268 388 : !bIsStreaming && CanHandleNextStep(writeAlg.get());
269 :
270 407 : if (pfnProgress &&
271 19 : (bCanHandleNextStep || !IsNativelyStreamingCompatible()))
272 : {
273 36 : pScaledData.reset(GDALCreateScaledProgress(
274 18 : 0.0, bIsStreaming || bCanHandleNextStep ? 1.0 : 0.5,
275 : pfnProgress, pProgressData));
276 : }
277 :
278 388 : GDALPipelineStepRunContext stepCtxt;
279 388 : stepCtxt.m_pfnProgress = pScaledData ? GDALScaledProgress : nullptr;
280 388 : stepCtxt.m_pProgressData = pScaledData.get();
281 388 : if (bCanHandleNextStep)
282 42 : stepCtxt.m_poNextUsableStep = writeAlg.get();
283 388 : if (RunPreStepPipelineValidations() && RunStep(stepCtxt))
284 : {
285 320 : if (bIsStreaming || bCanHandleNextStep || !bOutputSpecified)
286 : {
287 123 : ret = true;
288 : }
289 : else
290 : {
291 197 : writeAlg->m_outputVRTCompatible = m_outputVRTCompatible;
292 197 : writeAlg->m_inputDataset.clear();
293 197 : writeAlg->m_inputDataset.resize(1);
294 197 : writeAlg->m_inputDataset[0].Set(
295 : m_outputDataset.GetDatasetRef());
296 197 : if (pfnProgress)
297 : {
298 19 : pScaledData.reset(GDALCreateScaledProgress(
299 19 : IsNativelyStreamingCompatible() ? 0.0 : 0.5, 1.0,
300 : pfnProgress, pProgressData));
301 : }
302 197 : stepCtxt.m_pfnProgress =
303 197 : pScaledData ? GDALScaledProgress : nullptr;
304 197 : stepCtxt.m_pProgressData = pScaledData.get();
305 394 : if (writeAlg->ValidateArguments() &&
306 197 : writeAlg->RunStep(stepCtxt))
307 : {
308 195 : if (pfnProgress)
309 19 : pfnProgress(1.0, "", pProgressData);
310 :
311 195 : m_outputDataset.Set(
312 195 : writeAlg->m_outputDataset.GetDatasetRef());
313 195 : ret = true;
314 : }
315 : }
316 : }
317 : }
318 :
319 388 : return ret;
320 : }
321 : else
322 : {
323 575 : GDALPipelineStepRunContext stepCtxt;
324 575 : stepCtxt.m_pfnProgress = pfnProgress;
325 575 : stepCtxt.m_pProgressData = pProgressData;
326 575 : return RunPreStepPipelineValidations() && RunStep(stepCtxt);
327 : }
328 : }
329 :
330 : /************************************************************************/
331 : /* ProcessGDALGOutput() */
332 : /************************************************************************/
333 :
334 : GDALAlgorithm::ProcessGDALGOutputRet
335 1323 : GDALPipelineStepAlgorithm::ProcessGDALGOutput()
336 : {
337 1323 : if (m_standaloneStep)
338 : {
339 691 : return GDALAlgorithm::ProcessGDALGOutput();
340 : }
341 : else
342 : {
343 : // GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep() might
344 : // actually detect a GDALG output request and process it.
345 632 : return GDALAlgorithm::ProcessGDALGOutputRet::NOT_GDALG;
346 : }
347 : }
348 :
349 : /************************************************************************/
350 : /* GDALPipelineStepAlgorithm::CheckSafeForStreamOutput() */
351 : /************************************************************************/
352 :
353 47 : bool GDALPipelineStepAlgorithm::CheckSafeForStreamOutput()
354 : {
355 47 : if (m_standaloneStep)
356 : {
357 27 : return GDALAlgorithm::CheckSafeForStreamOutput();
358 : }
359 : else
360 : {
361 : // The check is actually done in
362 : // GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep()
363 : // so return true for now.
364 20 : return true;
365 : }
366 : }
367 :
368 : /************************************************************************/
369 : /* GDALPipelineAlgorithm */
370 : /************************************************************************/
371 :
372 : class GDALPipelineAlgorithm final
373 : : public GDALAbstractPipelineAlgorithm<GDALPipelineStepAlgorithm>
374 :
375 : {
376 : public:
377 : static constexpr const char *NAME = "pipeline";
378 : static constexpr const char *DESCRIPTION = "Execute a pipeline.";
379 : static constexpr const char *HELP_URL = "/programs/gdal_pipeline.html";
380 :
381 49 : GDALPipelineAlgorithm()
382 49 : : GDALAbstractPipelineAlgorithm(
383 : NAME, DESCRIPTION, HELP_URL,
384 49 : ConstructorOptions().SetStandaloneStep(false))
385 : {
386 49 : m_supportsStreamedOutput = true;
387 :
388 49 : AddProgressArg();
389 : AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER | GDAL_OF_VECTOR,
390 49 : /* positionalAndRequired = */ false)
391 49 : .SetMinCount(1)
392 49 : .SetMaxCount(INT_MAX)
393 49 : .SetHiddenForCLI();
394 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER | GDAL_OF_VECTOR,
395 49 : /* positionalAndRequired = */ false)
396 49 : .SetHiddenForCLI()
397 49 : .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
398 : AddOutputFormatArg(&m_format, /* bStreamAllowed = */ true,
399 49 : /* bGDALGAllowed = */ true)
400 49 : .SetHiddenForCLI();
401 98 : AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
402 49 : .SetHiddenForCLI()
403 49 : .SetPositional();
404 :
405 49 : GDALRasterPipelineAlgorithm::RegisterAlgorithms(m_stepRegistry, true);
406 49 : GDALVectorPipelineAlgorithm::RegisterAlgorithms(m_stepRegistry, true);
407 49 : m_stepRegistry.Register<GDALRasterContourAlgorithm>();
408 49 : m_stepRegistry.Register<GDALRasterFootprintAlgorithm>();
409 49 : m_stepRegistry.Register<GDALRasterPolygonizeAlgorithm>();
410 49 : m_stepRegistry.Register<GDALVectorGridAlgorithm>();
411 49 : m_stepRegistry.Register<GDALVectorRasterizeAlgorithm>();
412 49 : }
413 :
414 : // Declared to satisfy GDALPipelineStepAlgorithm, but not called as this
415 : // class is not an actual step, hence return value is "random"
416 0 : int GetInputType() const override
417 : {
418 0 : CPLAssert(false);
419 : return 0;
420 : }
421 :
422 : // Declared to satisfy GDALPipelineStepAlgorithm, but not called as this
423 : // class is not an actual step, hence return value is "random"
424 0 : int GetOutputType() const override
425 : {
426 0 : CPLAssert(false);
427 : return 0;
428 : }
429 :
430 : protected:
431 : bool
432 : ParseCommandLineArguments(const std::vector<std::string> &args) override;
433 :
434 : std::string GetUsageForCLI(bool shortUsage,
435 : const UsageOptions &usageOptions) const override;
436 : };
437 :
438 : /************************************************************************/
439 : /* GDALPipelineAlgorithm::ParseCommandLineArguments() */
440 : /************************************************************************/
441 :
442 35 : bool GDALPipelineAlgorithm::ParseCommandLineArguments(
443 : const std::vector<std::string> &args)
444 : {
445 43 : if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
446 8 : args[0] == "help" || args[0] == "--json-usage"))
447 : {
448 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
449 : }
450 34 : else if (args.size() == 1 && STARTS_WITH(args[0].c_str(), "--help-doc="))
451 : {
452 3 : m_helpDocCategory = args[0].substr(strlen("--help-doc="));
453 6 : return GDALAlgorithm::ParseCommandLineArguments({"--help-doc"});
454 : }
455 :
456 200 : for (const auto &arg : args)
457 : {
458 170 : if (arg.find("--pipeline") == 0)
459 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
460 :
461 : // gdal pipeline [--progress] "read poly.gpkg ..."
462 170 : if (arg.find("read ") == 0)
463 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
464 : }
465 :
466 30 : if (!m_steps.empty())
467 : {
468 1 : ReportError(CE_Failure, CPLE_AppDefined,
469 : "ParseCommandLineArguments() can only be called once per "
470 : "instance.");
471 1 : return false;
472 : }
473 :
474 : struct Step
475 : {
476 : std::unique_ptr<GDALPipelineStepAlgorithm> alg{};
477 : std::vector<std::string> args{};
478 : bool alreadyChangedType = false;
479 : bool isSubAlgorithm = false;
480 : };
481 :
482 58 : std::vector<Step> steps;
483 29 : steps.resize(1);
484 :
485 190 : for (const auto &arg : args)
486 : {
487 162 : if (arg == "--progress")
488 : {
489 1 : m_progressBarRequested = true;
490 1 : continue;
491 : }
492 :
493 161 : if (IsCalledFromCommandLine() && (arg == "-h" || arg == "--help"))
494 : {
495 0 : if (!steps.back().alg)
496 0 : steps.pop_back();
497 0 : if (steps.empty())
498 : {
499 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
500 : }
501 : else
502 : {
503 0 : m_stepOnWhichHelpIsRequested = std::move(steps.back().alg);
504 0 : return true;
505 : }
506 : }
507 :
508 161 : auto &curStep = steps.back();
509 :
510 161 : if (arg == "!" || arg == "|")
511 : {
512 38 : if (curStep.alg)
513 : {
514 38 : steps.resize(steps.size() + 1);
515 : }
516 : }
517 123 : else if (!curStep.alg)
518 : {
519 67 : std::string algName = arg;
520 67 : if (algName == "read")
521 : {
522 27 : curStep.alg = std::make_unique<GDALRasterReadAlgorithm>(true);
523 : }
524 : else
525 : {
526 40 : curStep.alg = GetStepAlg(algName);
527 40 : if (!curStep.alg)
528 31 : curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
529 : }
530 67 : if (!curStep.alg)
531 : {
532 1 : ReportError(CE_Failure, CPLE_AppDefined,
533 : "unknown step name: %s", algName.c_str());
534 1 : return false;
535 : }
536 132 : curStep.alg->SetCallPath({std::move(algName)});
537 66 : curStep.alg->SetReferencePathForRelativePaths(
538 : GetReferencePathForRelativePaths());
539 : }
540 : else
541 : {
542 56 : if (curStep.alg->HasSubAlgorithms())
543 : {
544 : auto subAlg = std::unique_ptr<GDALPipelineStepAlgorithm>(
545 : cpl::down_cast<GDALPipelineStepAlgorithm *>(
546 1 : curStep.alg->InstantiateSubAlgorithm(arg).release()));
547 1 : if (!subAlg)
548 : {
549 0 : ReportError(CE_Failure, CPLE_AppDefined,
550 : "'%s' is a unknown sub-algorithm of '%s'",
551 0 : arg.c_str(), curStep.alg->GetName().c_str());
552 0 : return false;
553 : }
554 1 : curStep.isSubAlgorithm = true;
555 1 : curStep.alg = std::move(subAlg);
556 1 : continue;
557 : }
558 :
559 55 : curStep.args.push_back(arg);
560 : }
561 : }
562 :
563 : // As we initially added a step without alg to bootstrap things, make
564 : // sure to remove it if it hasn't been filled, or the user has terminated
565 : // the pipeline with a '!' separator.
566 28 : if (!steps.back().alg)
567 0 : steps.pop_back();
568 :
569 : // Automatically add a final write step if none in m_executionForStreamOutput
570 : // mode
571 30 : if (m_executionForStreamOutput && !steps.empty() &&
572 2 : steps.back().alg->GetName() !=
573 30 : std::string(GDALRasterWriteAlgorithm::NAME).append(RASTER_SUFFIX))
574 : {
575 2 : steps.resize(steps.size() + 1);
576 4 : steps.back().alg = GetStepAlg(
577 6 : std::string(GDALRasterWriteAlgorithm::NAME).append(RASTER_SUFFIX));
578 2 : steps.back().args.push_back("--output-format");
579 2 : steps.back().args.push_back("stream");
580 2 : steps.back().args.push_back("streamed_dataset");
581 : }
582 :
583 28 : bool helpRequested = false;
584 28 : if (IsCalledFromCommandLine())
585 : {
586 15 : for (auto &step : steps)
587 10 : step.alg->SetCalledFromCommandLine();
588 :
589 31 : for (const std::string &v : args)
590 : {
591 26 : if (cpl::ends_with(v, "=?"))
592 1 : helpRequested = true;
593 : }
594 : }
595 :
596 28 : if (steps.size() < 2)
597 : {
598 1 : if (!steps.empty() && helpRequested)
599 : {
600 0 : steps.back().alg->ParseCommandLineArguments(steps.back().args);
601 0 : return false;
602 : }
603 :
604 1 : ReportError(CE_Failure, CPLE_AppDefined,
605 : "At least 2 steps must be provided");
606 1 : return false;
607 : }
608 :
609 54 : std::vector<GDALPipelineStepAlgorithm *> stepAlgs;
610 94 : for (const auto &step : steps)
611 67 : stepAlgs.push_back(step.alg.get());
612 27 : if (!CheckFirstStep(stepAlgs))
613 2 : return false; // CheckFirstStep emits an error
614 :
615 25 : if (steps.back().alg->GetName() != GDALRasterWriteAlgorithm::NAME)
616 : {
617 2 : if (helpRequested)
618 : {
619 1 : steps.back().alg->ParseCommandLineArguments(steps.back().args);
620 1 : return false;
621 : }
622 1 : ReportError(CE_Failure, CPLE_AppDefined, "Last step should be '%s'",
623 : GDALRasterWriteAlgorithm::NAME);
624 1 : return false;
625 : }
626 57 : for (size_t i = 0; i < steps.size() - 1; ++i)
627 : {
628 35 : if (steps[i].alg->GetName() == GDALRasterWriteAlgorithm::NAME)
629 : {
630 1 : ReportError(CE_Failure, CPLE_AppDefined,
631 : "Only last step can be '%s'",
632 : GDALRasterWriteAlgorithm::NAME);
633 1 : return false;
634 : }
635 : }
636 :
637 : // Propagate input parameters set at the pipeline level to the
638 : // "read" step
639 : {
640 22 : auto &step = steps.front();
641 183 : for (auto &arg : step.alg->GetArgs())
642 : {
643 161 : if (!arg->IsHidden())
644 : {
645 138 : auto pipelineArg = GetArg(arg->GetName());
646 138 : if (pipelineArg && pipelineArg->IsExplicitlySet())
647 : {
648 6 : arg->SetSkipIfAlreadySet(true);
649 6 : arg->SetFrom(*pipelineArg);
650 : }
651 : }
652 : }
653 : }
654 :
655 : // Same with "write" step
656 352 : const auto SetWriteArgFromPipeline = [this, &steps]()
657 : {
658 39 : auto &step = steps.back();
659 422 : for (auto &arg : step.alg->GetArgs())
660 : {
661 383 : if (!arg->IsHidden())
662 : {
663 313 : auto pipelineArg = GetArg(arg->GetName());
664 313 : if (pipelineArg && pipelineArg->IsExplicitlySet())
665 : {
666 28 : arg->SetSkipIfAlreadySet(true);
667 28 : arg->SetFrom(*pipelineArg);
668 : }
669 : }
670 : }
671 39 : };
672 :
673 22 : SetWriteArgFromPipeline();
674 :
675 : // Parse each step, but without running the validation
676 73 : for (auto &step : steps)
677 : {
678 : bool ret;
679 53 : CPLErrorAccumulator oAccumulator;
680 : {
681 : [[maybe_unused]] auto context =
682 53 : oAccumulator.InstallForCurrentScope();
683 53 : step.alg->m_skipValidationInParseCommandLine = true;
684 53 : ret = step.alg->ParseCommandLineArguments(step.args);
685 : }
686 53 : if (!ret)
687 : {
688 3 : auto alg = GetStepAlg(step.alg->GetName() + VECTOR_SUFFIX);
689 3 : if (alg)
690 : {
691 2 : step.alg = std::move(alg);
692 2 : step.alg->m_skipValidationInParseCommandLine = true;
693 2 : ret = step.alg->ParseCommandLineArguments(step.args);
694 2 : if (!ret)
695 1 : return false;
696 2 : step.alg->SetCallPath({step.alg->GetName()});
697 1 : step.alg->SetReferencePathForRelativePaths(
698 : GetReferencePathForRelativePaths());
699 1 : step.alreadyChangedType = true;
700 : }
701 : else
702 : {
703 2 : for (const auto &sError : oAccumulator.GetErrors())
704 : {
705 1 : CPLError(sError.type, sError.no, "%s", sError.msg.c_str());
706 : }
707 1 : return false;
708 : }
709 : }
710 : }
711 :
712 : // Evaluate "input" argument of "read" step, together with the "output"
713 : // argument of the "write" step, in case they point to the same dataset.
714 20 : auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
715 40 : if (inputArg && inputArg->IsExplicitlySet() &&
716 60 : inputArg->GetType() == GAAT_DATASET_LIST &&
717 20 : inputArg->Get<std::vector<GDALArgDatasetValue>>().size() == 1)
718 : {
719 20 : steps.front().alg->ProcessDatasetArg(inputArg, steps.back().alg.get());
720 : }
721 :
722 20 : int nLastStepOutputType = GDAL_OF_VECTOR;
723 20 : if (steps.front().alg->GetName() !=
724 41 : std::string(GDALRasterReadAlgorithm::NAME) &&
725 1 : steps.front().alg->GetOutputType() == GDAL_OF_RASTER)
726 : {
727 1 : nLastStepOutputType = GDAL_OF_RASTER;
728 : }
729 : else
730 : {
731 19 : auto poSrcDS = steps.front().alg->GetInputDatasets()[0].GetDatasetRef();
732 19 : if (poSrcDS)
733 : {
734 19 : if (poSrcDS->GetRasterCount() != 0)
735 10 : nLastStepOutputType = GDAL_OF_RASTER;
736 : }
737 : }
738 :
739 44 : for (size_t i = 1; i < steps.size(); ++i)
740 : {
741 52 : if (!steps[i].alreadyChangedType && !steps[i].isSubAlgorithm &&
742 52 : GetStepAlg(steps[i].alg->GetName()) == nullptr)
743 : {
744 38 : steps[i].alg = GetStepAlg(steps[i].alg->GetName() +
745 : (nLastStepOutputType == GDAL_OF_RASTER
746 : ? RASTER_SUFFIX
747 19 : : VECTOR_SUFFIX));
748 19 : CPLAssert(steps[i].alg);
749 :
750 19 : if (i == steps.size() - 1)
751 : {
752 17 : SetWriteArgFromPipeline();
753 : }
754 :
755 19 : steps[i].alg->m_skipValidationInParseCommandLine = true;
756 19 : if (!steps[i].alg->ParseCommandLineArguments(steps[i].args))
757 1 : return false;
758 36 : steps[i].alg->SetCallPath({steps[i].alg->GetName()});
759 18 : steps[i].alg->SetReferencePathForRelativePaths(
760 : GetReferencePathForRelativePaths());
761 18 : steps[i].alreadyChangedType = true;
762 : }
763 8 : else if (steps[i].alg->GetInputType() != nLastStepOutputType)
764 : {
765 8 : ReportError(
766 : CE_Failure, CPLE_AppDefined,
767 : "Step '%s' expects a %s input dataset, but previous step '%s' "
768 : "generates a %s output dataset",
769 2 : steps[i].alg->GetName().c_str(),
770 2 : steps[i].alg->GetInputType() == GDAL_OF_RASTER ? "raster"
771 : : "vector",
772 2 : steps[i - 1].alg->GetName().c_str(),
773 : nLastStepOutputType == GDAL_OF_RASTER ? "raster" : "vector");
774 2 : return false;
775 : }
776 24 : nLastStepOutputType = steps[i].alg->GetOutputType();
777 : }
778 :
779 58 : for (const auto &step : steps)
780 : {
781 41 : if (!step.alg->ValidateArguments())
782 0 : return false;
783 : }
784 :
785 58 : for (auto &step : steps)
786 41 : m_steps.push_back(std::move(step.alg));
787 :
788 17 : return true;
789 : }
790 :
791 : /************************************************************************/
792 : /* GDALPipelineAlgorithm::GetUsageForCLI() */
793 : /************************************************************************/
794 :
795 : std::string
796 4 : GDALPipelineAlgorithm::GetUsageForCLI(bool shortUsage,
797 : const UsageOptions &usageOptions) const
798 : {
799 4 : UsageOptions stepUsageOptions;
800 4 : stepUsageOptions.isPipelineStep = true;
801 :
802 4 : if (!m_helpDocCategory.empty() && m_helpDocCategory != "main")
803 : {
804 4 : auto alg = GetStepAlg(m_helpDocCategory);
805 4 : std::string ret;
806 2 : if (alg)
807 : {
808 3 : alg->SetCallPath({CPLString(m_helpDocCategory)
809 2 : .replaceAll(RASTER_SUFFIX, "")
810 3 : .replaceAll(VECTOR_SUFFIX, "")});
811 1 : alg->GetArg("help-doc")->Set(true);
812 1 : return alg->GetUsageForCLI(shortUsage, stepUsageOptions);
813 : }
814 : else
815 : {
816 1 : fprintf(stderr, "ERROR: unknown pipeline step '%s'\n",
817 : m_helpDocCategory.c_str());
818 : return CPLSPrintf("ERROR: unknown pipeline step '%s'\n",
819 1 : m_helpDocCategory.c_str());
820 : }
821 : }
822 :
823 2 : UsageOptions usageOptionsMain(usageOptions);
824 2 : usageOptionsMain.isPipelineMain = true;
825 : std::string ret =
826 4 : GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptionsMain);
827 2 : if (shortUsage)
828 0 : return ret;
829 :
830 : ret += "\n<PIPELINE> is of the form: read|calc|concat|mosaic|stack "
831 : "[READ-OPTIONS] "
832 2 : "( ! <STEP-NAME> [STEP-OPTIONS] )* ! write [WRITE-OPTIONS]\n";
833 :
834 2 : if (m_helpDocCategory == "main")
835 : {
836 1 : return ret;
837 : }
838 :
839 1 : ret += '\n';
840 1 : ret += "Example: 'gdal pipeline --progress ! read in.tif ! \\\n";
841 1 : ret += " rasterize --size 256 256 ! buffer 20 ! ";
842 1 : ret += "write out.gpkg --overwrite'\n";
843 1 : ret += '\n';
844 1 : ret += "Potential steps are:\n";
845 :
846 53 : for (const std::string &name : m_stepRegistry.GetNames())
847 : {
848 104 : auto alg = GetStepAlg(name);
849 52 : assert(alg);
850 52 : auto [options, maxOptLen] = alg->GetArgNamesForCLI();
851 52 : stepUsageOptions.maxOptLen =
852 52 : std::max(stepUsageOptions.maxOptLen, maxOptLen);
853 : }
854 :
855 : {
856 1 : ret += '\n';
857 1 : auto alg = std::make_unique<GDALRasterReadAlgorithm>();
858 2 : alg->SetCallPath({alg->GetName()});
859 1 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
860 : }
861 : {
862 1 : ret += '\n';
863 1 : auto alg = std::make_unique<GDALVectorReadAlgorithm>();
864 2 : alg->SetCallPath({alg->GetName()});
865 1 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
866 : }
867 53 : for (const std::string &name : m_stepRegistry.GetNames())
868 : {
869 104 : auto alg = GetStepAlg(name);
870 52 : assert(alg);
871 58 : if (alg->CanBeFirstStep() && !alg->IsHidden() &&
872 6 : !STARTS_WITH(name.c_str(), GDALRasterReadAlgorithm::NAME))
873 : {
874 4 : ret += '\n';
875 8 : alg->SetCallPath({name});
876 4 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
877 : }
878 : }
879 53 : for (const std::string &name : m_stepRegistry.GetNames())
880 : {
881 104 : auto alg = GetStepAlg(name);
882 52 : assert(alg);
883 97 : if (!alg->CanBeFirstStep() && !alg->IsHidden() &&
884 45 : !STARTS_WITH(name.c_str(), GDALRasterWriteAlgorithm::NAME))
885 : {
886 43 : ret += '\n';
887 129 : alg->SetCallPath({CPLString(alg->GetName())
888 86 : .replaceAll(RASTER_SUFFIX, "")
889 129 : .replaceAll(VECTOR_SUFFIX, "")});
890 43 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
891 : }
892 : }
893 : {
894 1 : ret += '\n';
895 1 : auto alg = std::make_unique<GDALRasterWriteAlgorithm>();
896 2 : alg->SetCallPath({alg->GetName()});
897 1 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
898 : }
899 : {
900 1 : ret += '\n';
901 1 : auto alg = std::make_unique<GDALVectorWriteAlgorithm>();
902 1 : assert(alg);
903 2 : alg->SetCallPath({alg->GetName()});
904 1 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
905 : }
906 :
907 1 : ret += GetUsageForCLIEnd();
908 :
909 1 : return ret;
910 : }
911 :
912 : GDAL_STATIC_REGISTER_ALG(GDALPipelineAlgorithm);
913 :
914 : //! @endcond
|