/src/gdal/apps/gdalalg_raster_calc.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: "gdal raster calc" subcommand |
5 | | * Author: Daniel Baston |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2025, ISciences LLC |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "gdalalg_raster_calc.h" |
14 | | |
15 | | #include "../frmts/vrt/gdal_vrt.h" |
16 | | #include "../frmts/vrt/vrtdataset.h" |
17 | | |
18 | | #include "cpl_float.h" |
19 | | #include "cpl_vsi_virtual.h" |
20 | | #include "gdal_priv.h" |
21 | | #include "gdal_utils.h" |
22 | | #include "vrtdataset.h" |
23 | | |
24 | | #include <algorithm> |
25 | | #include <cmath> |
26 | | #include <limits> |
27 | | #include <optional> |
28 | | |
29 | | //! @cond Doxygen_Suppress |
30 | | |
31 | | #ifndef _ |
32 | 0 | #define _(x) (x) |
33 | | #endif |
34 | | |
35 | | constexpr const char *DEFAULT_SOURCE_NAME = "X"; |
36 | | constexpr const char *PIPELINE_INPUT_DSN = ""; |
37 | | |
38 | | struct GDALCalcOptions |
39 | | { |
40 | | GDALDataType dstType{GDT_Unknown}; |
41 | | bool checkCRS{true}; |
42 | | bool checkExtent{true}; |
43 | | }; |
44 | | |
45 | | static bool MatchIsCompleteVariableNameWithNoIndex(const std::string &str, |
46 | | size_t from, size_t to) |
47 | 0 | { |
48 | 0 | if (to < str.size()) |
49 | 0 | { |
50 | | // If the character after the end of the match is: |
51 | | // * alphanumeric or _ : we've matched only part of a variable name |
52 | | // * [ : we've matched a variable that already has an index |
53 | | // * ( : we've matched a function name |
54 | 0 | if (std::isalnum(str[to]) || str[to] == '_' || str[to] == '[' || |
55 | 0 | str[to] == '(') |
56 | 0 | { |
57 | 0 | return false; |
58 | 0 | } |
59 | 0 | } |
60 | 0 | if (from > 0) |
61 | 0 | { |
62 | | // If the character before the start of the match is alphanumeric or _, |
63 | | // we've matched only part of a variable name. |
64 | 0 | if (std::isalnum(str[from - 1]) || str[from - 1] == '_') |
65 | 0 | { |
66 | 0 | return false; |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | 0 | return true; |
71 | 0 | } |
72 | | |
73 | | /** |
74 | | * Add a band subscript to all instances of a specified variable that |
75 | | * do not already have such a subscript. For example, "X" would be |
76 | | * replaced with "X[3]" but "X[1]" would be left untouched. |
77 | | */ |
78 | | static std::string SetBandIndices(const std::string &origExpression, |
79 | | const std::string &variable, int band, |
80 | | bool &expressionChanged) |
81 | 0 | { |
82 | 0 | std::string expression = origExpression; |
83 | 0 | expressionChanged = false; |
84 | |
|
85 | 0 | std::string::size_type seekPos = 0; |
86 | 0 | auto pos = expression.find(variable, seekPos); |
87 | 0 | while (pos != std::string::npos) |
88 | 0 | { |
89 | 0 | auto end = pos + variable.size(); |
90 | |
|
91 | 0 | if (MatchIsCompleteVariableNameWithNoIndex(expression, pos, end)) |
92 | 0 | { |
93 | | // No index specified for variable |
94 | 0 | expression = expression.substr(0, pos + variable.size()) + '[' + |
95 | 0 | std::to_string(band) + ']' + expression.substr(end); |
96 | 0 | expressionChanged = true; |
97 | 0 | } |
98 | |
|
99 | 0 | seekPos = end; |
100 | 0 | pos = expression.find(variable, seekPos); |
101 | 0 | } |
102 | |
|
103 | 0 | return expression; |
104 | 0 | } |
105 | | |
106 | | static bool PosIsAggregateFunctionArgument(const std::string &expression, |
107 | | size_t pos) |
108 | 0 | { |
109 | | // If this position is a function argument, we should be able to |
110 | | // scan backwards for a ( and find only variable names, literals or commas. |
111 | 0 | while (pos != 0) |
112 | 0 | { |
113 | 0 | const char c = expression[pos]; |
114 | 0 | if (c == '(') |
115 | 0 | { |
116 | 0 | pos--; |
117 | 0 | break; |
118 | 0 | } |
119 | 0 | if (!(isspace(c) || isalnum(c) || c == ',' || c == '.' || c == '[' || |
120 | 0 | c == ']' || c == '_')) |
121 | 0 | { |
122 | 0 | return false; |
123 | 0 | } |
124 | 0 | pos--; |
125 | 0 | } |
126 | | |
127 | | // Now what we've found the (, the preceding characters should be an |
128 | | // aggregate function name |
129 | 0 | if (pos < 2) |
130 | 0 | { |
131 | 0 | return false; |
132 | 0 | } |
133 | | |
134 | 0 | if (STARTS_WITH_CI(expression.c_str() + (pos - 2), "avg") || |
135 | 0 | STARTS_WITH_CI(expression.c_str() + (pos - 2), "sum") || |
136 | 0 | STARTS_WITH_CI(expression.c_str() + (pos - 2), "min") || |
137 | 0 | STARTS_WITH_CI(expression.c_str() + (pos - 2), "max")) |
138 | 0 | { |
139 | 0 | return true; |
140 | 0 | } |
141 | | |
142 | 0 | return false; |
143 | 0 | } |
144 | | |
145 | | /** |
146 | | * Replace X by X[1],X[2],...X[n] |
147 | | */ |
148 | | static std::string |
149 | | SetBandIndicesFlattenedExpression(const std::string &origExpression, |
150 | | const std::string &variable, int nBands) |
151 | 0 | { |
152 | 0 | std::string expression = origExpression; |
153 | |
|
154 | 0 | std::string::size_type seekPos = 0; |
155 | 0 | auto pos = expression.find(variable, seekPos); |
156 | 0 | while (pos != std::string::npos) |
157 | 0 | { |
158 | 0 | auto end = pos + variable.size(); |
159 | |
|
160 | 0 | if (MatchIsCompleteVariableNameWithNoIndex(expression, pos, end) && |
161 | 0 | PosIsAggregateFunctionArgument(expression, pos)) |
162 | 0 | { |
163 | 0 | std::string newExpr = expression.substr(0, pos); |
164 | 0 | for (int i = 1; i <= nBands; ++i) |
165 | 0 | { |
166 | 0 | if (i > 1) |
167 | 0 | newExpr += ','; |
168 | 0 | newExpr += variable; |
169 | 0 | newExpr += '['; |
170 | 0 | newExpr += std::to_string(i); |
171 | 0 | newExpr += ']'; |
172 | 0 | } |
173 | 0 | const size_t oldExprSize = expression.size(); |
174 | 0 | newExpr += expression.substr(end); |
175 | 0 | expression = std::move(newExpr); |
176 | 0 | end += expression.size() - oldExprSize; |
177 | 0 | } |
178 | |
|
179 | 0 | seekPos = end; |
180 | 0 | pos = expression.find(variable, seekPos); |
181 | 0 | } |
182 | |
|
183 | 0 | return expression; |
184 | 0 | } |
185 | | |
186 | | struct SourceProperties |
187 | | { |
188 | | int nBands{0}; |
189 | | int nX{0}; |
190 | | int nY{0}; |
191 | | bool hasGT{false}; |
192 | | GDALGeoTransform gt{}; |
193 | | OGRSpatialReferenceRefCountedPtr srs{}; |
194 | | std::vector<std::optional<double>> noData{}; |
195 | | GDALDataType eDT{GDT_Unknown}; |
196 | | }; |
197 | | |
198 | | static std::optional<SourceProperties> |
199 | | UpdateSourceProperties(SourceProperties &out, GDALDataset *ds, |
200 | | const GDALCalcOptions &options) |
201 | 0 | { |
202 | 0 | SourceProperties source; |
203 | 0 | bool srsMismatch = false; |
204 | 0 | bool extentMismatch = false; |
205 | 0 | bool dimensionMismatch = false; |
206 | |
|
207 | 0 | { |
208 | 0 | source.nBands = ds->GetRasterCount(); |
209 | 0 | source.nX = ds->GetRasterXSize(); |
210 | 0 | source.nY = ds->GetRasterYSize(); |
211 | 0 | source.noData.resize(source.nBands); |
212 | |
|
213 | 0 | if (options.checkExtent) |
214 | 0 | { |
215 | 0 | ds->GetGeoTransform(source.gt); |
216 | 0 | } |
217 | |
|
218 | 0 | if (options.checkCRS && out.srs) |
219 | 0 | { |
220 | 0 | const OGRSpatialReference *srs = ds->GetSpatialRef(); |
221 | 0 | srsMismatch = srs && !srs->IsSame(out.srs.get()); |
222 | 0 | } |
223 | | |
224 | | // Store the source data type if it is the same for all bands in the source |
225 | 0 | bool bandsHaveSameType = true; |
226 | 0 | for (int i = 1; i <= source.nBands; ++i) |
227 | 0 | { |
228 | 0 | GDALRasterBand *band = ds->GetRasterBand(i); |
229 | |
|
230 | 0 | if (i == 1) |
231 | 0 | { |
232 | 0 | source.eDT = band->GetRasterDataType(); |
233 | 0 | } |
234 | 0 | else if (bandsHaveSameType && |
235 | 0 | source.eDT != band->GetRasterDataType()) |
236 | 0 | { |
237 | 0 | source.eDT = GDT_Unknown; |
238 | 0 | bandsHaveSameType = false; |
239 | 0 | } |
240 | |
|
241 | 0 | int success; |
242 | 0 | double noData = band->GetNoDataValue(&success); |
243 | 0 | if (success) |
244 | 0 | { |
245 | 0 | source.noData[i - 1] = noData; |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } |
249 | |
|
250 | 0 | if (source.nX != out.nX || source.nY != out.nY) |
251 | 0 | { |
252 | 0 | dimensionMismatch = true; |
253 | 0 | } |
254 | |
|
255 | 0 | if (source.gt.xorig != out.gt.xorig || source.gt.xrot != out.gt.xrot || |
256 | 0 | source.gt.yorig != out.gt.yorig || source.gt.yrot != out.gt.yrot) |
257 | 0 | { |
258 | 0 | extentMismatch = true; |
259 | 0 | } |
260 | 0 | if (source.gt.xscale != out.gt.xscale || source.gt.yscale != out.gt.yscale) |
261 | 0 | { |
262 | | // Resolutions are different. Are the extents the same? |
263 | 0 | double xmaxOut = |
264 | 0 | out.gt.xorig + out.nX * out.gt.xscale + out.nY * out.gt.xrot; |
265 | 0 | double yminOut = |
266 | 0 | out.gt.yorig + out.nX * out.gt.yrot + out.nY * out.gt.yscale; |
267 | |
|
268 | 0 | double xmax = source.gt.xorig + source.nX * source.gt.xscale + |
269 | 0 | source.nY * source.gt.xrot; |
270 | 0 | double ymin = source.gt.yorig + source.nX * source.gt.yrot + |
271 | 0 | source.nY * source.gt.yscale; |
272 | | |
273 | | // Max allowable extent misalignment, expressed as fraction of a pixel |
274 | 0 | constexpr double EXTENT_RTOL = 1e-3; |
275 | |
|
276 | 0 | if (std::abs(xmax - xmaxOut) > |
277 | 0 | EXTENT_RTOL * std::abs(source.gt.xscale) || |
278 | 0 | std::abs(ymin - yminOut) > EXTENT_RTOL * std::abs(source.gt.yscale)) |
279 | 0 | { |
280 | 0 | extentMismatch = true; |
281 | 0 | } |
282 | 0 | } |
283 | |
|
284 | 0 | if (options.checkExtent && extentMismatch) |
285 | 0 | { |
286 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
287 | 0 | "Input extents are inconsistent."); |
288 | 0 | return std::nullopt; |
289 | 0 | } |
290 | | |
291 | 0 | if (!options.checkExtent && dimensionMismatch) |
292 | 0 | { |
293 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
294 | 0 | "Inputs do not have the same dimensions."); |
295 | 0 | return std::nullopt; |
296 | 0 | } |
297 | | |
298 | | // Find a common resolution |
299 | 0 | if (source.nX > out.nX) |
300 | 0 | { |
301 | 0 | auto dx = CPLGreatestCommonDivisor(out.gt.xscale, source.gt.xscale); |
302 | 0 | if (std::fabs(dx) < std::numeric_limits<double>::min()) |
303 | 0 | { |
304 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
305 | 0 | "Failed to find common resolution for inputs."); |
306 | 0 | return std::nullopt; |
307 | 0 | } |
308 | 0 | out.nX = static_cast<int>( |
309 | 0 | std::round(static_cast<double>(out.nX) * out.gt.xscale / dx)); |
310 | 0 | out.gt.xscale = dx; |
311 | 0 | } |
312 | 0 | if (source.nY > out.nY) |
313 | 0 | { |
314 | 0 | auto dy = CPLGreatestCommonDivisor(out.gt.yscale, source.gt.yscale); |
315 | 0 | if (std::fabs(dy) < std::numeric_limits<double>::min()) |
316 | 0 | { |
317 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
318 | 0 | "Failed to find common resolution for inputs."); |
319 | 0 | return std::nullopt; |
320 | 0 | } |
321 | 0 | out.nY = static_cast<int>( |
322 | 0 | std::round(static_cast<double>(out.nY) * out.gt.yscale / dy)); |
323 | 0 | out.gt.yscale = dy; |
324 | 0 | } |
325 | | |
326 | 0 | if (srsMismatch) |
327 | 0 | { |
328 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
329 | 0 | "Input spatial reference systems are inconsistent."); |
330 | 0 | return std::nullopt; |
331 | 0 | } |
332 | | |
333 | 0 | return source; |
334 | 0 | } |
335 | | |
336 | | /** Add one or more derived bands to a VRTDataset, representing the evaluation |
337 | | * of a single expression |
338 | | * |
339 | | * @param poDS VRT dataset |
340 | | * @param bandType the type of the band(s) to create |
341 | | * @param expression Expression for which band(s) should be added |
342 | | * @param dialect Expression dialect |
343 | | * @param flatten Generate a single band output raster per expression, even if |
344 | | * input datasets are multiband. |
345 | | * @param noDataText nodata value to use for the created band, or "none", or "" |
346 | | * @param pixelFunctionArguments Pixel function arguments. |
347 | | * @param sources Mapping of source names to DSNs |
348 | | * @param sourceProps Mapping of source names to properties |
349 | | * @param fakeSourceFilename If not empty, used instead of real input filenames. |
350 | | * @param pipelineInputSource A pointer to a dataset representing pipeline input. |
351 | | * @return true if the band(s) were added, false otherwise |
352 | | */ |
353 | | static bool CreateVRTDerivedBand( |
354 | | VRTDataset *poDS, GDALDataType bandType, const std::string &expression, |
355 | | const std::string &dialect, bool flatten, const std::string &noDataText, |
356 | | const std::vector<std::string> &pixelFunctionArguments, |
357 | | const std::map<std::string, std::string> &sources, |
358 | | const std::map<std::string, SourceProperties> &sourceProps, |
359 | | const std::string &fakeSourceFilename, GDALDataset *pipelineInputSource) |
360 | 0 | { |
361 | 0 | const char *pszVRTFilename = poDS->GetDescription(); |
362 | |
|
363 | 0 | const int nPrevBands = poDS->GetRasterCount(); |
364 | 0 | const int nXOut = poDS->GetRasterXSize(); |
365 | 0 | const int nYOut = poDS->GetRasterYSize(); |
366 | |
|
367 | 0 | CPLStringList papszBandArgs; |
368 | 0 | papszBandArgs.SetNameValue("subclass", "VRTDerivedRasterBand"); |
369 | |
|
370 | 0 | int nOutBands = 1; // By default, each expression produces a single output |
371 | | // band. When processing the expression below, we may |
372 | | // discover that the expression produces multiple bands, |
373 | | // in which case this will be updated. |
374 | |
|
375 | 0 | for (int nOutBand = 1; nOutBand <= nOutBands; nOutBand++) |
376 | 0 | { |
377 | | // Copy the expression for each output band, because we may modify it |
378 | | // when adding band indices (e.g., X -> X[1]) to the variables in the |
379 | | // expression. |
380 | 0 | std::string bandExpression = expression; |
381 | |
|
382 | 0 | if (poDS->AddBand(bandType == GDT_Unknown ? GDT_Float64 : bandType, |
383 | 0 | papszBandArgs) != CE_None) |
384 | 0 | { |
385 | 0 | return false; |
386 | 0 | } |
387 | 0 | VRTDerivedRasterBand *poBand = cpl::down_cast<VRTDerivedRasterBand *>( |
388 | 0 | poDS->GetRasterBand(nPrevBands + nOutBand)); |
389 | |
|
390 | 0 | std::optional<double> dstNoData; |
391 | 0 | bool autoSelectNoDataValue = false; |
392 | 0 | if (noDataText.empty()) |
393 | 0 | { |
394 | 0 | autoSelectNoDataValue = true; |
395 | 0 | } |
396 | 0 | else if (noDataText != "none") |
397 | 0 | { |
398 | 0 | if (auto parsed = cpl::strict_parse<double>(noDataText); |
399 | 0 | parsed.has_value()) |
400 | 0 | { |
401 | 0 | dstNoData = parsed.value(); |
402 | 0 | } |
403 | 0 | else |
404 | 0 | { |
405 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
406 | 0 | "Invalid NoData value: %s", noDataText.c_str()); |
407 | 0 | return false; |
408 | 0 | } |
409 | 0 | } |
410 | | |
411 | 0 | for (const auto &[source_name, dsn] : sources) |
412 | 0 | { |
413 | 0 | auto it = sourceProps.find(source_name); |
414 | 0 | CPLAssert(it != sourceProps.end()); |
415 | 0 | const auto &props = it->second; |
416 | |
|
417 | 0 | bool expressionAppliedPerBand = false; |
418 | 0 | if (dialect == "builtin") |
419 | 0 | { |
420 | 0 | expressionAppliedPerBand = !flatten; |
421 | 0 | } |
422 | 0 | else |
423 | 0 | { |
424 | 0 | const int nDefaultInBand = std::min(props.nBands, nOutBand); |
425 | |
|
426 | 0 | if (flatten) |
427 | 0 | { |
428 | 0 | bandExpression = SetBandIndicesFlattenedExpression( |
429 | 0 | bandExpression, source_name, props.nBands); |
430 | 0 | } |
431 | |
|
432 | 0 | bandExpression = |
433 | 0 | SetBandIndices(bandExpression, source_name, nDefaultInBand, |
434 | 0 | expressionAppliedPerBand); |
435 | 0 | } |
436 | |
|
437 | 0 | if (expressionAppliedPerBand) |
438 | 0 | { |
439 | 0 | if (nOutBands <= 1) |
440 | 0 | { |
441 | 0 | nOutBands = props.nBands; |
442 | 0 | } |
443 | 0 | else if (props.nBands != 1 && props.nBands != nOutBands) |
444 | 0 | { |
445 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
446 | 0 | "Expression cannot operate on all bands of " |
447 | 0 | "rasters with incompatible numbers of bands " |
448 | 0 | "(source %s has %d bands but expected to have " |
449 | 0 | "1 or %d bands).", |
450 | 0 | source_name.c_str(), props.nBands, nOutBands); |
451 | 0 | return false; |
452 | 0 | } |
453 | 0 | } |
454 | | |
455 | | // Create a source for each input band that is used in |
456 | | // the expression. |
457 | 0 | for (int nInBand = 1; nInBand <= props.nBands; nInBand++) |
458 | 0 | { |
459 | 0 | CPLString inBandVariable; |
460 | 0 | if (dialect == "builtin") |
461 | 0 | { |
462 | 0 | if (!flatten && props.nBands >= 2 && nInBand != nOutBand) |
463 | 0 | continue; |
464 | 0 | } |
465 | 0 | else |
466 | 0 | { |
467 | 0 | inBandVariable.Printf("%s[%d]", source_name.c_str(), |
468 | 0 | nInBand); |
469 | 0 | if (bandExpression.find(inBandVariable) == |
470 | 0 | std::string::npos) |
471 | 0 | { |
472 | 0 | continue; |
473 | 0 | } |
474 | 0 | } |
475 | | |
476 | 0 | const std::optional<double> &srcNoData = |
477 | 0 | props.noData[nInBand - 1]; |
478 | |
|
479 | 0 | std::unique_ptr<VRTSimpleSource> poSource; |
480 | 0 | if (srcNoData.has_value()) |
481 | 0 | { |
482 | 0 | poSource = std::make_unique<VRTComplexSource>(); |
483 | 0 | } |
484 | 0 | else |
485 | 0 | { |
486 | 0 | poSource = std::make_unique<VRTSimpleSource>(); |
487 | 0 | } |
488 | |
|
489 | 0 | if (!inBandVariable.empty()) |
490 | 0 | { |
491 | 0 | poSource->SetName(inBandVariable); |
492 | 0 | } |
493 | |
|
494 | 0 | if (fakeSourceFilename.empty()) |
495 | 0 | { |
496 | 0 | if (dsn == PIPELINE_INPUT_DSN) |
497 | 0 | { |
498 | 0 | CPLAssertNotNull(pipelineInputSource); |
499 | 0 | pipelineInputSource->Reference(); |
500 | 0 | poSource->SetSrcBand( |
501 | 0 | pipelineInputSource->GetRasterBand(nInBand)); |
502 | 0 | } |
503 | 0 | else |
504 | 0 | { |
505 | 0 | std::string osSourceFilename = dsn; |
506 | 0 | bool bRelativeToVRT = false; |
507 | 0 | if (pszVRTFilename[0]) |
508 | 0 | { |
509 | 0 | std::tie(osSourceFilename, bRelativeToVRT) = |
510 | 0 | VRTSimpleSource:: |
511 | 0 | ComputeSourceNameAndRelativeFlag( |
512 | 0 | CPLGetPathSafe(pszVRTFilename).c_str(), |
513 | 0 | dsn); |
514 | 0 | } |
515 | 0 | poSource->SetSrcBand(osSourceFilename.c_str(), nInBand); |
516 | 0 | } |
517 | 0 | } |
518 | 0 | else |
519 | 0 | { |
520 | 0 | poSource->SetSrcBand(fakeSourceFilename.c_str(), nInBand); |
521 | 0 | } |
522 | |
|
523 | 0 | if (srcNoData.has_value()) |
524 | 0 | { |
525 | 0 | cpl::down_cast<VRTComplexSource *>(poSource.get()) |
526 | 0 | ->SetNoDataValue(srcNoData.value()); |
527 | |
|
528 | 0 | if (autoSelectNoDataValue && !dstNoData.has_value()) |
529 | 0 | { |
530 | 0 | dstNoData = srcNoData; |
531 | 0 | } |
532 | 0 | } |
533 | |
|
534 | 0 | if (fakeSourceFilename.empty()) |
535 | 0 | { |
536 | 0 | poSource->SetSrcWindow(0, 0, props.nX, props.nY); |
537 | 0 | poSource->SetDstWindow(0, 0, nXOut, nYOut); |
538 | 0 | } |
539 | |
|
540 | 0 | poBand->AddSource(std::move(poSource)); |
541 | 0 | } |
542 | |
|
543 | 0 | if (dstNoData.has_value()) |
544 | 0 | { |
545 | 0 | if (!GDALIsValueExactAs(dstNoData.value(), bandType)) |
546 | 0 | { |
547 | 0 | CPLError( |
548 | 0 | CE_Failure, CPLE_AppDefined, |
549 | 0 | "Band output type %s cannot represent NoData value %g", |
550 | 0 | GDALGetDataTypeName(bandType), dstNoData.value()); |
551 | 0 | return false; |
552 | 0 | } |
553 | | |
554 | 0 | poBand->SetNoDataValue(dstNoData.value()); |
555 | 0 | } |
556 | 0 | } |
557 | | |
558 | 0 | if (dialect == "builtin") |
559 | 0 | { |
560 | 0 | poBand->SetPixelFunctionName(expression.c_str()); |
561 | 0 | } |
562 | 0 | else |
563 | 0 | { |
564 | 0 | poBand->SetPixelFunctionName("expression"); |
565 | 0 | poBand->AddPixelFunctionArgument("dialect", "muparser"); |
566 | | // Add the expression as a last step, because we may modify the |
567 | | // expression as we iterate through the bands. |
568 | 0 | poBand->AddPixelFunctionArgument("expression", |
569 | 0 | bandExpression.c_str()); |
570 | 0 | } |
571 | |
|
572 | 0 | if (!pixelFunctionArguments.empty()) |
573 | 0 | { |
574 | 0 | const CPLStringList args(pixelFunctionArguments); |
575 | 0 | for (const auto &[key, value] : cpl::IterateNameValue(args)) |
576 | 0 | { |
577 | 0 | poBand->AddPixelFunctionArgument(key, value); |
578 | 0 | } |
579 | 0 | } |
580 | 0 | } |
581 | | |
582 | 0 | return true; |
583 | 0 | } |
584 | | |
585 | | static bool ParseSourceDescriptors(const std::vector<std::string> &inputs, |
586 | | std::map<std::string, std::string> &datasets, |
587 | | std::string &firstSourceName, |
588 | | bool requireSourceNames) |
589 | 0 | { |
590 | 0 | for (size_t iInput = 0; iInput < inputs.size(); iInput++) |
591 | 0 | { |
592 | 0 | const std::string &input = inputs[iInput]; |
593 | 0 | std::string name; |
594 | |
|
595 | 0 | const auto pos = input.find('='); |
596 | 0 | if (pos == std::string::npos) |
597 | 0 | { |
598 | 0 | if (requireSourceNames && inputs.size() > 1) |
599 | 0 | { |
600 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
601 | 0 | "Inputs must be named when more than one input is " |
602 | 0 | "provided."); |
603 | 0 | return false; |
604 | 0 | } |
605 | 0 | name = DEFAULT_SOURCE_NAME; |
606 | 0 | if (iInput > 0) |
607 | 0 | { |
608 | 0 | name += std::to_string(iInput); |
609 | 0 | } |
610 | 0 | } |
611 | 0 | else |
612 | 0 | { |
613 | 0 | name = input.substr(0, pos); |
614 | 0 | } |
615 | | |
616 | | // Check input name is legal |
617 | 0 | for (size_t i = 0; i < name.size(); ++i) |
618 | 0 | { |
619 | 0 | const char c = name[i]; |
620 | 0 | if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) |
621 | 0 | { |
622 | | // ok |
623 | 0 | } |
624 | 0 | else if (c == '_' || (c >= '0' && c <= '9')) |
625 | 0 | { |
626 | 0 | if (i == 0) |
627 | 0 | { |
628 | | // Reserved constants in MuParser start with an underscore |
629 | 0 | CPLError( |
630 | 0 | CE_Failure, CPLE_AppDefined, |
631 | 0 | "Name '%s' is illegal because it starts with a '%c'", |
632 | 0 | name.c_str(), c); |
633 | 0 | return false; |
634 | 0 | } |
635 | 0 | } |
636 | 0 | else |
637 | 0 | { |
638 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
639 | 0 | "Name '%s' is illegal because character '%c' is not " |
640 | 0 | "allowed", |
641 | 0 | name.c_str(), c); |
642 | 0 | return false; |
643 | 0 | } |
644 | 0 | } |
645 | | |
646 | 0 | std::string dsn = |
647 | 0 | (pos == std::string::npos) ? input : input.substr(pos + 1); |
648 | |
|
649 | 0 | if (!dsn.empty() && dsn.front() == '[' && dsn.back() == ']') |
650 | 0 | { |
651 | 0 | dsn = "{\"type\":\"gdal_streamed_alg\", \"command_line\":\"gdal " |
652 | 0 | "raster pipeline " + |
653 | 0 | CPLString(dsn.substr(1, dsn.size() - 2)) |
654 | 0 | .replaceAll('\\', "\\\\") |
655 | 0 | .replaceAll('"', "\\\"") + |
656 | 0 | "\"}"; |
657 | 0 | } |
658 | |
|
659 | 0 | if (datasets.find(name) != datasets.end()) |
660 | 0 | { |
661 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
662 | 0 | "An input with name '%s' has already been provided", |
663 | 0 | name.c_str()); |
664 | 0 | return false; |
665 | 0 | } |
666 | 0 | datasets[name] = std::move(dsn); |
667 | |
|
668 | 0 | if (iInput == 0) |
669 | 0 | { |
670 | 0 | firstSourceName = std::move(name); |
671 | 0 | } |
672 | 0 | } |
673 | | |
674 | 0 | return true; |
675 | 0 | } |
676 | | |
677 | | static bool ReadFileLists(const std::vector<GDALArgDatasetValue> &inputDS, |
678 | | std::vector<std::string> &inputFilenames) |
679 | 0 | { |
680 | 0 | for (const auto &dsVal : inputDS) |
681 | 0 | { |
682 | 0 | const auto &input = dsVal.GetName(); |
683 | 0 | if (!input.empty() && input[0] == '@') |
684 | 0 | { |
685 | 0 | auto f = |
686 | 0 | VSIVirtualHandleUniquePtr(VSIFOpenL(input.c_str() + 1, "r")); |
687 | 0 | if (!f) |
688 | 0 | { |
689 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s", |
690 | 0 | input.c_str() + 1); |
691 | 0 | return false; |
692 | 0 | } |
693 | 0 | while (const char *filename = CPLReadLineL(f.get())) |
694 | 0 | { |
695 | 0 | inputFilenames.push_back(filename); |
696 | 0 | } |
697 | 0 | } |
698 | 0 | else |
699 | 0 | { |
700 | 0 | inputFilenames.push_back(input); |
701 | 0 | } |
702 | 0 | } |
703 | | |
704 | 0 | return true; |
705 | 0 | } |
706 | | |
707 | | /** Creates a VRT dataset with one or more derived raster bands containing |
708 | | * results of an expression. |
709 | | * |
710 | | * To make this work with muparser (which does not support vector types), we |
711 | | * do a simple parsing of the expression internally, transforming it into |
712 | | * multiple expressions with explicit band indices. For example, for a two-band |
713 | | * raster "X", the expression "X + 3" will be transformed into "X[1] + 3" and |
714 | | * "X[2] + 3". The use of brackets is for readability only; as far as the |
715 | | * expression engine is concerned, the variables "X[1]" and "X[2]" have nothing |
716 | | * to do with each other. |
717 | | * |
718 | | * @param inputs Either: |
719 | | * - a list of sources, expressed as NAME=DSN |
720 | | * - pointer to a single opened dataset |
721 | | * @param expressions A list of expressions to be evaluated |
722 | | * @param dialect Expression dialect |
723 | | * @param flatten Generate a single band output raster per expression, even if |
724 | | * input datasets are multiband. |
725 | | * @param noData NoData values to use for output bands, or "none", or "" |
726 | | * @param pixelFunctionArguments Pixel function arguments. |
727 | | * @param options flags controlling which checks should be performed on the inputs |
728 | | * @param[out] maxSourceBands Maximum number of bands in source dataset(s) |
729 | | * @param fakeSourceFilename If not empty, used instead of real input filenames. |
730 | | * |
731 | | * @return a newly created VRTDataset, or nullptr on error |
732 | | */ |
733 | | static std::unique_ptr<GDALDataset> GDALCalcCreateVRTDerived( |
734 | | std::variant<GDALDataset *, const std::vector<std::string> *> inputs, |
735 | | const std::vector<std::string> &expressions, const std::string &dialect, |
736 | | bool flatten, const std::string &noData, |
737 | | const std::vector<std::vector<std::string>> &pixelFunctionArguments, |
738 | | const GDALCalcOptions &options, int &maxSourceBands, |
739 | | const std::string &fakeSourceFilename = std::string()) |
740 | 0 | { |
741 | 0 | std::map<std::string, std::string> sources; |
742 | 0 | std::map<std::string, SourceProperties> sourceProps; |
743 | 0 | GDALDataset *pipelineInputDS = std::holds_alternative<GDALDataset *>(inputs) |
744 | 0 | ? std::get<GDALDataset *>(inputs) |
745 | 0 | : nullptr; |
746 | |
|
747 | 0 | maxSourceBands = 0; |
748 | | |
749 | | // Read properties from the first source |
750 | 0 | SourceProperties out; |
751 | 0 | { |
752 | 0 | std::unique_ptr<GDALDataset> poTmpDS; |
753 | 0 | const GDALDataset *poTemplateDS; |
754 | |
|
755 | 0 | if (pipelineInputDS) |
756 | 0 | { |
757 | 0 | poTemplateDS = pipelineInputDS; |
758 | 0 | } |
759 | 0 | else |
760 | 0 | { |
761 | 0 | const std::vector<std::string> &sourceDescriptors = |
762 | 0 | *std::get<const std::vector<std::string> *>(inputs); |
763 | |
|
764 | 0 | if (sourceDescriptors.empty()) |
765 | 0 | { |
766 | 0 | return nullptr; |
767 | 0 | } |
768 | | |
769 | 0 | const bool requireSourceNames = dialect != "builtin"; |
770 | |
|
771 | 0 | std::string firstSource; |
772 | 0 | if (!ParseSourceDescriptors(sourceDescriptors, sources, firstSource, |
773 | 0 | requireSourceNames)) |
774 | 0 | { |
775 | 0 | return nullptr; |
776 | 0 | } |
777 | | |
778 | | // Use the first source provided to determine properties of the output |
779 | 0 | const char *firstDSN = sources[firstSource].c_str(); |
780 | |
|
781 | 0 | poTmpDS.reset(GDALDataset::Open(firstDSN, GDAL_OF_RASTER)); |
782 | 0 | if (!poTmpDS) |
783 | 0 | { |
784 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Failed to open %s", |
785 | 0 | firstDSN); |
786 | 0 | return nullptr; |
787 | 0 | } |
788 | 0 | poTemplateDS = poTmpDS.get(); |
789 | 0 | } |
790 | | |
791 | 0 | out.nX = poTemplateDS->GetRasterXSize(); |
792 | 0 | out.nY = poTemplateDS->GetRasterYSize(); |
793 | 0 | out.nBands = 1; |
794 | 0 | out.srs = OGRSpatialReferenceRefCountedPtr::makeClone( |
795 | 0 | poTemplateDS->GetSpatialRef()); |
796 | 0 | out.hasGT = poTemplateDS->GetGeoTransform(out.gt) == CE_None; |
797 | |
|
798 | 0 | maxSourceBands = 0; |
799 | |
|
800 | 0 | if (pipelineInputDS) |
801 | 0 | { |
802 | 0 | sources[DEFAULT_SOURCE_NAME] = PIPELINE_INPUT_DSN; |
803 | 0 | if (auto props = |
804 | 0 | UpdateSourceProperties(out, pipelineInputDS, options)) |
805 | 0 | { |
806 | 0 | sourceProps[DEFAULT_SOURCE_NAME] = props.value(); |
807 | 0 | maxSourceBands = props.value().nBands; |
808 | 0 | } |
809 | 0 | else |
810 | 0 | { |
811 | 0 | return nullptr; // error message emitted from UpdateSourceProperties |
812 | 0 | } |
813 | 0 | } |
814 | 0 | else |
815 | 0 | { |
816 | | // Collect properties of the different sources, and verify them for |
817 | | // consistency. |
818 | 0 | for (const auto &[source_name, dsn] : sources) |
819 | 0 | { |
820 | | // TODO avoid opening the first source twice. |
821 | 0 | std::unique_ptr<GDALDataset> ds( |
822 | 0 | GDALDataset::Open(dsn.c_str(), GDAL_OF_RASTER)); |
823 | |
|
824 | 0 | if (!ds) |
825 | 0 | { |
826 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Failed to open %s", |
827 | 0 | dsn.c_str()); |
828 | 0 | return nullptr; |
829 | 0 | } |
830 | | |
831 | 0 | auto props = UpdateSourceProperties(out, ds.get(), options); |
832 | 0 | if (props.has_value()) |
833 | 0 | { |
834 | 0 | maxSourceBands = std::max(maxSourceBands, props->nBands); |
835 | 0 | sourceProps[source_name] = std::move(props.value()); |
836 | 0 | } |
837 | 0 | else |
838 | 0 | { |
839 | 0 | return nullptr; // error message emitted from UpdateSourceProperties |
840 | 0 | } |
841 | 0 | } |
842 | 0 | } |
843 | 0 | } |
844 | | |
845 | 0 | size_t iExpr = 0; |
846 | |
|
847 | 0 | auto poDS = VRTDataset::CreateVRTDataset("", out.nX, out.nY, 0, |
848 | 0 | options.dstType, nullptr); |
849 | |
|
850 | 0 | for (const auto &origExpression : expressions) |
851 | 0 | { |
852 | 0 | GDALDataType bandType = options.dstType; |
853 | | |
854 | | // If output band type has not been specified, set it equal to the |
855 | | // input band type for certain pixel functions, if the inputs have |
856 | | // a consistent band type. |
857 | 0 | if (bandType == GDT_Unknown && dialect == "builtin" && |
858 | 0 | (origExpression == "min" || origExpression == "max" || |
859 | 0 | origExpression == "mode")) |
860 | 0 | { |
861 | 0 | for (const auto &[_, props] : sourceProps) |
862 | 0 | { |
863 | 0 | if (bandType == GDT_Unknown) |
864 | 0 | { |
865 | 0 | bandType = props.eDT; |
866 | 0 | } |
867 | 0 | else if (props.eDT == GDT_Unknown || props.eDT != bandType) |
868 | 0 | { |
869 | 0 | bandType = GDT_Unknown; |
870 | 0 | break; |
871 | 0 | } |
872 | 0 | } |
873 | 0 | } |
874 | |
|
875 | 0 | if (!CreateVRTDerivedBand( |
876 | 0 | poDS.get(), bandType, origExpression, dialect, flatten, noData, |
877 | 0 | pixelFunctionArguments[iExpr], sources, sourceProps, |
878 | 0 | fakeSourceFilename, pipelineInputDS)) |
879 | 0 | { |
880 | 0 | return nullptr; |
881 | 0 | } |
882 | 0 | ++iExpr; |
883 | 0 | } |
884 | | |
885 | 0 | if (out.hasGT) |
886 | 0 | { |
887 | 0 | poDS->SetGeoTransform(out.gt); |
888 | 0 | } |
889 | 0 | if (out.srs) |
890 | 0 | { |
891 | 0 | poDS->SetSpatialRef(out.srs.get()); |
892 | 0 | } |
893 | |
|
894 | 0 | return poDS; |
895 | 0 | } |
896 | | |
897 | | /************************************************************************/ |
898 | | /* GDALRasterCalcAlgorithm::GDALRasterCalcAlgorithm() */ |
899 | | /************************************************************************/ |
900 | | |
901 | | GDALRasterCalcAlgorithm::GDALRasterCalcAlgorithm(bool standaloneStep) noexcept |
902 | 0 | : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL, |
903 | 0 | ConstructorOptions() |
904 | 0 | .SetStandaloneStep(standaloneStep) |
905 | 0 | .SetAddDefaultArguments(false) |
906 | 0 | .SetAutoOpenInputDatasets(false) |
907 | 0 | .SetInputDatasetMetaVar("INPUTS") |
908 | 0 | .SetInputDatasetMaxCount(INT_MAX)) |
909 | 0 | { |
910 | 0 | AddRasterInputArgs(false, false); |
911 | 0 | if (standaloneStep) |
912 | 0 | { |
913 | 0 | AddProgressArg(); |
914 | 0 | AddRasterOutputArgs(false); |
915 | 0 | } |
916 | |
|
917 | 0 | AddOutputDataTypeArg(&m_type); |
918 | |
|
919 | 0 | AddArg("no-check-crs", 0, |
920 | 0 | _("Do not check consistency of input coordinate reference systems"), |
921 | 0 | &m_noCheckCRS) |
922 | 0 | .AddHiddenAlias("no-check-srs"); |
923 | 0 | AddArg("no-check-extent", 0, _("Do not check consistency of input extents"), |
924 | 0 | &m_noCheckExtent); |
925 | |
|
926 | 0 | AddArg("propagate-nodata", 0, |
927 | 0 | _("Whether to set pixels to the output NoData value if any of the " |
928 | 0 | "input pixels is NoData"), |
929 | 0 | &m_propagateNoData); |
930 | |
|
931 | 0 | AddArg("calc", 0, _("Expression(s) to evaluate"), &m_expr) |
932 | 0 | .SetRequired() |
933 | 0 | .SetPackedValuesAllowed(false) |
934 | 0 | .SetMinCount(1) |
935 | 0 | .SetAutoCompleteFunction( |
936 | 0 | [this](const std::string ¤tValue) |
937 | 0 | { |
938 | 0 | std::vector<std::string> ret; |
939 | 0 | if (m_dialect == "builtin") |
940 | 0 | { |
941 | 0 | if (currentValue.find('(') == std::string::npos) |
942 | 0 | return VRTDerivedRasterBand::GetPixelFunctionNames(); |
943 | 0 | } |
944 | 0 | return ret; |
945 | 0 | }); |
946 | |
|
947 | 0 | AddArg("dialect", 0, _("Expression dialect"), &m_dialect) |
948 | 0 | .SetDefault(m_dialect) |
949 | 0 | .SetChoices("muparser", "builtin"); |
950 | |
|
951 | 0 | AddArg("flatten", 0, |
952 | 0 | _("Generate a single band output raster per expression, even if " |
953 | 0 | "input datasets are multiband"), |
954 | 0 | &m_flatten); |
955 | |
|
956 | 0 | AddNodataArg(&m_nodata, true); |
957 | | |
958 | | // This is a hidden option only used by test_gdalalg_raster_calc_expression_rewriting() |
959 | | // for now |
960 | 0 | AddArg("no-check-expression", 0, |
961 | 0 | _("Whether to skip expression validity checks for virtual format " |
962 | 0 | "output"), |
963 | 0 | &m_noCheckExpression) |
964 | 0 | .SetHidden(); |
965 | |
|
966 | 0 | AddValidationAction( |
967 | 0 | [this]() |
968 | 0 | { |
969 | 0 | GDALPipelineStepRunContext ctxt; |
970 | 0 | return m_noCheckExpression || !IsGDALGOutput() || RunStep(ctxt); |
971 | 0 | }); |
972 | 0 | } |
973 | | |
974 | | /************************************************************************/ |
975 | | /* GDALRasterCalcAlgorithm::RunImpl() */ |
976 | | /************************************************************************/ |
977 | | |
978 | | bool GDALRasterCalcAlgorithm::RunImpl(GDALProgressFunc pfnProgress, |
979 | | void *pProgressData) |
980 | 0 | { |
981 | 0 | GDALPipelineStepRunContext stepCtxt; |
982 | 0 | stepCtxt.m_pfnProgress = pfnProgress; |
983 | 0 | stepCtxt.m_pProgressData = pProgressData; |
984 | 0 | return RunPreStepPipelineValidations() && RunStep(stepCtxt); |
985 | 0 | } |
986 | | |
987 | | /************************************************************************/ |
988 | | /* GDALRasterCalcAlgorithm::RunStep() */ |
989 | | /************************************************************************/ |
990 | | |
991 | | bool GDALRasterCalcAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt) |
992 | 0 | { |
993 | 0 | CPLAssert(!m_outputDataset.GetDatasetRef()); |
994 | | |
995 | 0 | GDALCalcOptions options; |
996 | 0 | options.checkExtent = !m_noCheckExtent; |
997 | 0 | options.checkCRS = !m_noCheckCRS; |
998 | 0 | if (!m_type.empty()) |
999 | 0 | { |
1000 | 0 | options.dstType = GDALGetDataTypeByName(m_type.c_str()); |
1001 | 0 | } |
1002 | |
|
1003 | 0 | GDALDataset *poPipelineInput = nullptr; |
1004 | 0 | std::vector<std::string> inputFilenames; |
1005 | 0 | if (m_inputDataset.size() == 1 && m_inputDataset[0].GetDatasetRef()) |
1006 | 0 | { |
1007 | 0 | poPipelineInput = m_inputDataset[0].GetDatasetRef(); |
1008 | 0 | } |
1009 | 0 | else |
1010 | 0 | { |
1011 | 0 | if (!ReadFileLists(m_inputDataset, inputFilenames)) |
1012 | 0 | { |
1013 | 0 | return false; |
1014 | 0 | } |
1015 | 0 | } |
1016 | | |
1017 | 0 | std::vector<std::vector<std::string>> pixelFunctionArgs; |
1018 | 0 | if (m_dialect == "builtin") |
1019 | 0 | { |
1020 | 0 | for (std::string &expr : m_expr) |
1021 | 0 | { |
1022 | 0 | const CPLStringList aosTokens( |
1023 | 0 | CSLTokenizeString2(expr.c_str(), "()", |
1024 | 0 | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES)); |
1025 | 0 | const char *pszFunction = aosTokens[0]; |
1026 | 0 | const auto *pair = |
1027 | 0 | VRTDerivedRasterBand::GetPixelFunction(pszFunction); |
1028 | 0 | if (!pair) |
1029 | 0 | { |
1030 | 0 | ReportError(CE_Failure, CPLE_NotSupported, |
1031 | 0 | "'%s' is an unknown builtin function", pszFunction); |
1032 | 0 | return false; |
1033 | 0 | } |
1034 | 0 | if (aosTokens.size() == 2) |
1035 | 0 | { |
1036 | 0 | std::vector<std::string> validArguments; |
1037 | 0 | AddOptionsSuggestions(pair->second.c_str(), 0, std::string(), |
1038 | 0 | validArguments); |
1039 | 0 | for (std::string &s : validArguments) |
1040 | 0 | { |
1041 | 0 | if (!s.empty() && s.back() == '=') |
1042 | 0 | s.pop_back(); |
1043 | 0 | } |
1044 | |
|
1045 | 0 | const CPLStringList aosTokensArgs(CSLTokenizeString2( |
1046 | 0 | aosTokens[1], ",", |
1047 | 0 | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES)); |
1048 | 0 | for (const auto &[key, value] : |
1049 | 0 | cpl::IterateNameValue(aosTokensArgs)) |
1050 | 0 | { |
1051 | 0 | if (std::find(validArguments.begin(), validArguments.end(), |
1052 | 0 | key) == validArguments.end()) |
1053 | 0 | { |
1054 | 0 | if (validArguments.empty()) |
1055 | 0 | { |
1056 | 0 | ReportError( |
1057 | 0 | CE_Failure, CPLE_IllegalArg, |
1058 | 0 | "'%s' is a unrecognized argument for builtin " |
1059 | 0 | "function '%s'. It does not accept any " |
1060 | 0 | "argument", |
1061 | 0 | key, pszFunction); |
1062 | 0 | } |
1063 | 0 | else |
1064 | 0 | { |
1065 | 0 | std::string validArgumentsStr; |
1066 | 0 | for (const std::string &s : validArguments) |
1067 | 0 | { |
1068 | 0 | if (!validArgumentsStr.empty()) |
1069 | 0 | validArgumentsStr += ", "; |
1070 | 0 | validArgumentsStr += '\''; |
1071 | 0 | validArgumentsStr += s; |
1072 | 0 | validArgumentsStr += '\''; |
1073 | 0 | } |
1074 | 0 | ReportError( |
1075 | 0 | CE_Failure, CPLE_IllegalArg, |
1076 | 0 | "'%s' is a unrecognized argument for builtin " |
1077 | 0 | "function '%s'. Only %s %s supported", |
1078 | 0 | key, pszFunction, |
1079 | 0 | validArguments.size() == 1 ? "is" : "are", |
1080 | 0 | validArgumentsStr.c_str()); |
1081 | 0 | } |
1082 | 0 | return false; |
1083 | 0 | } |
1084 | 0 | CPL_IGNORE_RET_VAL(value); |
1085 | 0 | } |
1086 | 0 | pixelFunctionArgs.emplace_back(aosTokensArgs); |
1087 | 0 | } |
1088 | 0 | else |
1089 | 0 | { |
1090 | 0 | pixelFunctionArgs.push_back(std::vector<std::string>()); |
1091 | 0 | } |
1092 | 0 | expr = pszFunction; |
1093 | 0 | } |
1094 | 0 | } |
1095 | 0 | else |
1096 | 0 | { |
1097 | 0 | pixelFunctionArgs.resize(m_expr.size()); |
1098 | 0 | } |
1099 | | |
1100 | 0 | if (m_propagateNoData) |
1101 | 0 | { |
1102 | 0 | if (m_nodata == "none") |
1103 | 0 | { |
1104 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
1105 | 0 | "Output NoData value must be specified to use " |
1106 | 0 | "--propagate-nodata"); |
1107 | 0 | return false; |
1108 | 0 | } |
1109 | 0 | for (auto &args : pixelFunctionArgs) |
1110 | 0 | { |
1111 | 0 | args.push_back("propagateNoData=1"); |
1112 | 0 | } |
1113 | 0 | } |
1114 | | |
1115 | 0 | int maxSourceBands = 0; |
1116 | 0 | const bool bIsVRT = |
1117 | 0 | m_format == "VRT" || |
1118 | 0 | (m_format.empty() && |
1119 | 0 | EQUAL(CPLGetExtensionSafe(m_outputDataset.GetName().c_str()).c_str(), |
1120 | 0 | "VRT")); |
1121 | |
|
1122 | 0 | std::variant<GDALDataset *, const std::vector<std::string> *> inputs; |
1123 | 0 | if (poPipelineInput) |
1124 | 0 | { |
1125 | 0 | inputs = poPipelineInput; |
1126 | 0 | } |
1127 | 0 | else |
1128 | 0 | { |
1129 | 0 | inputs = &inputFilenames; |
1130 | 0 | } |
1131 | |
|
1132 | 0 | auto vrt = |
1133 | 0 | GDALCalcCreateVRTDerived(inputs, m_expr, m_dialect, m_flatten, m_nodata, |
1134 | 0 | pixelFunctionArgs, options, maxSourceBands); |
1135 | 0 | if (vrt == nullptr) |
1136 | 0 | { |
1137 | 0 | return false; |
1138 | 0 | } |
1139 | | |
1140 | 0 | if (!m_noCheckExpression) |
1141 | 0 | { |
1142 | 0 | const bool bIsGDALG = |
1143 | 0 | m_format == "GDALG" || |
1144 | 0 | (m_format.empty() && |
1145 | 0 | cpl::ends_with(m_outputDataset.GetName(), ".gdalg.json")); |
1146 | 0 | if (!m_standaloneStep || m_format == "stream" || bIsVRT || bIsGDALG) |
1147 | 0 | { |
1148 | | // Try reading a single pixel to check formulas are valid. |
1149 | 0 | std::vector<GByte> dummyData(vrt->GetRasterCount()); |
1150 | |
|
1151 | 0 | auto poGTIFFDrv = GetGDALDriverManager()->GetDriverByName("GTiff"); |
1152 | 0 | std::string osTmpFilename; |
1153 | 0 | if (poGTIFFDrv) |
1154 | 0 | { |
1155 | 0 | std::string osFilename = |
1156 | 0 | VSIMemGenerateHiddenFilename("tmp.tif"); |
1157 | 0 | auto poDS = std::unique_ptr<GDALDataset>( |
1158 | 0 | poGTIFFDrv->Create(osFilename.c_str(), 1, 1, maxSourceBands, |
1159 | 0 | GDT_UInt8, nullptr)); |
1160 | 0 | if (poDS) |
1161 | 0 | osTmpFilename = std::move(osFilename); |
1162 | 0 | } |
1163 | 0 | if (!osTmpFilename.empty()) |
1164 | 0 | { |
1165 | 0 | auto fakeVRT = GDALCalcCreateVRTDerived( |
1166 | 0 | &inputFilenames, m_expr, m_dialect, m_flatten, m_nodata, |
1167 | 0 | pixelFunctionArgs, options, maxSourceBands, osTmpFilename); |
1168 | 0 | if (fakeVRT && |
1169 | 0 | fakeVRT->RasterIO(GF_Read, 0, 0, 1, 1, dummyData.data(), 1, |
1170 | 0 | 1, GDT_UInt8, vrt->GetRasterCount(), |
1171 | 0 | nullptr, 0, 0, 0, nullptr) != CE_None) |
1172 | 0 | { |
1173 | 0 | return false; |
1174 | 0 | } |
1175 | 0 | } |
1176 | 0 | if (bIsGDALG) |
1177 | 0 | { |
1178 | 0 | return true; |
1179 | 0 | } |
1180 | 0 | } |
1181 | 0 | } |
1182 | | |
1183 | 0 | if (m_format == "stream" || !m_standaloneStep) |
1184 | 0 | { |
1185 | 0 | m_outputDataset.Set(std::move(vrt)); |
1186 | 0 | return true; |
1187 | 0 | } |
1188 | | |
1189 | 0 | CPLStringList translateArgs; |
1190 | 0 | if (!m_format.empty()) |
1191 | 0 | { |
1192 | 0 | translateArgs.AddString("-of"); |
1193 | 0 | translateArgs.AddString(m_format.c_str()); |
1194 | 0 | } |
1195 | 0 | for (const auto &co : m_creationOptions) |
1196 | 0 | { |
1197 | 0 | translateArgs.AddString("-co"); |
1198 | 0 | translateArgs.AddString(co.c_str()); |
1199 | 0 | } |
1200 | |
|
1201 | 0 | bool bOK = false; |
1202 | 0 | GDALTranslateOptions *translateOptions = |
1203 | 0 | GDALTranslateOptionsNew(translateArgs.List(), nullptr); |
1204 | 0 | if (translateOptions) |
1205 | 0 | { |
1206 | 0 | GDALTranslateOptionsSetProgress(translateOptions, ctxt.m_pfnProgress, |
1207 | 0 | ctxt.m_pProgressData); |
1208 | |
|
1209 | 0 | auto poOutDS = |
1210 | 0 | std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(GDALTranslate( |
1211 | 0 | m_outputDataset.GetName().c_str(), |
1212 | 0 | GDALDataset::ToHandle(vrt.get()), translateOptions, nullptr))); |
1213 | 0 | GDALTranslateOptionsFree(translateOptions); |
1214 | |
|
1215 | 0 | bOK = poOutDS != nullptr; |
1216 | 0 | m_outputDataset.Set(std::move(poOutDS)); |
1217 | 0 | } |
1218 | |
|
1219 | 0 | return bOK; |
1220 | 0 | } |
1221 | | |
1222 | 0 | GDALRasterCalcAlgorithmStandalone::~GDALRasterCalcAlgorithmStandalone() = |
1223 | | default; |
1224 | | |
1225 | | //! @endcond |