Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster/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 : #ifndef GDALALG_ABSTRACT_PIPELINE_INCLUDED
14 : #define GDALALG_ABSTRACT_PIPELINE_INCLUDED
15 :
16 : //! @cond Doxygen_Suppress
17 :
18 : #include "cpl_conv.h"
19 : #include "cpl_json.h"
20 : #include "gdalalgorithm.h"
21 : #include "gdal_priv.h"
22 :
23 : #include <algorithm>
24 :
25 : template <class StepAlgorithm>
26 : class GDALAbstractPipelineAlgorithm CPL_NON_FINAL : public StepAlgorithm
27 : {
28 : public:
29 : std::vector<std::string> GetAutoComplete(std::vector<std::string> &args,
30 : bool lastWordIsComplete,
31 : bool /* showAllOptions*/) override;
32 :
33 : bool Finalize() override;
34 :
35 : std::string GetUsageAsJSON() const override;
36 :
37 : /* cppcheck-suppress functionStatic */
38 0 : void SetDataset(GDALDataset *)
39 : {
40 0 : }
41 :
42 : protected:
43 170 : GDALAbstractPipelineAlgorithm(const std::string &name,
44 : const std::string &description,
45 : const std::string &helpURL,
46 : bool standaloneStep)
47 170 : : StepAlgorithm(name, description, helpURL, standaloneStep)
48 : {
49 170 : }
50 :
51 170 : ~GDALAbstractPipelineAlgorithm() override
52 : {
53 : // Destroy steps in the reverse order they have been constructed,
54 : // as a step can create object that depends on the validity of
55 : // objects of previous steps, and while cleaning them it needs those
56 : // prior objects to be still alive.
57 : // Typically for "gdal vector pipeline read ... ! sql ..."
58 390 : for (auto it = std::rbegin(m_steps); it != std::rend(m_steps); it++)
59 : {
60 220 : it->reset();
61 : }
62 340 : }
63 :
64 : virtual GDALArgDatasetValue &GetOutputDataset() = 0;
65 :
66 : std::string m_pipeline{};
67 :
68 : std::unique_ptr<StepAlgorithm> GetStepAlg(const std::string &name) const;
69 :
70 : GDALAlgorithmRegistry m_stepRegistry{};
71 : std::vector<std::unique_ptr<StepAlgorithm>> m_steps{};
72 :
73 : private:
74 : bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override;
75 : };
76 :
77 : /************************************************************************/
78 : /* GDALAbstractPipelineAlgorithm::GetStepAlg() */
79 : /************************************************************************/
80 :
81 : template <class StepAlgorithm>
82 : std::unique_ptr<StepAlgorithm>
83 507 : GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetStepAlg(
84 : const std::string &name) const
85 : {
86 1014 : auto alg = m_stepRegistry.Instantiate(name);
87 : return std::unique_ptr<StepAlgorithm>(
88 1014 : cpl::down_cast<StepAlgorithm *>(alg.release()));
89 : }
90 :
91 : /************************************************************************/
92 : /* GDALAbstractPipelineAlgorithm::GetAutoComplete() */
93 : /************************************************************************/
94 :
95 : template <class StepAlgorithm>
96 : std::vector<std::string>
97 17 : GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetAutoComplete(
98 : std::vector<std::string> &args, bool lastWordIsComplete,
99 : bool /* showAllOptions*/)
100 : {
101 17 : std::vector<std::string> ret;
102 17 : if (args.size() <= 1)
103 : {
104 6 : if (args.empty() || args.front() != "read")
105 4 : ret.push_back("read");
106 : }
107 23 : else if (args.back() == "!" ||
108 23 : (args[args.size() - 2] == "!" && !GetStepAlg(args.back())))
109 : {
110 70 : for (const std::string &name : m_stepRegistry.GetNames())
111 : {
112 66 : if (name != "read")
113 : {
114 62 : ret.push_back(name);
115 : }
116 : }
117 : }
118 : else
119 : {
120 14 : std::string lastStep = "read";
121 14 : std::vector<std::string> lastArgs;
122 24 : for (size_t i = 1; i < args.size(); ++i)
123 : {
124 17 : lastArgs.push_back(args[i]);
125 17 : if (i + 1 < args.size() && args[i] == "!")
126 : {
127 6 : ++i;
128 6 : lastArgs.clear();
129 6 : lastStep = args[i];
130 : }
131 : }
132 :
133 14 : auto curAlg = GetStepAlg(lastStep);
134 7 : if (curAlg)
135 : {
136 7 : ret = curAlg->GetAutoComplete(lastArgs, lastWordIsComplete,
137 : /* showAllOptions = */ false);
138 : }
139 : }
140 17 : return ret;
141 : }
142 :
143 : /************************************************************************/
144 : /* GDALAbstractPipelineAlgorithm::RunStep() */
145 : /************************************************************************/
146 :
147 : template <class StepAlgorithm>
148 95 : bool GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep(
149 : GDALProgressFunc pfnProgress, void *pProgressData)
150 : {
151 95 : if (m_steps.empty())
152 : {
153 : // If invoked programmatically, not from the command line.
154 :
155 36 : if (m_pipeline.empty())
156 : {
157 2 : StepAlgorithm::ReportError(CE_Failure, CPLE_AppDefined,
158 : "'pipeline' argument not set");
159 5 : return false;
160 : }
161 :
162 34 : const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
163 34 : if (!this->ParseCommandLineArguments(aosTokens))
164 3 : return false;
165 : }
166 :
167 : // Handle output to GDALG file
168 90 : if (!m_steps.empty() && m_steps.back()->GetName() == "write")
169 : {
170 90 : if (m_steps.back()->IsGDALGOutput())
171 : {
172 6 : const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
173 6 : const auto &filename =
174 : outputArg->GDALAlgorithmArg::template Get<GDALArgDatasetValue>()
175 6 : .GetName();
176 6 : const char *pszType = "";
177 6 : if (GDALDoesFileOrDatasetExist(filename.c_str(), &pszType))
178 : {
179 1 : const auto overwriteArg =
180 1 : m_steps.back()->GetArg(GDAL_ARG_NAME_OVERWRITE);
181 1 : if (overwriteArg && overwriteArg->GetType() == GAAT_BOOLEAN)
182 : {
183 1 : if (!overwriteArg->GDALAlgorithmArg::template Get<bool>())
184 : {
185 0 : CPLError(CE_Failure, CPLE_AppDefined,
186 : "%s '%s' already exists. Specify the "
187 : "--overwrite option to overwrite it.",
188 : pszType, filename.c_str());
189 0 : return false;
190 : }
191 : }
192 : }
193 :
194 12 : std::string osCommandLine;
195 :
196 24 : for (const auto &path : GDALAlgorithm::m_callPath)
197 : {
198 18 : if (!osCommandLine.empty())
199 12 : osCommandLine += ' ';
200 18 : osCommandLine += path;
201 : }
202 :
203 : // Do not include the last step
204 18 : for (size_t i = 0; i + 1 < m_steps.size(); ++i)
205 : {
206 12 : const auto &step = m_steps[i];
207 12 : if (!step->IsNativelyStreamingCompatible())
208 : {
209 2 : GDALAlgorithm::ReportError(
210 : CE_Warning, CPLE_AppDefined,
211 : "Step %s is not natively streaming compatible, and "
212 : "may cause significant processing time at opening",
213 2 : step->GDALAlgorithm::GetName().c_str());
214 : }
215 :
216 12 : if (i > 0)
217 6 : osCommandLine += " !";
218 25 : for (const auto &path : step->GDALAlgorithm::m_callPath)
219 : {
220 13 : if (!osCommandLine.empty())
221 13 : osCommandLine += ' ';
222 13 : osCommandLine += path;
223 : }
224 :
225 136 : for (const auto &arg : step->GetArgs())
226 : {
227 124 : if (arg->IsExplicitlySet())
228 : {
229 10 : osCommandLine += ' ';
230 10 : std::string strArg;
231 10 : if (!arg->Serialize(strArg))
232 : {
233 0 : CPLError(CE_Failure, CPLE_AppDefined,
234 : "Cannot serialize argument %s",
235 0 : arg->GetName().c_str());
236 0 : return false;
237 : }
238 10 : osCommandLine += strArg;
239 : }
240 : }
241 : }
242 :
243 6 : return GDALAlgorithm::SaveGDALG(filename, osCommandLine);
244 : }
245 :
246 84 : const auto outputFormatArg =
247 84 : m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT);
248 84 : const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
249 84 : if (outputArg && outputArg->GetType() == GAAT_DATASET &&
250 : outputArg->IsExplicitlySet())
251 : {
252 84 : const auto &outputFile =
253 : outputArg
254 : ->GDALAlgorithmArg::template Get<GDALArgDatasetValue>();
255 : bool isVRTOutput;
256 84 : if (outputFormatArg && outputFormatArg->GetType() == GAAT_STRING &&
257 : outputFormatArg->IsExplicitlySet())
258 : {
259 27 : const auto &val =
260 : outputFormatArg
261 : ->GDALAlgorithmArg::template Get<std::string>();
262 27 : isVRTOutput = EQUAL(val.c_str(), "vrt");
263 : }
264 : else
265 : {
266 57 : isVRTOutput = EQUAL(
267 : CPLGetExtensionSafe(outputFile.GetName().c_str()).c_str(),
268 : "vrt");
269 : }
270 97 : if (isVRTOutput && !outputFile.GetName().empty() &&
271 13 : m_steps.size() > 3)
272 : {
273 1 : StepAlgorithm::ReportError(
274 : CE_Failure, CPLE_NotSupported,
275 : "VRT output is not supported when there are more than 3 "
276 : "steps. Consider using the GDALG driver (files with "
277 : ".gdalg.json extension)");
278 1 : return false;
279 : }
280 83 : if (isVRTOutput)
281 : {
282 24 : for (const auto &step : m_steps)
283 : {
284 24 : if (!step->m_outputVRTCompatible)
285 : {
286 12 : step->ReportError(
287 : CE_Failure, CPLE_NotSupported,
288 : "VRT output is not supported. Consider using the "
289 : "GDALG driver instead (files with .gdalg.json "
290 : "extension)");
291 12 : return false;
292 : }
293 : }
294 : }
295 : }
296 : }
297 :
298 71 : if (GDALAlgorithm::m_executionForStreamOutput)
299 : {
300 : // For security reasons, to avoid that reading a .gdalg.json file writes
301 : // a file on the file system.
302 44 : for (const auto &step : m_steps)
303 : {
304 46 : if (step->GetName() == "write" &&
305 14 : !EQUAL(step->m_format.c_str(), "stream"))
306 : {
307 2 : StepAlgorithm::ReportError(CE_Failure, CPLE_AppDefined,
308 : "in streamed execution, --format "
309 : "stream should be used");
310 2 : return false;
311 : }
312 : }
313 : }
314 :
315 69 : int countPipelinesWithProgress = 1; // write
316 93 : for (size_t i = 1; i + 1 < m_steps.size(); ++i)
317 : {
318 24 : if (!m_steps[i]->IsNativelyStreamingCompatible())
319 5 : ++countPipelinesWithProgress;
320 : }
321 :
322 69 : GDALDataset *poCurDS = nullptr;
323 69 : int iCurPipelineWithProgress = 0;
324 217 : for (size_t i = 0; i < m_steps.size(); ++i)
325 : {
326 155 : auto &step = m_steps[i];
327 155 : if (i > 0)
328 : {
329 : if constexpr (std::is_same_v<decltype(step->m_inputDataset),
330 : GDALArgDatasetValue>)
331 : {
332 46 : if (step->m_inputDataset.GetDatasetRef())
333 : {
334 : // Shouldn't happen
335 0 : StepAlgorithm::ReportError(
336 : CE_Failure, CPLE_AppDefined,
337 : "Step nr %d (%s) has already an input dataset",
338 0 : static_cast<int>(i), step->GetName().c_str());
339 4 : return false;
340 : }
341 46 : step->m_inputDataset.Set(poCurDS);
342 : }
343 : else if constexpr (std::is_same_v<decltype(step->m_inputDataset),
344 : std::vector<GDALArgDatasetValue>>)
345 : {
346 40 : if (!step->m_inputDataset.empty() &&
347 0 : step->m_inputDataset[0].GetDatasetRef())
348 : {
349 : // Shouldn't happen
350 0 : StepAlgorithm::ReportError(
351 : CE_Failure, CPLE_AppDefined,
352 : "Step nr %d (%s) has already an input dataset",
353 0 : static_cast<int>(i), step->GetName().c_str());
354 3 : return false;
355 : }
356 40 : step->m_inputDataset.clear();
357 40 : step->m_inputDataset.resize(1);
358 40 : step->m_inputDataset[0].Set(poCurDS);
359 : }
360 : }
361 155 : if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef())
362 : {
363 : // Shouldn't happen
364 2 : StepAlgorithm::ReportError(
365 : CE_Failure, CPLE_AppDefined,
366 : "Step nr %d (%s) has already an output dataset",
367 2 : static_cast<int>(i), step->GetName().c_str());
368 2 : return false;
369 : }
370 :
371 0 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
372 153 : nullptr, GDALDestroyScaledProgress);
373 153 : if (i == m_steps.size() - 1 || !step->IsNativelyStreamingCompatible())
374 : {
375 67 : pScaledData.reset(GDALCreateScaledProgress(
376 : iCurPipelineWithProgress /
377 : static_cast<double>(countPipelinesWithProgress),
378 67 : (iCurPipelineWithProgress + 1) /
379 : static_cast<double>(countPipelinesWithProgress),
380 : pfnProgress, pProgressData));
381 67 : ++iCurPipelineWithProgress;
382 : }
383 153 : if (!step->Run(pScaledData ? GDALScaledProgress : nullptr,
384 : pScaledData.get()))
385 : {
386 5 : return false;
387 : }
388 148 : poCurDS = step->m_outputDataset.GetDatasetRef();
389 148 : if (!poCurDS)
390 : {
391 0 : StepAlgorithm::ReportError(
392 : CE_Failure, CPLE_AppDefined,
393 : "Step nr %d (%s) failed to produce an output dataset",
394 0 : static_cast<int>(i), step->GetName().c_str());
395 0 : return false;
396 : }
397 : }
398 :
399 62 : if (pfnProgress)
400 6 : pfnProgress(1.0, "", pProgressData);
401 :
402 62 : if (!GetOutputDataset().GetDatasetRef())
403 : {
404 62 : GetOutputDataset().Set(poCurDS);
405 : }
406 :
407 62 : return true;
408 : }
409 :
410 : /************************************************************************/
411 : /* GDALAbstractPipelineAlgorithm::Finalize() */
412 : /************************************************************************/
413 :
414 : template <class StepAlgorithm>
415 58 : bool GDALAbstractPipelineAlgorithm<StepAlgorithm>::Finalize()
416 : {
417 58 : bool ret = GDALAlgorithm::Finalize();
418 179 : for (auto &step : m_steps)
419 : {
420 121 : ret = step->Finalize() && ret;
421 : }
422 58 : return ret;
423 : }
424 :
425 : /************************************************************************/
426 : /* GDALAbstractPipelineAlgorithm::GetUsageAsJSON() */
427 : /************************************************************************/
428 :
429 : template <class StepAlgorithm>
430 7 : std::string GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetUsageAsJSON() const
431 : {
432 14 : CPLJSONDocument oDoc;
433 7 : CPL_IGNORE_RET_VAL(oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON()));
434 :
435 14 : CPLJSONArray jPipelineSteps;
436 129 : for (const std::string &name : m_stepRegistry.GetNames())
437 : {
438 244 : auto alg = GetStepAlg(name);
439 122 : CPLJSONDocument oStepDoc;
440 122 : CPL_IGNORE_RET_VAL(oStepDoc.LoadMemory(alg->GetUsageAsJSON()));
441 122 : jPipelineSteps.Add(oStepDoc.GetRoot());
442 : }
443 7 : oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
444 :
445 14 : return oDoc.SaveAsString();
446 : }
447 :
448 : //! @endcond
449 :
450 : #endif
|