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