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