Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster 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 "gdalalg_raster_pipeline.h"
14 : #include "gdalalg_raster_read.h"
15 : #include "gdalalg_raster_astype.h"
16 : #include "gdalalg_raster_clip.h"
17 : #include "gdalalg_raster_edit.h"
18 : #include "gdalalg_raster_reproject.h"
19 : #include "gdalalg_raster_resize.h"
20 : #include "gdalalg_raster_scale.h"
21 : #include "gdalalg_raster_select.h"
22 : #include "gdalalg_raster_write.h"
23 : #include "gdalalg_raster_unscale.h"
24 :
25 : #include "cpl_conv.h"
26 : #include "cpl_string.h"
27 :
28 : #include <algorithm>
29 :
30 : //! @cond Doxygen_Suppress
31 :
32 : #ifndef _
33 : #define _(x) (x)
34 : #endif
35 :
36 : /************************************************************************/
37 : /* GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm() */
38 : /************************************************************************/
39 :
40 432 : GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm(
41 : const std::string &name, const std::string &description,
42 432 : const std::string &helpURL, bool standaloneStep)
43 : : GDALAlgorithm(name, description, helpURL),
44 432 : m_standaloneStep(standaloneStep)
45 : {
46 432 : if (m_standaloneStep)
47 : {
48 69 : m_supportsStreamedOutput = true;
49 :
50 69 : AddInputArgs(false, false);
51 69 : AddProgressArg();
52 69 : AddOutputArgs(false);
53 : }
54 432 : }
55 :
56 : /************************************************************************/
57 : /* GDALRasterPipelineStepAlgorithm::AddInputArgs() */
58 : /************************************************************************/
59 :
60 243 : void GDALRasterPipelineStepAlgorithm::AddInputArgs(
61 : bool openForMixedRasterVector, bool hiddenForCLI)
62 : {
63 243 : AddInputFormatsArg(&m_inputFormats)
64 : .AddMetadataItem(
65 : GAAMDI_REQUIRED_CAPABILITIES,
66 : openForMixedRasterVector
67 735 : ? std::vector<std::string>{GDAL_DCAP_RASTER, GDAL_DCAP_VECTOR}
68 723 : : std::vector<std::string>{GDAL_DCAP_RASTER})
69 243 : .SetHiddenForCLI(hiddenForCLI);
70 243 : AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
71 : AddInputDatasetArg(&m_inputDataset,
72 : openForMixedRasterVector
73 : ? (GDAL_OF_RASTER | GDAL_OF_VECTOR)
74 : : GDAL_OF_RASTER,
75 243 : /* positionalAndRequired = */ !hiddenForCLI)
76 243 : .SetHiddenForCLI(hiddenForCLI);
77 243 : }
78 :
79 : /************************************************************************/
80 : /* GDALRasterPipelineStepAlgorithm::AddOutputArgs() */
81 : /************************************************************************/
82 :
83 240 : void GDALRasterPipelineStepAlgorithm::AddOutputArgs(bool hiddenForCLI)
84 : {
85 : AddOutputFormatArg(&m_format, /* bStreamAllowed = */ true,
86 240 : /* bGDALGAllowed = */ true)
87 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
88 960 : {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY})
89 240 : .SetHiddenForCLI(hiddenForCLI);
90 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
91 240 : /* positionalAndRequired = */ !hiddenForCLI)
92 240 : .SetHiddenForCLI(hiddenForCLI);
93 240 : m_outputDataset.SetInputFlags(GADV_NAME | GADV_OBJECT);
94 240 : AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
95 240 : AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
96 240 : }
97 :
98 : /************************************************************************/
99 : /* GDALRasterPipelineStepAlgorithm::RunImpl() */
100 : /************************************************************************/
101 :
102 230 : bool GDALRasterPipelineStepAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
103 : void *pProgressData)
104 : {
105 230 : if (m_standaloneStep)
106 : {
107 88 : GDALRasterReadAlgorithm readAlg;
108 440 : for (auto &arg : readAlg.GetArgs())
109 : {
110 396 : auto stepArg = GetArg(arg->GetName());
111 396 : if (stepArg && stepArg->IsExplicitlySet())
112 : {
113 44 : arg->SetSkipIfAlreadySet(true);
114 44 : arg->SetFrom(*stepArg);
115 : }
116 : }
117 :
118 44 : GDALRasterWriteAlgorithm writeAlg;
119 484 : for (auto &arg : writeAlg.GetArgs())
120 : {
121 440 : auto stepArg = GetArg(arg->GetName());
122 440 : if (stepArg && stepArg->IsExplicitlySet())
123 : {
124 80 : arg->SetSkipIfAlreadySet(true);
125 80 : arg->SetFrom(*stepArg);
126 : }
127 : }
128 :
129 : // Already checked by GDALAlgorithm::Run()
130 44 : CPLAssert(!m_executionForStreamOutput ||
131 : EQUAL(m_format.c_str(), "stream"));
132 :
133 44 : bool ret = false;
134 44 : if (readAlg.Run())
135 : {
136 44 : m_inputDataset.Set(readAlg.m_outputDataset.GetDatasetRef());
137 44 : m_outputDataset.Set(nullptr);
138 44 : if (RunStep(nullptr, nullptr))
139 : {
140 37 : if (m_format == "stream")
141 : {
142 1 : ret = true;
143 : }
144 : else
145 : {
146 36 : writeAlg.m_inputDataset.Set(
147 : m_outputDataset.GetDatasetRef());
148 36 : if (writeAlg.Run(pfnProgress, pProgressData))
149 : {
150 36 : m_outputDataset.Set(
151 : writeAlg.m_outputDataset.GetDatasetRef());
152 36 : ret = true;
153 : }
154 : }
155 : }
156 : }
157 :
158 44 : return ret;
159 : }
160 : else
161 : {
162 186 : return RunStep(pfnProgress, pProgressData);
163 : }
164 : }
165 :
166 : /************************************************************************/
167 : /* ProcessGDALGOutput() */
168 : /************************************************************************/
169 :
170 : GDALAlgorithm::ProcessGDALGOutputRet
171 252 : GDALRasterPipelineStepAlgorithm::ProcessGDALGOutput()
172 : {
173 252 : if (m_standaloneStep)
174 : {
175 61 : return GDALAlgorithm::ProcessGDALGOutput();
176 : }
177 : else
178 : {
179 : // GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep() might
180 : // actually detect a GDALG output request and process it.
181 191 : return GDALAlgorithm::ProcessGDALGOutputRet::NOT_GDALG;
182 : }
183 : }
184 :
185 : /************************************************************************/
186 : /* GDALRasterPipelineStepAlgorithm::CheckSafeForStreamOutput() */
187 : /************************************************************************/
188 :
189 7 : bool GDALRasterPipelineStepAlgorithm::CheckSafeForStreamOutput()
190 : {
191 7 : if (m_standaloneStep)
192 : {
193 2 : return GDALAlgorithm::CheckSafeForStreamOutput();
194 : }
195 : else
196 : {
197 : // The check is actually done in
198 : // GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep()
199 : // so return true for now.
200 5 : return true;
201 : }
202 : }
203 :
204 : /************************************************************************/
205 : /* GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm() */
206 : /************************************************************************/
207 :
208 73 : GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm(
209 73 : bool openForMixedRasterVector)
210 : : GDALAbstractPipelineAlgorithm<GDALRasterPipelineStepAlgorithm>(
211 : NAME, DESCRIPTION, HELP_URL,
212 73 : /*standaloneStep=*/false)
213 : {
214 73 : m_supportsStreamedOutput = true;
215 :
216 73 : AddInputArgs(openForMixedRasterVector, /* hiddenForCLI = */ true);
217 73 : AddProgressArg();
218 146 : AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
219 73 : .SetHiddenForCLI()
220 73 : .SetPositional();
221 73 : AddOutputArgs(/* hiddenForCLI = */ true);
222 :
223 73 : m_stepRegistry.Register<GDALRasterReadAlgorithm>();
224 73 : m_stepRegistry.Register<GDALRasterWriteAlgorithm>();
225 73 : m_stepRegistry.Register<GDALRasterAsTypeAlgorithm>();
226 73 : m_stepRegistry.Register<GDALRasterClipAlgorithm>();
227 73 : m_stepRegistry.Register<GDALRasterEditAlgorithm>();
228 73 : m_stepRegistry.Register<GDALRasterReprojectAlgorithm>();
229 73 : m_stepRegistry.Register<GDALRasterResizeAlgorithm>();
230 73 : m_stepRegistry.Register<GDALRasterScaleAlgorithm>();
231 73 : m_stepRegistry.Register<GDALRasterSelectAlgorithm>();
232 73 : m_stepRegistry.Register<GDALRasterUnscaleAlgorithm>();
233 73 : }
234 :
235 : /************************************************************************/
236 : /* GDALRasterPipelineAlgorithm::ParseCommandLineArguments() */
237 : /************************************************************************/
238 :
239 57 : bool GDALRasterPipelineAlgorithm::ParseCommandLineArguments(
240 : const std::vector<std::string> &args)
241 : {
242 69 : if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
243 12 : args[0] == "help" || args[0] == "--json-usage"))
244 : {
245 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
246 : }
247 56 : else if (args.size() == 1 && STARTS_WITH(args[0].c_str(), "--help-doc="))
248 : {
249 3 : m_helpDocCategory = args[0].substr(strlen("--help-doc="));
250 6 : return GDALAlgorithm::ParseCommandLineArguments({"--help-doc"});
251 : }
252 :
253 404 : for (const auto &arg : args)
254 : {
255 353 : if (arg.find("--pipeline") == 0)
256 2 : return GDALAlgorithm::ParseCommandLineArguments(args);
257 :
258 : // gdal raster pipeline [--progress] "read in.tif ..."
259 352 : if (arg.find("read ") == 0)
260 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
261 : }
262 :
263 51 : if (!m_steps.empty())
264 : {
265 1 : ReportError(CE_Failure, CPLE_AppDefined,
266 : "ParseCommandLineArguments() can only be called once per "
267 : "instance.");
268 1 : return false;
269 : }
270 :
271 : struct Step
272 : {
273 : std::unique_ptr<GDALRasterPipelineStepAlgorithm> alg{};
274 : std::vector<std::string> args{};
275 : };
276 :
277 100 : std::vector<Step> steps;
278 50 : steps.resize(1);
279 :
280 388 : for (const auto &arg : args)
281 : {
282 340 : if (arg == "--progress")
283 : {
284 1 : m_progressBarRequested = true;
285 1 : continue;
286 : }
287 :
288 339 : auto &curStep = steps.back();
289 :
290 339 : if (arg == "!" || arg == "|")
291 : {
292 75 : if (curStep.alg)
293 : {
294 67 : steps.resize(steps.size() + 1);
295 : }
296 : }
297 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
298 264 : else if (arg == "+step")
299 : {
300 4 : if (curStep.alg)
301 : {
302 2 : steps.resize(steps.size() + 1);
303 : }
304 : }
305 260 : else if (arg.find("+gdal=") == 0)
306 : {
307 3 : const std::string stepName = arg.substr(strlen("+gdal="));
308 3 : curStep.alg = GetStepAlg(stepName);
309 3 : if (!curStep.alg)
310 : {
311 1 : ReportError(CE_Failure, CPLE_AppDefined,
312 : "unknown step name: %s", stepName.c_str());
313 1 : return false;
314 : }
315 : }
316 : #endif
317 257 : else if (!curStep.alg)
318 : {
319 114 : std::string algName = arg;
320 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
321 114 : if (!algName.empty() && algName[0] == '+')
322 1 : algName = algName.substr(1);
323 : #endif
324 114 : curStep.alg = GetStepAlg(algName);
325 114 : if (!curStep.alg)
326 : {
327 1 : ReportError(CE_Failure, CPLE_AppDefined,
328 : "unknown step name: %s", algName.c_str());
329 1 : return false;
330 : }
331 226 : curStep.alg->SetCallPath({algName});
332 : }
333 : else
334 : {
335 143 : if (curStep.alg->HasSubAlgorithms())
336 : {
337 : auto subAlg = std::unique_ptr<GDALRasterPipelineStepAlgorithm>(
338 : cpl::down_cast<GDALRasterPipelineStepAlgorithm *>(
339 0 : curStep.alg->InstantiateSubAlgorithm(arg).release()));
340 0 : if (!subAlg)
341 : {
342 0 : ReportError(CE_Failure, CPLE_AppDefined,
343 : "'%s' is a unknown sub-algorithm of '%s'",
344 0 : arg.c_str(), curStep.alg->GetName().c_str());
345 0 : return false;
346 : }
347 0 : curStep.alg = std::move(subAlg);
348 0 : continue;
349 : }
350 :
351 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
352 147 : if (!arg.empty() && arg[0] == '+' &&
353 4 : arg.find(' ') == std::string::npos)
354 : {
355 3 : curStep.args.push_back("--" + arg.substr(1));
356 3 : continue;
357 : }
358 : #endif
359 140 : curStep.args.push_back(arg);
360 : }
361 : }
362 :
363 : // As we initially added a step without alg to bootstrap things, make
364 : // sure to remove it if it hasn't been filled, or the user has terminated
365 : // the pipeline with a '!' separator.
366 48 : if (!steps.back().alg)
367 2 : steps.pop_back();
368 :
369 : // Automatically add a final write step if none in m_executionForStreamOutput
370 : // mode
371 54 : if (m_executionForStreamOutput && !steps.empty() &&
372 6 : steps.back().alg->GetName() != GDALRasterWriteAlgorithm::NAME)
373 : {
374 4 : steps.resize(steps.size() + 1);
375 4 : steps.back().alg = GetStepAlg(GDALRasterWriteAlgorithm::NAME);
376 4 : steps.back().args.push_back("--output-format");
377 4 : steps.back().args.push_back("stream");
378 4 : steps.back().args.push_back("streamed_dataset");
379 : }
380 :
381 48 : if (steps.size() < 2)
382 : {
383 2 : ReportError(CE_Failure, CPLE_AppDefined,
384 : "At least 2 steps must be provided");
385 2 : return false;
386 : }
387 :
388 46 : if (steps.front().alg->GetName() != GDALRasterReadAlgorithm::NAME)
389 : {
390 1 : ReportError(CE_Failure, CPLE_AppDefined, "First step should be '%s'",
391 : GDALRasterReadAlgorithm::NAME);
392 1 : return false;
393 : }
394 68 : for (size_t i = 1; i < steps.size() - 1; ++i)
395 : {
396 24 : if (steps[i].alg->GetName() == GDALRasterReadAlgorithm::NAME)
397 : {
398 1 : ReportError(CE_Failure, CPLE_AppDefined,
399 : "Only first step can be '%s'",
400 : GDALRasterReadAlgorithm::NAME);
401 1 : return false;
402 : }
403 : }
404 44 : if (steps.back().alg->GetName() != GDALRasterWriteAlgorithm::NAME)
405 : {
406 1 : ReportError(CE_Failure, CPLE_AppDefined, "Last step should be '%s'",
407 : GDALRasterWriteAlgorithm::NAME);
408 1 : return false;
409 : }
410 108 : for (size_t i = 0; i < steps.size() - 1; ++i)
411 : {
412 66 : if (steps[i].alg->GetName() == GDALRasterWriteAlgorithm::NAME)
413 : {
414 1 : ReportError(CE_Failure, CPLE_AppDefined,
415 : "Only last step can be '%s'",
416 : GDALRasterWriteAlgorithm::NAME);
417 1 : return false;
418 : }
419 : }
420 :
421 148 : for (auto &step : steps)
422 : {
423 106 : step.alg->SetReferencePathForRelativePaths(
424 : GetReferencePathForRelativePaths());
425 : }
426 :
427 : // Propagate input parameters set at the pipeline level to the
428 : // "read" step
429 : {
430 42 : auto &step = steps.front();
431 420 : for (auto &arg : step.alg->GetArgs())
432 : {
433 378 : auto pipelineArg = GetArg(arg->GetName());
434 378 : if (pipelineArg && pipelineArg->IsExplicitlySet())
435 : {
436 2 : arg->SetSkipIfAlreadySet(true);
437 2 : arg->SetFrom(*pipelineArg);
438 : }
439 : }
440 : }
441 :
442 : // Same with "write" step
443 : {
444 42 : auto &step = steps.back();
445 462 : for (auto &arg : step.alg->GetArgs())
446 : {
447 420 : auto pipelineArg = GetArg(arg->GetName());
448 420 : if (pipelineArg && pipelineArg->IsExplicitlySet())
449 : {
450 1 : arg->SetSkipIfAlreadySet(true);
451 1 : arg->SetFrom(*pipelineArg);
452 : }
453 : }
454 : }
455 :
456 : // Parse each step, but without running the validation
457 137 : for (const auto &step : steps)
458 : {
459 101 : step.alg->m_skipValidationInParseCommandLine = true;
460 101 : if (!step.alg->ParseCommandLineArguments(step.args))
461 6 : return false;
462 : }
463 :
464 : // Evaluate "input" argument of "read" step, together with the "output"
465 : // argument of the "write" step, in case they point to the same dataset.
466 36 : auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
467 72 : if (inputArg && inputArg->IsExplicitlySet() &&
468 36 : inputArg->GetType() == GAAT_DATASET)
469 : {
470 36 : steps.front().alg->ProcessDatasetArg(inputArg, steps.back().alg.get());
471 : }
472 :
473 122 : for (const auto &step : steps)
474 : {
475 88 : if (!step.alg->ValidateArguments())
476 2 : return false;
477 : }
478 :
479 120 : for (auto &step : steps)
480 86 : m_steps.push_back(std::move(step.alg));
481 :
482 34 : return true;
483 : }
484 :
485 : /************************************************************************/
486 : /* GDALRasterPipelineAlgorithm::GetUsageForCLI() */
487 : /************************************************************************/
488 :
489 6 : std::string GDALRasterPipelineAlgorithm::GetUsageForCLI(
490 : bool shortUsage, const UsageOptions &usageOptions) const
491 : {
492 6 : UsageOptions stepUsageOptions;
493 6 : stepUsageOptions.isPipelineStep = true;
494 :
495 6 : if (!m_helpDocCategory.empty() && m_helpDocCategory != "main")
496 : {
497 4 : auto alg = GetStepAlg(m_helpDocCategory);
498 4 : std::string ret;
499 2 : if (alg)
500 : {
501 2 : alg->SetCallPath({m_helpDocCategory});
502 1 : alg->GetArg("help-doc")->Set(true);
503 1 : return alg->GetUsageForCLI(shortUsage, stepUsageOptions);
504 : }
505 : else
506 : {
507 1 : fprintf(stderr, "ERROR: unknown pipeline step '%s'\n",
508 : m_helpDocCategory.c_str());
509 : return CPLSPrintf("ERROR: unknown pipeline step '%s'\n",
510 1 : m_helpDocCategory.c_str());
511 : }
512 : }
513 :
514 8 : std::string ret = GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions);
515 4 : if (shortUsage)
516 2 : return ret;
517 :
518 : ret += "\n<PIPELINE> is of the form: read [READ-OPTIONS] "
519 2 : "( ! <STEP-NAME> [STEP-OPTIONS] )* ! write [WRITE-OPTIONS]\n";
520 :
521 2 : if (m_helpDocCategory == "main")
522 : {
523 1 : return ret;
524 : }
525 :
526 1 : ret += '\n';
527 1 : ret += "Example: 'gdal raster pipeline --progress ! read in.tif ! \\\n";
528 1 : ret += " reproject --dst-crs=EPSG:32632 ! ";
529 1 : ret += "write out.tif --overwrite'\n";
530 1 : ret += '\n';
531 1 : ret += "Potential steps are:\n";
532 :
533 11 : for (const std::string &name : m_stepRegistry.GetNames())
534 : {
535 20 : auto alg = GetStepAlg(name);
536 10 : auto [options, maxOptLen] = alg->GetArgNamesForCLI();
537 10 : stepUsageOptions.maxOptLen =
538 10 : std::max(stepUsageOptions.maxOptLen, maxOptLen);
539 : }
540 :
541 : {
542 1 : const auto name = GDALRasterReadAlgorithm::NAME;
543 1 : ret += '\n';
544 2 : auto alg = GetStepAlg(name);
545 2 : alg->SetCallPath({name});
546 1 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
547 : }
548 11 : for (const std::string &name : m_stepRegistry.GetNames())
549 : {
550 19 : if (name != GDALRasterReadAlgorithm::NAME &&
551 9 : name != GDALRasterWriteAlgorithm::NAME)
552 : {
553 8 : ret += '\n';
554 8 : auto alg = GetStepAlg(name);
555 16 : alg->SetCallPath({name});
556 8 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
557 : }
558 : }
559 : {
560 1 : const auto name = GDALRasterWriteAlgorithm::NAME;
561 1 : ret += '\n';
562 2 : auto alg = GetStepAlg(name);
563 2 : alg->SetCallPath({name});
564 1 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
565 : }
566 :
567 1 : return ret;
568 : }
569 :
570 : //! @endcond
|