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