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