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_clip.h"
16 : #include "gdalalg_raster_edit.h"
17 : #include "gdalalg_raster_reproject.h"
18 : #include "gdalalg_raster_write.h"
19 :
20 : #include "cpl_conv.h"
21 : #include "cpl_string.h"
22 :
23 : #include <algorithm>
24 :
25 : //! @cond Doxygen_Suppress
26 :
27 : #ifndef _
28 : #define _(x) (x)
29 : #endif
30 :
31 : /************************************************************************/
32 : /* GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm() */
33 : /************************************************************************/
34 :
35 222 : GDALRasterPipelineStepAlgorithm::GDALRasterPipelineStepAlgorithm(
36 : const std::string &name, const std::string &description,
37 222 : const std::string &helpURL, bool standaloneStep)
38 : : GDALAlgorithm(name, description, helpURL),
39 222 : m_standaloneStep(standaloneStep)
40 : {
41 222 : if (m_standaloneStep)
42 : {
43 13 : AddInputArgs(false, false);
44 13 : AddProgressArg();
45 13 : AddOutputArgs(false);
46 : }
47 222 : }
48 :
49 : /************************************************************************/
50 : /* GDALRasterPipelineStepAlgorithm::AddInputArgs() */
51 : /************************************************************************/
52 :
53 122 : void GDALRasterPipelineStepAlgorithm::AddInputArgs(
54 : bool openForMixedRasterVector, bool hiddenForCLI)
55 : {
56 122 : AddInputFormatsArg(&m_inputFormats)
57 : .AddMetadataItem(
58 : GAAMDI_REQUIRED_CAPABILITIES,
59 : openForMixedRasterVector
60 372 : ? std::vector<std::string>{GDAL_DCAP_RASTER, GDAL_DCAP_VECTOR}
61 360 : : std::vector<std::string>{GDAL_DCAP_RASTER})
62 122 : .SetHiddenForCLI(hiddenForCLI);
63 122 : AddOpenOptionsArg(&m_openOptions).SetHiddenForCLI(hiddenForCLI);
64 : AddInputDatasetArg(&m_inputDataset,
65 : openForMixedRasterVector
66 : ? (GDAL_OF_RASTER | GDAL_OF_VECTOR)
67 : : GDAL_OF_RASTER,
68 122 : /* positionalAndRequired = */ !hiddenForCLI)
69 122 : .SetHiddenForCLI(hiddenForCLI);
70 122 : }
71 :
72 : /************************************************************************/
73 : /* GDALRasterPipelineStepAlgorithm::AddOutputArgs() */
74 : /************************************************************************/
75 :
76 120 : void GDALRasterPipelineStepAlgorithm::AddOutputArgs(bool hiddenForCLI)
77 : {
78 120 : AddOutputFormatArg(&m_format)
79 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
80 480 : {GDAL_DCAP_RASTER, GDAL_DCAP_CREATECOPY})
81 120 : .SetHiddenForCLI(hiddenForCLI);
82 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
83 120 : /* positionalAndRequired = */ !hiddenForCLI)
84 120 : .SetHiddenForCLI(hiddenForCLI);
85 120 : m_outputDataset.SetInputFlags(GADV_NAME | GADV_OBJECT);
86 120 : AddCreationOptionsArg(&m_creationOptions).SetHiddenForCLI(hiddenForCLI);
87 120 : AddOverwriteArg(&m_overwrite).SetHiddenForCLI(hiddenForCLI);
88 120 : }
89 :
90 : /************************************************************************/
91 : /* GDALRasterPipelineStepAlgorithm::RunImpl() */
92 : /************************************************************************/
93 :
94 92 : bool GDALRasterPipelineStepAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
95 : void *pProgressData)
96 : {
97 92 : if (m_standaloneStep)
98 : {
99 4 : GDALRasterReadAlgorithm readAlg;
100 18 : for (auto &arg : readAlg.GetArgs())
101 : {
102 16 : auto stepArg = GetArg(arg->GetName());
103 16 : if (stepArg && stepArg->IsExplicitlySet())
104 : {
105 2 : arg->SetSkipIfAlreadySet(true);
106 2 : arg->SetFrom(*stepArg);
107 : }
108 : }
109 :
110 2 : GDALRasterWriteAlgorithm writeAlg;
111 20 : for (auto &arg : writeAlg.GetArgs())
112 : {
113 18 : auto stepArg = GetArg(arg->GetName());
114 18 : if (stepArg && stepArg->IsExplicitlySet())
115 : {
116 2 : arg->SetSkipIfAlreadySet(true);
117 2 : arg->SetFrom(*stepArg);
118 : }
119 : }
120 :
121 2 : bool ret = false;
122 2 : if (readAlg.Run())
123 : {
124 2 : m_inputDataset.Set(readAlg.m_outputDataset.GetDatasetRef());
125 2 : m_outputDataset.Set(nullptr);
126 2 : if (RunStep(nullptr, nullptr))
127 : {
128 1 : writeAlg.m_inputDataset.Set(m_outputDataset.GetDatasetRef());
129 1 : if (writeAlg.Run(pfnProgress, pProgressData))
130 : {
131 1 : m_outputDataset.Set(
132 : writeAlg.m_outputDataset.GetDatasetRef());
133 1 : ret = true;
134 : }
135 : }
136 : }
137 :
138 2 : return ret;
139 : }
140 : else
141 : {
142 90 : return RunStep(pfnProgress, pProgressData);
143 : }
144 : }
145 :
146 : /************************************************************************/
147 : /* GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm() */
148 : /************************************************************************/
149 :
150 60 : GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm(
151 60 : bool openForMixedRasterVector)
152 : : GDALAbstractPipelineAlgorithm<GDALRasterPipelineStepAlgorithm>(
153 : NAME, DESCRIPTION, HELP_URL,
154 60 : /*standaloneStep=*/false)
155 : {
156 60 : AddInputArgs(openForMixedRasterVector, /* hiddenForCLI = */ true);
157 60 : AddProgressArg();
158 120 : AddArg("pipeline", 0, _("Pipeline string"), &m_pipeline)
159 60 : .SetHiddenForCLI()
160 60 : .SetPositional();
161 60 : AddOutputArgs(/* hiddenForCLI = */ true);
162 :
163 60 : m_stepRegistry.Register<GDALRasterReadAlgorithm>();
164 60 : m_stepRegistry.Register<GDALRasterWriteAlgorithm>();
165 60 : m_stepRegistry.Register<GDALRasterClipAlgorithm>();
166 60 : m_stepRegistry.Register<GDALRasterEditAlgorithm>();
167 60 : m_stepRegistry.Register<GDALRasterReprojectAlgorithm>();
168 60 : }
169 :
170 : /************************************************************************/
171 : /* GDALRasterPipelineAlgorithm::ParseCommandLineArguments() */
172 : /************************************************************************/
173 :
174 44 : bool GDALRasterPipelineAlgorithm::ParseCommandLineArguments(
175 : const std::vector<std::string> &args)
176 : {
177 50 : if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
178 6 : args[0] == "help" || args[0] == "--json-usage"))
179 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
180 :
181 330 : for (const auto &arg : args)
182 : {
183 289 : if (arg.find("--pipeline") == 0)
184 2 : return GDALAlgorithm::ParseCommandLineArguments(args);
185 :
186 : // gdal raster pipeline [--progress] "read in.tif ..."
187 288 : if (arg.find("read ") == 0)
188 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
189 : }
190 :
191 41 : if (!m_steps.empty())
192 : {
193 1 : ReportError(CE_Failure, CPLE_AppDefined,
194 : "ParseCommandLineArguments() can only be called once per "
195 : "instance.");
196 1 : return false;
197 : }
198 :
199 : struct Step
200 : {
201 : std::unique_ptr<GDALRasterPipelineStepAlgorithm> alg{};
202 : std::vector<std::string> args{};
203 : };
204 :
205 80 : std::vector<Step> steps;
206 40 : steps.resize(1);
207 :
208 318 : for (const auto &arg : args)
209 : {
210 279 : if (arg == "--progress")
211 : {
212 1 : m_progressBarRequested = true;
213 1 : continue;
214 : }
215 :
216 278 : auto &curStep = steps.back();
217 :
218 278 : if (arg == "!" || arg == "|")
219 : {
220 60 : if (curStep.alg)
221 : {
222 58 : steps.resize(steps.size() + 1);
223 : }
224 : }
225 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
226 218 : else if (arg == "+step")
227 : {
228 2 : if (curStep.alg)
229 : {
230 1 : steps.resize(steps.size() + 1);
231 : }
232 : }
233 216 : else if (arg.find("+gdal=") == 0)
234 : {
235 1 : const std::string stepName = arg.substr(strlen("+gdal="));
236 1 : curStep.alg = GetStepAlg(stepName);
237 1 : if (!curStep.alg)
238 : {
239 0 : ReportError(CE_Failure, CPLE_AppDefined,
240 : "unknown step name: %s", stepName.c_str());
241 0 : return false;
242 : }
243 : }
244 : #endif
245 215 : else if (!curStep.alg)
246 : {
247 96 : std::string algName = arg;
248 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
249 96 : if (!algName.empty() && algName[0] == '+')
250 1 : algName = algName.substr(1);
251 : #endif
252 96 : curStep.alg = GetStepAlg(algName);
253 96 : if (!curStep.alg)
254 : {
255 1 : ReportError(CE_Failure, CPLE_AppDefined,
256 : "unknown step name: %s", algName.c_str());
257 1 : return false;
258 : }
259 : }
260 : else
261 : {
262 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
263 119 : if (!arg.empty() && arg[0] == '+')
264 : {
265 2 : curStep.args.push_back("--" + arg.substr(1));
266 2 : continue;
267 : }
268 : #endif
269 117 : curStep.args.push_back(arg);
270 : }
271 : }
272 :
273 : // As we initially added a step without alg to bootstrap things, make
274 : // sure to remove it if it hasn't been filled, or the user has terminated
275 : // the pipeline with a '!' separator.
276 39 : if (!steps.back().alg)
277 2 : steps.pop_back();
278 :
279 39 : if (steps.size() < 2)
280 : {
281 2 : ReportError(CE_Failure, CPLE_AppDefined,
282 : "At least 2 steps must be provided");
283 2 : return false;
284 : }
285 :
286 37 : if (steps.front().alg->GetName() != GDALRasterReadAlgorithm::NAME)
287 : {
288 1 : ReportError(CE_Failure, CPLE_AppDefined, "First step should be '%s'",
289 : GDALRasterReadAlgorithm::NAME);
290 1 : return false;
291 : }
292 55 : for (size_t i = 1; i < steps.size() - 1; ++i)
293 : {
294 20 : if (steps[i].alg->GetName() == GDALRasterReadAlgorithm::NAME)
295 : {
296 1 : ReportError(CE_Failure, CPLE_AppDefined,
297 : "Only first step can be '%s'",
298 : GDALRasterReadAlgorithm::NAME);
299 1 : return false;
300 : }
301 : }
302 35 : if (steps.back().alg->GetName() != GDALRasterWriteAlgorithm::NAME)
303 : {
304 1 : ReportError(CE_Failure, CPLE_AppDefined, "Last step should be '%s'",
305 : GDALRasterWriteAlgorithm::NAME);
306 1 : return false;
307 : }
308 86 : for (size_t i = 0; i < steps.size() - 1; ++i)
309 : {
310 53 : if (steps[i].alg->GetName() == GDALRasterWriteAlgorithm::NAME)
311 : {
312 1 : ReportError(CE_Failure, CPLE_AppDefined,
313 : "Only last step can be '%s'",
314 : GDALRasterWriteAlgorithm::NAME);
315 1 : return false;
316 : }
317 : }
318 :
319 33 : if (!m_pipeline.empty())
320 : {
321 : // Propagate input parameters set at the pipeline level to the
322 : // "read" step
323 : {
324 6 : auto &step = steps.front();
325 54 : for (auto &arg : step.alg->GetArgs())
326 : {
327 48 : auto pipelineArg = GetArg(arg->GetName());
328 48 : if (pipelineArg && pipelineArg->IsExplicitlySet())
329 : {
330 2 : arg->SetSkipIfAlreadySet(true);
331 2 : arg->SetFrom(*pipelineArg);
332 : }
333 : }
334 : }
335 :
336 : // Same with "write" step
337 : {
338 6 : auto &step = steps.back();
339 60 : for (auto &arg : step.alg->GetArgs())
340 : {
341 54 : auto pipelineArg = GetArg(arg->GetName());
342 54 : if (pipelineArg && pipelineArg->IsExplicitlySet())
343 : {
344 1 : arg->SetSkipIfAlreadySet(true);
345 1 : arg->SetFrom(*pipelineArg);
346 : }
347 : }
348 : }
349 : }
350 :
351 : // Parse each step, but without running the validation
352 106 : for (const auto &step : steps)
353 : {
354 79 : step.alg->m_skipValidationInParseCommandLine = true;
355 79 : if (!step.alg->ParseCommandLineArguments(step.args))
356 6 : return false;
357 : }
358 :
359 : // Evaluate "input" argument of "read" step, together with the "output"
360 : // argument of the "write" step, in case they point to the same dataset.
361 27 : auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
362 54 : if (inputArg && inputArg->IsExplicitlySet() &&
363 27 : inputArg->GetType() == GAAT_DATASET)
364 : {
365 27 : steps.front().alg->ProcessDatasetArg(inputArg, steps.back().alg.get());
366 : }
367 :
368 93 : for (const auto &step : steps)
369 : {
370 67 : if (!step.alg->ValidateArguments())
371 1 : return false;
372 : }
373 :
374 92 : for (auto &step : steps)
375 66 : m_steps.push_back(std::move(step.alg));
376 :
377 26 : return true;
378 : }
379 :
380 : /************************************************************************/
381 : /* GDALRasterPipelineAlgorithm::GetUsageForCLI() */
382 : /************************************************************************/
383 :
384 2 : std::string GDALRasterPipelineAlgorithm::GetUsageForCLI(
385 : bool shortUsage, const UsageOptions &usageOptions) const
386 : {
387 2 : std::string ret = GDALAlgorithm::GetUsageForCLI(shortUsage, usageOptions);
388 2 : if (shortUsage)
389 1 : return ret;
390 :
391 : ret += "\n<PIPELINE> is of the form: read [READ-OPTIONS] "
392 1 : "( ! <STEP-NAME> [STEP-OPTIONS] )* ! write [WRITE-OPTIONS]\n";
393 1 : ret += '\n';
394 1 : ret += "Example: 'gdal raster pipeline --progress ! read in.tif ! \\\n";
395 1 : ret += " reproject --dst-crs=EPSG:32632 ! ";
396 1 : ret += "write out.tif --overwrite'\n";
397 1 : ret += '\n';
398 1 : ret += "Potential steps are:\n";
399 :
400 1 : UsageOptions stepUsageOptions;
401 1 : stepUsageOptions.isPipelineStep = true;
402 :
403 6 : for (const std::string &name : m_stepRegistry.GetNames())
404 : {
405 10 : auto alg = GetStepAlg(name);
406 5 : auto [options, maxOptLen] = alg->GetArgNamesForCLI();
407 5 : stepUsageOptions.maxOptLen =
408 5 : std::max(stepUsageOptions.maxOptLen, maxOptLen);
409 : }
410 :
411 : {
412 1 : const auto name = GDALRasterReadAlgorithm::NAME;
413 1 : ret += '\n';
414 2 : auto alg = GetStepAlg(name);
415 2 : alg->SetCallPath({name});
416 1 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
417 : }
418 6 : for (const std::string &name : m_stepRegistry.GetNames())
419 : {
420 9 : if (name != GDALRasterReadAlgorithm::NAME &&
421 4 : name != GDALRasterWriteAlgorithm::NAME)
422 : {
423 3 : ret += '\n';
424 3 : auto alg = GetStepAlg(name);
425 6 : alg->SetCallPath({name});
426 3 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
427 : }
428 : }
429 : {
430 1 : const auto name = GDALRasterWriteAlgorithm::NAME;
431 1 : ret += '\n';
432 2 : auto alg = GetStepAlg(name);
433 2 : alg->SetCallPath({name});
434 1 : ret += alg->GetUsageForCLI(shortUsage, stepUsageOptions);
435 : }
436 :
437 1 : return ret;
438 : }
439 :
440 : //! @endcond
|