Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Utilities
4 : * Purpose: Command line application to convert a multidimensional raster
5 : * Author: Even Rouault,<even.rouault at spatialys.com>
6 : *
7 : * ****************************************************************************
8 : * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "commonutils.h"
15 : #include "gdal_priv.h"
16 : #include "gdal_utils.h"
17 : #include "gdal_utils_priv.h"
18 : #include "gdalargumentparser.h"
19 : #include "vrtdataset.h"
20 : #include <algorithm>
21 : #include <map>
22 : #include <set>
23 :
24 : /************************************************************************/
25 : /* GDALMultiDimTranslateOptions */
26 : /************************************************************************/
27 :
28 : struct GDALMultiDimTranslateOptions
29 : {
30 : std::string osFormat{};
31 : CPLStringList aosCreateOptions{};
32 : std::vector<std::string> aosArraySpec{};
33 : CPLStringList aosArrayOptions{};
34 : std::vector<std::string> aosSubset{};
35 : std::vector<std::string> aosScaleFactor{};
36 : std::vector<std::string> aosGroup{};
37 : GDALProgressFunc pfnProgress = GDALDummyProgress;
38 : bool bStrict = false;
39 : void *pProgressData = nullptr;
40 : bool bUpdate = false;
41 : bool bOverwrite = false;
42 : bool bNoOverwrite = false;
43 : };
44 :
45 : /************************************************************************/
46 : /* GDALMultiDimTranslateAppOptionsGetParser() */
47 : /************************************************************************/
48 :
49 : static std::unique_ptr<GDALArgumentParser>
50 124 : GDALMultiDimTranslateAppOptionsGetParser(
51 : GDALMultiDimTranslateOptions *psOptions,
52 : GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
53 : {
54 : auto argParser = std::make_unique<GDALArgumentParser>(
55 124 : "gdalmdimtranslate", /* bForBinary=*/psOptionsForBinary != nullptr);
56 :
57 124 : argParser->add_description(
58 : _("Converts multidimensional data between different formats, and "
59 124 : "performs subsetting."));
60 :
61 124 : argParser->add_epilog(
62 : _("For more details, consult "
63 124 : "https://gdal.org/programs/gdalmdimtranslate.html"));
64 :
65 124 : if (psOptionsForBinary)
66 : {
67 : argParser->add_input_format_argument(
68 3 : &psOptionsForBinary->aosAllowInputDrivers);
69 : }
70 :
71 124 : argParser->add_output_format_argument(psOptions->osFormat);
72 :
73 124 : argParser->add_creation_options_argument(psOptions->aosCreateOptions);
74 :
75 124 : auto &group = argParser->add_mutually_exclusive_group();
76 124 : group.add_argument("-array")
77 248 : .metavar("<array_spec>")
78 124 : .append()
79 124 : .store_into(psOptions->aosArraySpec)
80 : .help(_(
81 124 : "Select a single array instead of converting the whole dataset."));
82 :
83 124 : argParser->add_argument("-arrayoption")
84 248 : .metavar("<NAME>=<VALUE>")
85 124 : .append()
86 1 : .action([psOptions](const std::string &s)
87 125 : { psOptions->aosArrayOptions.AddString(s.c_str()); })
88 : .help(_("Option passed to GDALGroup::GetMDArrayNames() to filter "
89 124 : "arrays."));
90 :
91 124 : group.add_argument("-group")
92 248 : .metavar("<group_spec>")
93 124 : .append()
94 124 : .store_into(psOptions->aosGroup)
95 : .help(_(
96 124 : "Select a single group instead of converting the whole dataset."));
97 :
98 : // Note: this is mutually exclusive with "view" option in -array
99 124 : argParser->add_argument("-subset")
100 248 : .metavar("<subset_spec>")
101 124 : .append()
102 124 : .store_into(psOptions->aosSubset)
103 124 : .help(_("Select a subset of the data."));
104 :
105 : // Note: this is mutually exclusive with "view" option in -array
106 124 : argParser->add_argument("-scaleaxes")
107 248 : .metavar("<scaleaxes_spec>")
108 : .action(
109 4 : [psOptions](const std::string &s)
110 : {
111 : CPLStringList aosScaleFactors(
112 4 : CSLTokenizeString2(s.c_str(), ",", 0));
113 4 : for (int j = 0; j < aosScaleFactors.size(); j++)
114 : {
115 2 : psOptions->aosScaleFactor.push_back(aosScaleFactors[j]);
116 : }
117 126 : })
118 : .help(
119 124 : _("Applies a integral scale factor to one or several dimensions."));
120 :
121 124 : argParser->add_argument("-strict")
122 124 : .flag()
123 124 : .store_into(psOptions->bStrict)
124 124 : .help(_("Turn warnings into failures."));
125 :
126 : // Undocumented option used by gdal mdim convert
127 124 : argParser->add_argument("--overwrite")
128 124 : .store_into(psOptions->bOverwrite)
129 124 : .hidden();
130 :
131 : // Undocumented option used by gdal mdim convert
132 124 : argParser->add_argument("--no-overwrite")
133 124 : .store_into(psOptions->bNoOverwrite)
134 124 : .hidden();
135 :
136 124 : if (psOptionsForBinary)
137 : {
138 : argParser->add_open_options_argument(
139 3 : psOptionsForBinary->aosOpenOptions);
140 :
141 3 : argParser->add_argument("src_dataset")
142 6 : .metavar("<src_dataset>")
143 3 : .store_into(psOptionsForBinary->osSource)
144 3 : .help(_("The source dataset name."));
145 :
146 3 : argParser->add_argument("dst_dataset")
147 6 : .metavar("<dst_dataset>")
148 3 : .store_into(psOptionsForBinary->osDest)
149 3 : .help(_("The destination file name."));
150 :
151 3 : argParser->add_quiet_argument(&psOptionsForBinary->bQuiet);
152 : }
153 :
154 124 : return argParser;
155 : }
156 :
157 : /************************************************************************/
158 : /* GDALMultiDimTranslateAppGetParserUsage() */
159 : /************************************************************************/
160 :
161 0 : std::string GDALMultiDimTranslateAppGetParserUsage()
162 : {
163 : try
164 : {
165 0 : GDALMultiDimTranslateOptions sOptions;
166 0 : GDALMultiDimTranslateOptionsForBinary sOptionsForBinary;
167 : auto argParser = GDALMultiDimTranslateAppOptionsGetParser(
168 0 : &sOptions, &sOptionsForBinary);
169 0 : return argParser->usage();
170 : }
171 0 : catch (const std::exception &err)
172 : {
173 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
174 0 : err.what());
175 0 : return std::string();
176 : }
177 : }
178 :
179 : /************************************************************************/
180 : /* FindMinMaxIdxNumeric() */
181 : /************************************************************************/
182 :
183 40 : static void FindMinMaxIdxNumeric(const GDALMDArray *var, double *pdfTmp,
184 : const size_t nCount, const GUInt64 nStartIdx,
185 : const double dfMin, const double dfMax,
186 : const bool bSlice, bool &bFoundMinIdx,
187 : GUInt64 &nMinIdx, bool &bFoundMaxIdx,
188 : GUInt64 &nMaxIdx, bool &bLastWasReversed,
189 : bool &bEmpty, const double EPS)
190 : {
191 40 : if (nCount >= 2)
192 : {
193 40 : bool bReversed = false;
194 40 : if (pdfTmp[0] > pdfTmp[nCount - 1])
195 : {
196 19 : bReversed = true;
197 19 : std::reverse(pdfTmp, pdfTmp + nCount);
198 : }
199 40 : if (nStartIdx > 0 && bLastWasReversed != bReversed)
200 : {
201 0 : CPLError(CE_Failure, CPLE_AppDefined,
202 0 : "Variable %s is non monotonic", var->GetName().c_str());
203 0 : bEmpty = true;
204 0 : return;
205 : }
206 40 : bLastWasReversed = bReversed;
207 :
208 40 : if (!bFoundMinIdx)
209 : {
210 40 : if (bReversed && nStartIdx == 0 && dfMin > pdfTmp[nCount - 1])
211 : {
212 2 : bEmpty = true;
213 2 : return;
214 : }
215 38 : else if (!bReversed && dfMin < pdfTmp[0] - EPS)
216 : {
217 6 : if (bSlice)
218 : {
219 1 : bEmpty = true;
220 1 : return;
221 : }
222 5 : bFoundMinIdx = true;
223 5 : nMinIdx = nStartIdx;
224 : }
225 32 : else if (dfMin >= pdfTmp[0] - EPS &&
226 28 : dfMin <= pdfTmp[nCount - 1] + EPS)
227 : {
228 130 : for (size_t i = 0; i < nCount; i++)
229 : {
230 130 : if (dfMin <= pdfTmp[i] + EPS)
231 : {
232 26 : bFoundMinIdx = true;
233 26 : nMinIdx = nStartIdx + (bReversed ? nCount - 1 - i : i);
234 26 : break;
235 : }
236 : }
237 26 : CPLAssert(bFoundMinIdx);
238 : }
239 : }
240 37 : if (!bFoundMaxIdx)
241 : {
242 37 : if (bReversed && nStartIdx == 0 && dfMax > pdfTmp[nCount - 1])
243 : {
244 2 : if (bSlice)
245 : {
246 0 : bEmpty = true;
247 0 : return;
248 : }
249 2 : bFoundMaxIdx = true;
250 2 : nMaxIdx = 0;
251 : }
252 35 : else if (!bReversed && dfMax < pdfTmp[0] - EPS)
253 : {
254 1 : if (nStartIdx == 0)
255 : {
256 1 : bEmpty = true;
257 1 : return;
258 : }
259 0 : bFoundMaxIdx = true;
260 0 : nMaxIdx = nStartIdx - 1;
261 : }
262 34 : else if (dfMax > pdfTmp[0] - EPS &&
263 32 : dfMax <= pdfTmp[nCount - 1] + EPS)
264 : {
265 156 : for (size_t i = 1; i < nCount; i++)
266 : {
267 148 : if (dfMax <= pdfTmp[i] - EPS)
268 : {
269 18 : bFoundMaxIdx = true;
270 18 : nMaxIdx = nStartIdx +
271 18 : (bReversed ? nCount - 1 - (i - 1) : i - 1);
272 18 : break;
273 : }
274 : }
275 26 : if (!bFoundMaxIdx)
276 : {
277 8 : bFoundMaxIdx = true;
278 8 : nMaxIdx = nStartIdx + (bReversed ? 0 : nCount - 1);
279 : }
280 : }
281 : }
282 : }
283 : else
284 : {
285 0 : if (!bFoundMinIdx)
286 : {
287 0 : if (dfMin <= pdfTmp[0] + EPS)
288 : {
289 0 : bFoundMinIdx = true;
290 0 : nMinIdx = nStartIdx;
291 : }
292 0 : else if (bLastWasReversed && nStartIdx > 0)
293 : {
294 0 : bFoundMinIdx = true;
295 0 : nMinIdx = nStartIdx - 1;
296 : }
297 : }
298 0 : if (!bFoundMaxIdx)
299 : {
300 0 : if (dfMax >= pdfTmp[0] - EPS)
301 : {
302 0 : bFoundMaxIdx = true;
303 0 : nMaxIdx = nStartIdx;
304 : }
305 0 : else if (!bLastWasReversed && nStartIdx > 0)
306 : {
307 0 : bFoundMaxIdx = true;
308 0 : nMaxIdx = nStartIdx - 1;
309 : }
310 : }
311 : }
312 : }
313 :
314 : /************************************************************************/
315 : /* FindMinMaxIdxString() */
316 : /************************************************************************/
317 :
318 40 : static void FindMinMaxIdxString(const GDALMDArray *var, const char **ppszTmp,
319 : const size_t nCount, const GUInt64 nStartIdx,
320 : const std::string &osMin,
321 : const std::string &osMax, const bool bSlice,
322 : bool &bFoundMinIdx, GUInt64 &nMinIdx,
323 : bool &bFoundMaxIdx, GUInt64 &nMaxIdx,
324 : bool &bLastWasReversed, bool &bEmpty)
325 : {
326 40 : bool bFoundNull = false;
327 200 : for (size_t i = 0; i < nCount; i++)
328 : {
329 160 : if (ppszTmp[i] == nullptr)
330 : {
331 0 : bFoundNull = true;
332 0 : break;
333 : }
334 : }
335 40 : if (bFoundNull)
336 : {
337 0 : CPLError(CE_Failure, CPLE_AppDefined,
338 0 : "Variable %s contains null strings", var->GetName().c_str());
339 0 : bEmpty = true;
340 0 : return;
341 : }
342 40 : if (nCount >= 2)
343 : {
344 40 : bool bReversed = false;
345 40 : if (std::string(ppszTmp[0]) > std::string(ppszTmp[nCount - 1]))
346 : {
347 19 : bReversed = true;
348 19 : std::reverse(ppszTmp, ppszTmp + nCount);
349 : }
350 40 : if (nStartIdx > 0 && bLastWasReversed != bReversed)
351 : {
352 0 : CPLError(CE_Failure, CPLE_AppDefined,
353 0 : "Variable %s is non monotonic", var->GetName().c_str());
354 0 : bEmpty = true;
355 0 : return;
356 : }
357 40 : bLastWasReversed = bReversed;
358 :
359 40 : if (!bFoundMinIdx)
360 : {
361 59 : if (bReversed && nStartIdx == 0 &&
362 59 : osMin > std::string(ppszTmp[nCount - 1]))
363 : {
364 2 : bEmpty = true;
365 2 : return;
366 : }
367 38 : else if (!bReversed && osMin < std::string(ppszTmp[0]))
368 : {
369 5 : if (bSlice)
370 : {
371 1 : bEmpty = true;
372 1 : return;
373 : }
374 4 : bFoundMinIdx = true;
375 4 : nMinIdx = nStartIdx;
376 : }
377 94 : else if (osMin >= std::string(ppszTmp[0]) &&
378 61 : osMin <= std::string(ppszTmp[nCount - 1]))
379 : {
380 68 : for (size_t i = 0; i < nCount; i++)
381 : {
382 68 : if (osMin <= std::string(ppszTmp[i]))
383 : {
384 26 : bFoundMinIdx = true;
385 26 : nMinIdx = nStartIdx + (bReversed ? nCount - 1 - i : i);
386 26 : break;
387 : }
388 : }
389 26 : CPLAssert(bFoundMinIdx);
390 : }
391 : }
392 37 : if (!bFoundMaxIdx)
393 : {
394 54 : if (bReversed && nStartIdx == 0 &&
395 54 : osMax > std::string(ppszTmp[nCount - 1]))
396 : {
397 3 : if (bSlice)
398 : {
399 0 : bEmpty = true;
400 0 : return;
401 : }
402 3 : bFoundMaxIdx = true;
403 3 : nMaxIdx = 0;
404 : }
405 34 : else if (!bReversed && osMax < std::string(ppszTmp[0]))
406 : {
407 1 : if (nStartIdx == 0)
408 : {
409 1 : bEmpty = true;
410 1 : return;
411 : }
412 0 : bFoundMaxIdx = true;
413 0 : nMaxIdx = nStartIdx - 1;
414 : }
415 33 : else if (osMax == std::string(ppszTmp[0]))
416 : {
417 6 : bFoundMaxIdx = true;
418 6 : nMaxIdx = nStartIdx + (bReversed ? nCount - 1 : 0);
419 : }
420 79 : else if (osMax > std::string(ppszTmp[0]) &&
421 52 : osMax <= std::string(ppszTmp[nCount - 1]))
422 : {
423 42 : for (size_t i = 1; i < nCount; i++)
424 : {
425 42 : if (osMax <= std::string(ppszTmp[i]))
426 : {
427 20 : bFoundMaxIdx = true;
428 20 : if (osMax == std::string(ppszTmp[i]))
429 16 : nMaxIdx =
430 16 : nStartIdx + (bReversed ? nCount - 1 - i : i);
431 : else
432 4 : nMaxIdx =
433 4 : nStartIdx +
434 4 : (bReversed ? nCount - 1 - (i - 1) : i - 1);
435 20 : break;
436 : }
437 : }
438 20 : CPLAssert(bFoundMaxIdx);
439 : }
440 : }
441 : }
442 : else
443 : {
444 0 : if (!bFoundMinIdx)
445 : {
446 0 : if (osMin <= std::string(ppszTmp[0]))
447 : {
448 0 : bFoundMinIdx = true;
449 0 : nMinIdx = nStartIdx;
450 : }
451 0 : else if (bLastWasReversed && nStartIdx > 0)
452 : {
453 0 : bFoundMinIdx = true;
454 0 : nMinIdx = nStartIdx - 1;
455 : }
456 : }
457 0 : if (!bFoundMaxIdx)
458 : {
459 0 : if (osMax >= std::string(ppszTmp[0]))
460 : {
461 0 : bFoundMaxIdx = true;
462 0 : nMaxIdx = nStartIdx;
463 : }
464 0 : else if (!bLastWasReversed && nStartIdx > 0)
465 : {
466 0 : bFoundMaxIdx = true;
467 0 : nMaxIdx = nStartIdx - 1;
468 : }
469 : }
470 : }
471 : }
472 :
473 : /************************************************************************/
474 : /* GetDimensionDesc() */
475 : /************************************************************************/
476 :
477 : struct DimensionDesc
478 : {
479 : GUInt64 nStartIdx = 0;
480 : GUInt64 nStep = 1;
481 : GUInt64 nSize = 0;
482 : GUInt64 nOriSize = 0;
483 : bool bSlice = false;
484 : };
485 :
486 : struct DimensionRemapper
487 : {
488 : std::map<std::string, DimensionDesc> oMap{};
489 : };
490 :
491 : static const DimensionDesc *
492 207 : GetDimensionDesc(DimensionRemapper &oDimRemapper,
493 : const GDALMultiDimTranslateOptions *psOptions,
494 : const std::shared_ptr<GDALDimension> &poDim)
495 : {
496 414 : std::string osKey(poDim->GetFullName());
497 : osKey +=
498 207 : CPLSPrintf("_" CPL_FRMT_GUIB, static_cast<GUIntBig>(poDim->GetSize()));
499 207 : auto oIter = oDimRemapper.oMap.find(osKey);
500 317 : if (oIter != oDimRemapper.oMap.end() &&
501 110 : oIter->second.nOriSize == poDim->GetSize())
502 : {
503 110 : return &(oIter->second);
504 : }
505 97 : DimensionDesc desc;
506 97 : desc.nSize = poDim->GetSize();
507 97 : desc.nOriSize = desc.nSize;
508 :
509 194 : CPLString osRadix(poDim->GetName());
510 97 : osRadix += '(';
511 107 : for (const auto &subset : psOptions->aosSubset)
512 : {
513 93 : if (STARTS_WITH(subset.c_str(), osRadix.c_str()))
514 : {
515 83 : auto var = poDim->GetIndexingVariable();
516 166 : if (!var || var->GetDimensionCount() != 1 ||
517 83 : var->GetDimensions()[0]->GetSize() != poDim->GetSize())
518 : {
519 0 : CPLError(CE_Failure, CPLE_AppDefined,
520 : "Dimension %s has a subset specification, but lacks "
521 : "a single dimension indexing variable",
522 0 : poDim->GetName().c_str());
523 0 : return nullptr;
524 : }
525 83 : if (subset.back() != ')')
526 : {
527 2 : CPLError(CE_Failure, CPLE_AppDefined,
528 : "Missing ')' in subset specification.");
529 2 : return nullptr;
530 : }
531 : CPLStringList aosTokens(CSLTokenizeString2(
532 : subset
533 81 : .substr(osRadix.size(), subset.size() - 1 - osRadix.size())
534 : .c_str(),
535 81 : ",", CSLT_HONOURSTRINGS));
536 81 : if (aosTokens.size() == 1)
537 : {
538 26 : desc.bSlice = true;
539 : }
540 81 : if (aosTokens.size() != 1 && aosTokens.size() != 2)
541 : {
542 1 : CPLError(CE_Failure, CPLE_AppDefined,
543 : "Invalid number of values in subset specification.");
544 1 : return nullptr;
545 : }
546 :
547 : const bool bIsNumeric =
548 80 : var->GetDataType().GetClass() == GEDTC_NUMERIC;
549 : const GDALExtendedDataType dt(
550 : bIsNumeric ? GDALExtendedDataType::Create(GDT_Float64)
551 80 : : GDALExtendedDataType::CreateString());
552 :
553 80 : double dfMin = 0;
554 80 : double dfMax = 0;
555 80 : std::string osMin;
556 80 : std::string osMax;
557 80 : if (bIsNumeric)
558 : {
559 80 : if (CPLGetValueType(aosTokens[0]) == CPL_VALUE_STRING ||
560 40 : (aosTokens.size() == 2 &&
561 28 : CPLGetValueType(aosTokens[1]) == CPL_VALUE_STRING))
562 : {
563 0 : CPLError(CE_Failure, CPLE_AppDefined,
564 : "Non numeric bound in subset specification.");
565 0 : return nullptr;
566 : }
567 40 : dfMin = CPLAtof(aosTokens[0]);
568 40 : dfMax = dfMin;
569 40 : if (aosTokens.size() == 2)
570 28 : dfMax = CPLAtof(aosTokens[1]);
571 40 : if (dfMin > dfMax)
572 0 : std::swap(dfMin, dfMax);
573 : }
574 : else
575 : {
576 40 : osMin = aosTokens[0];
577 40 : osMax = osMin;
578 40 : if (aosTokens.size() == 2)
579 26 : osMax = aosTokens[1];
580 40 : if (osMin > osMax)
581 0 : std::swap(osMin, osMax);
582 : }
583 :
584 80 : const size_t nDTSize(dt.GetSize());
585 : const size_t nMaxChunkSize = static_cast<size_t>(std::min(
586 80 : static_cast<GUInt64>(10 * 1000 * 1000), poDim->GetSize()));
587 80 : std::vector<GByte> abyTmp(nDTSize * nMaxChunkSize);
588 80 : double *pdfTmp = reinterpret_cast<double *>(&abyTmp[0]);
589 80 : const char **ppszTmp = reinterpret_cast<const char **>(&abyTmp[0]);
590 80 : GUInt64 nStartIdx = 0;
591 160 : const double EPS = std::max(std::max(1e-10, fabs(dfMin) / 1e10),
592 80 : fabs(dfMax) / 1e10);
593 80 : bool bFoundMinIdx = false;
594 80 : bool bFoundMaxIdx = false;
595 80 : GUInt64 nMinIdx = 0;
596 80 : GUInt64 nMaxIdx = 0;
597 80 : bool bLastWasReversed = false;
598 80 : bool bEmpty = false;
599 : while (true)
600 : {
601 : const size_t nCount = static_cast<size_t>(
602 200 : std::min(static_cast<GUInt64>(nMaxChunkSize),
603 100 : poDim->GetSize() - nStartIdx));
604 100 : if (nCount == 0)
605 20 : break;
606 80 : const GUInt64 anStartId[] = {nStartIdx};
607 80 : const size_t anCount[] = {nCount};
608 160 : if (!var->Read(anStartId, anCount, nullptr, nullptr, dt,
609 80 : &abyTmp[0], nullptr, 0))
610 : {
611 0 : return nullptr;
612 : }
613 80 : if (bIsNumeric)
614 : {
615 40 : FindMinMaxIdxNumeric(
616 40 : var.get(), pdfTmp, nCount, nStartIdx, dfMin, dfMax,
617 40 : desc.bSlice, bFoundMinIdx, nMinIdx, bFoundMaxIdx,
618 : nMaxIdx, bLastWasReversed, bEmpty, EPS);
619 : }
620 : else
621 : {
622 40 : FindMinMaxIdxString(var.get(), ppszTmp, nCount, nStartIdx,
623 40 : osMin, osMax, desc.bSlice, bFoundMinIdx,
624 : nMinIdx, bFoundMaxIdx, nMaxIdx,
625 : bLastWasReversed, bEmpty);
626 : }
627 80 : if (dt.NeedsFreeDynamicMemory())
628 : {
629 200 : for (size_t i = 0; i < nCount; i++)
630 : {
631 160 : dt.FreeDynamicMemory(&abyTmp[i * nDTSize]);
632 : }
633 : }
634 80 : if (bEmpty || (bFoundMinIdx && bFoundMaxIdx) ||
635 : nCount < nMaxChunkSize)
636 : {
637 : break;
638 : }
639 20 : nStartIdx += nMaxChunkSize;
640 20 : }
641 :
642 : // cppcheck-suppress knownConditionTrueFalse
643 80 : if (!bLastWasReversed)
644 : {
645 42 : if (!bFoundMinIdx)
646 6 : bEmpty = true;
647 36 : else if (!bFoundMaxIdx)
648 9 : nMaxIdx = poDim->GetSize() - 1;
649 : else
650 27 : bEmpty = nMaxIdx < nMinIdx;
651 : }
652 : else
653 : {
654 38 : if (!bFoundMaxIdx)
655 8 : bEmpty = true;
656 30 : else if (!bFoundMinIdx)
657 5 : nMinIdx = poDim->GetSize() - 1;
658 : else
659 25 : bEmpty = nMinIdx < nMaxIdx;
660 : }
661 80 : if (bEmpty)
662 : {
663 16 : CPLError(CE_Failure, CPLE_AppDefined,
664 : "Subset specification results in an empty set");
665 16 : return nullptr;
666 : }
667 :
668 : // cppcheck-suppress knownConditionTrueFalse
669 64 : if (!bLastWasReversed)
670 : {
671 34 : CPLAssert(nMaxIdx >= nMinIdx);
672 34 : desc.nStartIdx = nMinIdx;
673 34 : desc.nSize = nMaxIdx - nMinIdx + 1;
674 : }
675 : else
676 : {
677 30 : CPLAssert(nMaxIdx <= nMinIdx);
678 30 : desc.nStartIdx = nMaxIdx;
679 30 : desc.nSize = nMinIdx - nMaxIdx + 1;
680 : }
681 :
682 64 : break;
683 : }
684 : }
685 :
686 82 : for (const auto &scaleFactor : psOptions->aosScaleFactor)
687 : {
688 6 : if (STARTS_WITH(scaleFactor.c_str(), osRadix.c_str()))
689 : {
690 2 : if (scaleFactor.back() != ')')
691 : {
692 0 : CPLError(CE_Failure, CPLE_AppDefined,
693 : "Missing ')' in scalefactor specification.");
694 0 : return nullptr;
695 : }
696 : std::string osScaleFactor(scaleFactor.substr(
697 2 : osRadix.size(), scaleFactor.size() - 1 - osRadix.size()));
698 2 : int nScaleFactor = atoi(osScaleFactor.c_str());
699 2 : if (CPLGetValueType(osScaleFactor.c_str()) != CPL_VALUE_INTEGER ||
700 : nScaleFactor <= 0)
701 : {
702 0 : CPLError(CE_Failure, CPLE_NotSupported,
703 : "Only positive integer scale factor is supported");
704 0 : return nullptr;
705 : }
706 2 : desc.nSize /= nScaleFactor;
707 2 : if (desc.nSize == 0)
708 0 : desc.nSize = 1;
709 2 : desc.nStep *= nScaleFactor;
710 2 : break;
711 : }
712 : }
713 :
714 78 : oDimRemapper.oMap[osKey] = desc;
715 78 : return &oDimRemapper.oMap[osKey];
716 : }
717 :
718 : /************************************************************************/
719 : /* ParseArraySpec() */
720 : /************************************************************************/
721 :
722 : // foo
723 : // name=foo,transpose=[1,0],view=[0],dstname=bar,ot=Float32
724 151 : static bool ParseArraySpec(const std::string &arraySpec, std::string &srcName,
725 : std::string &dstName, int &band,
726 : std::vector<int> &anTransposedAxis,
727 : std::string &viewExpr,
728 : GDALExtendedDataType &outputType, bool &bResampled)
729 : {
730 287 : if (!STARTS_WITH(arraySpec.c_str(), "name=") &&
731 136 : !STARTS_WITH(arraySpec.c_str(), "band="))
732 : {
733 135 : srcName = arraySpec;
734 135 : dstName = arraySpec;
735 135 : auto pos = dstName.rfind('/');
736 135 : if (pos != std::string::npos)
737 22 : dstName = dstName.substr(pos + 1);
738 135 : return true;
739 : }
740 :
741 32 : std::vector<std::string> tokens;
742 32 : std::string curToken;
743 16 : bool bInArray = false;
744 644 : for (size_t i = 0; i < arraySpec.size(); ++i)
745 : {
746 628 : if (!bInArray && arraySpec[i] == ',')
747 : {
748 20 : tokens.emplace_back(std::move(curToken));
749 20 : curToken = std::string();
750 : }
751 : else
752 : {
753 608 : if (arraySpec[i] == '[')
754 : {
755 8 : bInArray = true;
756 : }
757 600 : else if (arraySpec[i] == ']')
758 : {
759 8 : bInArray = false;
760 : }
761 608 : curToken += arraySpec[i];
762 : }
763 : }
764 16 : if (!curToken.empty())
765 : {
766 16 : tokens.emplace_back(std::move(curToken));
767 : }
768 50 : for (const auto &token : tokens)
769 : {
770 36 : if (STARTS_WITH(token.c_str(), "name="))
771 : {
772 15 : srcName = token.substr(strlen("name="));
773 15 : if (dstName.empty())
774 15 : dstName = srcName;
775 : }
776 21 : else if (STARTS_WITH(token.c_str(), "band="))
777 : {
778 1 : band = atoi(token.substr(strlen("band=")).c_str());
779 1 : if (dstName.empty())
780 1 : dstName = CPLSPrintf("Band%d", band);
781 : }
782 20 : else if (STARTS_WITH(token.c_str(), "dstname="))
783 : {
784 10 : dstName = token.substr(strlen("dstname="));
785 : }
786 10 : else if (STARTS_WITH(token.c_str(), "transpose="))
787 : {
788 2 : auto transposeExpr = token.substr(strlen("transpose="));
789 4 : if (transposeExpr.size() < 3 || transposeExpr[0] != '[' ||
790 2 : transposeExpr.back() != ']')
791 : {
792 0 : CPLError(CE_Failure, CPLE_AppDefined,
793 : "Invalid value for transpose");
794 0 : return false;
795 : }
796 2 : transposeExpr = transposeExpr.substr(1, transposeExpr.size() - 2);
797 : CPLStringList aosAxis(
798 2 : CSLTokenizeString2(transposeExpr.c_str(), ",", 0));
799 5 : for (int i = 0; i < aosAxis.size(); ++i)
800 : {
801 4 : int iAxis = atoi(aosAxis[i]);
802 : // check for non-integer characters
803 4 : if (iAxis == 0)
804 : {
805 2 : if (!EQUAL(aosAxis[i], "0"))
806 : {
807 1 : CPLError(CE_Failure, CPLE_AppDefined,
808 : "Invalid value for axis in transpose: %s",
809 : aosAxis[i]);
810 1 : return false;
811 : }
812 : }
813 :
814 3 : anTransposedAxis.push_back(iAxis);
815 : }
816 : }
817 8 : else if (STARTS_WITH(token.c_str(), "view="))
818 : {
819 6 : viewExpr = token.substr(strlen("view="));
820 : }
821 2 : else if (STARTS_WITH(token.c_str(), "ot="))
822 : {
823 0 : auto outputTypeStr = token.substr(strlen("ot="));
824 0 : if (outputTypeStr == "String")
825 0 : outputType = GDALExtendedDataType::CreateString();
826 : else
827 : {
828 0 : auto eDT = GDALGetDataTypeByName(outputTypeStr.c_str());
829 0 : if (eDT == GDT_Unknown)
830 0 : return false;
831 0 : outputType = GDALExtendedDataType::Create(eDT);
832 : }
833 : }
834 2 : else if (STARTS_WITH(token.c_str(), "resample="))
835 : {
836 1 : bResampled = CPLTestBool(token.c_str() + strlen("resample="));
837 : }
838 : else
839 : {
840 1 : CPLError(CE_Failure, CPLE_AppDefined,
841 : "Unexpected array specification part: %s", token.c_str());
842 1 : return false;
843 : }
844 : }
845 14 : return true;
846 : }
847 :
848 : /************************************************************************/
849 : /* TranslateArray() */
850 : /************************************************************************/
851 :
852 148 : static bool TranslateArray(
853 : DimensionRemapper &oDimRemapper,
854 : const std::shared_ptr<GDALMDArray> &poSrcArrayIn,
855 : const std::string &arraySpec,
856 : const std::shared_ptr<GDALGroup> &poSrcRootGroup,
857 : const std::shared_ptr<GDALGroup> &poSrcGroup,
858 : const std::shared_ptr<VRTGroup> &poDstRootGroup,
859 : std::shared_ptr<VRTGroup> &poDstGroup, GDALDataset *poSrcDS,
860 : std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
861 : std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
862 : const GDALMultiDimTranslateOptions *psOptions)
863 : {
864 296 : std::string srcArrayName;
865 296 : std::string dstArrayName;
866 148 : int band = -1;
867 296 : std::vector<int> anTransposedAxis;
868 296 : std::string viewExpr;
869 148 : bool bResampled = false;
870 296 : GDALExtendedDataType outputType(GDALExtendedDataType::Create(GDT_Unknown));
871 148 : if (!ParseArraySpec(arraySpec, srcArrayName, dstArrayName, band,
872 : anTransposedAxis, viewExpr, outputType, bResampled))
873 : {
874 2 : return false;
875 : }
876 :
877 146 : std::shared_ptr<GDALMDArray> srcArray;
878 146 : bool bSrcArrayAccessibleThroughSrcGroup = true;
879 146 : if (poSrcRootGroup && poSrcGroup)
880 : {
881 142 : if (!srcArrayName.empty() && srcArrayName[0] == '/')
882 28 : srcArray = poSrcRootGroup->OpenMDArrayFromFullname(srcArrayName);
883 : else
884 114 : srcArray = poSrcGroup->OpenMDArray(srcArrayName);
885 142 : if (!srcArray)
886 : {
887 3 : if (poSrcArrayIn && poSrcArrayIn->GetFullName() == arraySpec)
888 : {
889 2 : bSrcArrayAccessibleThroughSrcGroup = false;
890 2 : srcArray = poSrcArrayIn;
891 : }
892 : else
893 : {
894 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find array %s",
895 : srcArrayName.c_str());
896 1 : return false;
897 : }
898 : }
899 : }
900 4 : else if (band < 0)
901 : {
902 3 : srcArray = poSrcDS->AsMDArray();
903 : }
904 : else
905 : {
906 1 : auto poBand = poSrcDS->GetRasterBand(band);
907 1 : if (!poBand)
908 0 : return false;
909 1 : srcArray = poBand->AsMDArray();
910 : }
911 :
912 290 : auto tmpArray = srcArray;
913 :
914 145 : if (bResampled)
915 : {
916 : auto newTmpArray =
917 3 : tmpArray->GetResampled(std::vector<std::shared_ptr<GDALDimension>>(
918 1 : tmpArray->GetDimensionCount()),
919 2 : GRIORA_NearestNeighbour, nullptr, nullptr);
920 1 : if (!newTmpArray)
921 0 : return false;
922 1 : tmpArray = std::move(newTmpArray);
923 : }
924 :
925 145 : if (!anTransposedAxis.empty())
926 : {
927 1 : auto newTmpArray = tmpArray->Transpose(anTransposedAxis);
928 1 : if (!newTmpArray)
929 0 : return false;
930 1 : tmpArray = std::move(newTmpArray);
931 : }
932 145 : const auto &srcArrayDims(tmpArray->GetDimensions());
933 : std::map<std::shared_ptr<GDALDimension>, std::shared_ptr<GDALDimension>>
934 290 : oMapSubsetDimToSrcDim;
935 :
936 290 : std::vector<GDALMDArray::ViewSpec> viewSpecs;
937 145 : if (!viewExpr.empty())
938 : {
939 6 : if (!psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty())
940 : {
941 0 : CPLError(CE_Failure, CPLE_NotSupported,
942 : "View specification not supported when used together "
943 : "with subset and/or scalefactor options");
944 0 : return false;
945 : }
946 6 : auto newTmpArray = tmpArray->GetView(viewExpr, true, viewSpecs);
947 6 : if (!newTmpArray)
948 0 : return false;
949 6 : tmpArray = std::move(newTmpArray);
950 : }
951 188 : else if (!psOptions->aosSubset.empty() ||
952 49 : !psOptions->aosScaleFactor.empty())
953 : {
954 98 : bool bHasModifiedDim = false;
955 98 : viewExpr = '[';
956 194 : for (size_t i = 0; i < srcArrayDims.size(); ++i)
957 : {
958 112 : const auto &srcDim(srcArrayDims[i]);
959 : const auto poDimDesc =
960 112 : GetDimensionDesc(oDimRemapper, psOptions, srcDim);
961 112 : if (poDimDesc == nullptr)
962 16 : return false;
963 96 : if (i > 0)
964 14 : viewExpr += ',';
965 76 : if (!poDimDesc->bSlice && poDimDesc->nStartIdx == 0 &&
966 172 : poDimDesc->nStep == 1 && poDimDesc->nSize == srcDim->GetSize())
967 : {
968 28 : viewExpr += ":";
969 : }
970 : else
971 : {
972 68 : bHasModifiedDim = true;
973 : viewExpr += CPLSPrintf(
974 68 : CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStartIdx));
975 68 : if (!poDimDesc->bSlice)
976 : {
977 48 : viewExpr += ':';
978 : viewExpr +=
979 : CPLSPrintf(CPL_FRMT_GUIB,
980 48 : static_cast<GUInt64>(poDimDesc->nStartIdx +
981 48 : poDimDesc->nSize *
982 48 : poDimDesc->nStep));
983 48 : viewExpr += ':';
984 : viewExpr += CPLSPrintf(
985 48 : CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStep));
986 : }
987 : }
988 : }
989 82 : viewExpr += ']';
990 82 : if (bHasModifiedDim)
991 : {
992 66 : auto tmpArrayNew = tmpArray->GetView(viewExpr, false, viewSpecs);
993 66 : if (!tmpArrayNew)
994 0 : return false;
995 66 : tmpArray = std::move(tmpArrayNew);
996 66 : size_t j = 0;
997 66 : const auto &tmpArrayDims(tmpArray->GetDimensions());
998 146 : for (size_t i = 0; i < srcArrayDims.size(); ++i)
999 : {
1000 80 : const auto &srcDim(srcArrayDims[i]);
1001 : const auto poDimDesc =
1002 80 : GetDimensionDesc(oDimRemapper, psOptions, srcDim);
1003 80 : if (poDimDesc == nullptr)
1004 0 : return false;
1005 80 : if (poDimDesc->bSlice)
1006 20 : continue;
1007 60 : CPLAssert(j < tmpArrayDims.size());
1008 60 : oMapSubsetDimToSrcDim[tmpArrayDims[j]] = srcDim;
1009 60 : j++;
1010 : }
1011 : }
1012 : else
1013 : {
1014 16 : viewExpr.clear();
1015 : }
1016 : }
1017 :
1018 129 : int idxSliceSpec = -1;
1019 201 : for (size_t i = 0; i < viewSpecs.size(); ++i)
1020 : {
1021 72 : if (viewSpecs[i].m_osFieldName.empty())
1022 : {
1023 72 : if (idxSliceSpec >= 0)
1024 : {
1025 0 : idxSliceSpec = -1;
1026 0 : break;
1027 : }
1028 : else
1029 : {
1030 72 : idxSliceSpec = static_cast<int>(i);
1031 : }
1032 : }
1033 : }
1034 :
1035 : // Map source dimensions to target dimensions
1036 258 : std::vector<std::shared_ptr<GDALDimension>> dstArrayDims;
1037 129 : const auto &tmpArrayDims(tmpArray->GetDimensions());
1038 278 : for (size_t i = 0; i < tmpArrayDims.size(); ++i)
1039 : {
1040 149 : const auto &srcDim(tmpArrayDims[i]);
1041 149 : std::string srcDimFullName(srcDim->GetFullName());
1042 :
1043 0 : std::shared_ptr<GDALDimension> dstDim;
1044 : {
1045 298 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1046 149 : if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
1047 : {
1048 : dstDim =
1049 88 : poDstRootGroup->OpenDimensionFromFullname(srcDimFullName);
1050 : }
1051 : }
1052 149 : if (dstDim)
1053 : {
1054 45 : dstArrayDims.emplace_back(dstDim);
1055 45 : continue;
1056 : }
1057 :
1058 104 : auto oIter = mapSrcToDstDims.find(srcDimFullName);
1059 104 : if (oIter != mapSrcToDstDims.end())
1060 : {
1061 2 : dstArrayDims.emplace_back(oIter->second);
1062 2 : continue;
1063 : }
1064 102 : auto oIterRealSrcDim = oMapSubsetDimToSrcDim.find(srcDim);
1065 102 : if (oIterRealSrcDim != oMapSubsetDimToSrcDim.end())
1066 : {
1067 52 : srcDimFullName = oIterRealSrcDim->second->GetFullName();
1068 52 : oIter = mapSrcToDstDims.find(srcDimFullName);
1069 52 : if (oIter != mapSrcToDstDims.end())
1070 : {
1071 10 : dstArrayDims.emplace_back(oIter->second);
1072 10 : continue;
1073 : }
1074 : }
1075 :
1076 92 : const auto nDimSize = srcDim->GetSize();
1077 92 : std::string newDimNameFullName(srcDimFullName);
1078 92 : std::string newDimName(srcDim->GetName());
1079 92 : int nIncr = 2;
1080 92 : std::string osDstGroupFullName(poDstGroup->GetFullName());
1081 92 : if (osDstGroupFullName == "/")
1082 90 : osDstGroupFullName.clear();
1083 184 : auto oIter2 = mapDstDimFullNames.find(osDstGroupFullName + '/' +
1084 184 : srcDim->GetName());
1085 97 : while (oIter2 != mapDstDimFullNames.end() &&
1086 4 : oIter2->second->GetSize() != nDimSize)
1087 : {
1088 1 : newDimName = srcDim->GetName() + CPLSPrintf("_%d", nIncr);
1089 2 : newDimNameFullName = osDstGroupFullName + '/' + srcDim->GetName() +
1090 1 : CPLSPrintf("_%d", nIncr);
1091 1 : nIncr++;
1092 1 : oIter2 = mapDstDimFullNames.find(newDimNameFullName);
1093 : }
1094 95 : if (oIter2 != mapDstDimFullNames.end() &&
1095 3 : oIter2->second->GetSize() == nDimSize)
1096 : {
1097 3 : dstArrayDims.emplace_back(oIter2->second);
1098 3 : continue;
1099 : }
1100 :
1101 178 : dstDim = poDstGroup->CreateDimension(newDimName, srcDim->GetType(),
1102 89 : srcDim->GetDirection(), nDimSize);
1103 89 : if (!dstDim)
1104 0 : return false;
1105 89 : if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
1106 : {
1107 79 : mapSrcToDstDims[srcDimFullName] = dstDim;
1108 : }
1109 89 : mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
1110 89 : dstArrayDims.emplace_back(dstDim);
1111 :
1112 0 : std::shared_ptr<GDALMDArray> srcIndexVar;
1113 89 : GDALMDArray::Range range;
1114 89 : range.m_nStartIdx = 0;
1115 89 : range.m_nIncr = 1;
1116 89 : std::string indexingVarSpec;
1117 89 : if (idxSliceSpec >= 0)
1118 : {
1119 54 : const auto &viewSpec(viewSpecs[idxSliceSpec]);
1120 54 : auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
1121 53 : if (iParentDim != static_cast<size_t>(-1) &&
1122 : (srcIndexVar =
1123 107 : srcArrayDims[iParentDim]->GetIndexingVariable()) !=
1124 51 : nullptr &&
1125 158 : srcIndexVar->GetDimensionCount() == 1 &&
1126 51 : srcIndexVar->GetFullName() != srcArray->GetFullName())
1127 : {
1128 15 : CPLAssert(iParentDim < viewSpec.m_parentRanges.size());
1129 15 : range = viewSpec.m_parentRanges[iParentDim];
1130 15 : indexingVarSpec = "name=" + srcIndexVar->GetFullName();
1131 15 : indexingVarSpec += ",dstname=" + newDimName;
1132 30 : if (psOptions->aosSubset.empty() &&
1133 15 : psOptions->aosScaleFactor.empty())
1134 : {
1135 15 : if (range.m_nStartIdx != 0 || range.m_nIncr != 1 ||
1136 6 : srcArrayDims[iParentDim]->GetSize() !=
1137 6 : srcDim->GetSize())
1138 : {
1139 3 : indexingVarSpec += ",view=[";
1140 4 : if (range.m_nIncr > 0 ||
1141 1 : range.m_nStartIdx != srcDim->GetSize() - 1)
1142 : {
1143 : indexingVarSpec +=
1144 2 : CPLSPrintf(CPL_FRMT_GUIB, range.m_nStartIdx);
1145 : }
1146 3 : indexingVarSpec += ':';
1147 3 : if (range.m_nIncr > 0)
1148 : {
1149 : const auto nEndIdx =
1150 2 : range.m_nStartIdx +
1151 2 : range.m_nIncr * srcDim->GetSize();
1152 : indexingVarSpec +=
1153 2 : CPLSPrintf(CPL_FRMT_GUIB, nEndIdx);
1154 : }
1155 2 : else if (range.m_nStartIdx >
1156 1 : -range.m_nIncr * srcDim->GetSize())
1157 : {
1158 : const auto nEndIdx =
1159 0 : range.m_nStartIdx +
1160 0 : range.m_nIncr * srcDim->GetSize();
1161 : indexingVarSpec +=
1162 0 : CPLSPrintf(CPL_FRMT_GUIB, nEndIdx - 1);
1163 : }
1164 3 : indexingVarSpec += ':';
1165 : indexingVarSpec +=
1166 3 : CPLSPrintf(CPL_FRMT_GIB, range.m_nIncr);
1167 3 : indexingVarSpec += ']';
1168 : }
1169 : }
1170 : }
1171 : }
1172 : else
1173 : {
1174 35 : srcIndexVar = srcDim->GetIndexingVariable();
1175 35 : if (srcIndexVar)
1176 : {
1177 33 : indexingVarSpec = srcIndexVar->GetFullName();
1178 : }
1179 : }
1180 137 : if (srcIndexVar && !indexingVarSpec.empty() &&
1181 48 : srcIndexVar->GetFullName() != srcArray->GetFullName())
1182 : {
1183 38 : if (poSrcRootGroup)
1184 : {
1185 28 : if (!TranslateArray(oDimRemapper, srcIndexVar, indexingVarSpec,
1186 : poSrcRootGroup, poSrcGroup, poDstRootGroup,
1187 : poDstGroup, poSrcDS, mapSrcToDstDims,
1188 : mapDstDimFullNames, psOptions))
1189 : {
1190 0 : return false;
1191 : }
1192 : }
1193 : else
1194 : {
1195 10 : bool bIndexingVarCreated = false;
1196 19 : if (srcIndexVar->GetName() == "X" ||
1197 9 : srcIndexVar->GetName() == "Y")
1198 : {
1199 5 : GDALGeoTransform gt;
1200 10 : if (poSrcDS->GetGeoTransform(gt) == CE_None &&
1201 5 : gt.IsAxisAligned())
1202 : {
1203 : auto var = poDstGroup->CreateVRTMDArray(
1204 : newDimName, {dstDim},
1205 25 : GDALExtendedDataType::Create(GDT_Float64));
1206 5 : if (var)
1207 : {
1208 : const double dfStart =
1209 5 : srcIndexVar->GetName() == "X"
1210 5 : ? gt.xorig +
1211 1 : (range.m_nStartIdx + 0.5) * gt.xscale
1212 4 : : gt.yorig +
1213 4 : (range.m_nStartIdx + 0.5) * gt.yscale;
1214 : const double dfIncr =
1215 5 : (srcIndexVar->GetName() == "X" ? gt.xscale
1216 5 : : gt.yscale) *
1217 5 : range.m_nIncr;
1218 : auto poSource = std::make_unique<
1219 : VRTMDArraySourceRegularlySpaced>(dfStart,
1220 5 : dfIncr);
1221 5 : var->AddSource(std::move(poSource));
1222 5 : bIndexingVarCreated = true;
1223 : }
1224 : // else: error emitted by CreateVRTMDArray()
1225 : }
1226 : }
1227 :
1228 : // Arbitrary: to avoid blowing up RAM
1229 10 : constexpr size_t MAX_SIZE_FOR_INDEXING_VAR = 1000 * 1000;
1230 25 : if (!bIndexingVarCreated &&
1231 10 : srcIndexVar->GetDimensionCount() == 1 &&
1232 20 : srcIndexVar->GetDataType().GetClass() != GEDTC_COMPOUND &&
1233 5 : srcIndexVar->GetDimensions()[0]->GetSize() <
1234 : MAX_SIZE_FOR_INDEXING_VAR)
1235 : {
1236 : auto var = poDstGroup->CreateVRTMDArray(
1237 20 : newDimName, {dstDim}, srcIndexVar->GetDataType());
1238 5 : if (var)
1239 : {
1240 10 : std::vector<GUInt64> anOffset = {0};
1241 : const size_t nCount = static_cast<size_t>(
1242 5 : srcIndexVar->GetDimensions()[0]->GetSize());
1243 10 : std::vector<size_t> anCount = {nCount};
1244 10 : std::vector<GByte> abyValues;
1245 5 : abyValues.resize(srcIndexVar->GetDataType().GetSize() *
1246 : nCount);
1247 5 : const GInt64 arrayStep[] = {1};
1248 5 : const GPtrDiff_t anBufferStride[] = {1};
1249 10 : srcIndexVar->Read(anOffset.data(), anCount.data(),
1250 : arrayStep, anBufferStride,
1251 5 : srcIndexVar->GetDataType(),
1252 5 : abyValues.data());
1253 : auto poSource =
1254 : std::make_unique<VRTMDArraySourceInlinedValues>(
1255 0 : var.get(),
1256 5 : /* bIsConstantValue = */ false,
1257 5 : std::move(anOffset), std::move(anCount),
1258 10 : std::move(abyValues));
1259 5 : var->AddSource(std::move(poSource));
1260 : }
1261 : // else: error emitted by CreateVRTMDArray()
1262 : }
1263 5 : else if (!bIndexingVarCreated)
1264 : {
1265 0 : CPLDebug("GDAL", "Cannot create indexing variable for %s",
1266 0 : srcIndexVar->GetName().c_str());
1267 : }
1268 : }
1269 :
1270 76 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1271 76 : auto poDstIndexingVar(poDstGroup->OpenMDArray(newDimName));
1272 38 : if (poDstIndexingVar)
1273 37 : dstDim->SetIndexingVariable(std::move(poDstIndexingVar));
1274 : }
1275 : }
1276 258 : if (outputType.GetClass() == GEDTC_NUMERIC &&
1277 129 : outputType.GetNumericDataType() == GDT_Unknown)
1278 : {
1279 129 : outputType = GDALExtendedDataType(tmpArray->GetDataType());
1280 : }
1281 :
1282 258 : CPLStringList aosArrayCO;
1283 184 : if (!bResampled && anTransposedAxis.empty() && viewExpr.empty() &&
1284 353 : psOptions->aosSubset.empty() && psOptions->aosScaleFactor.empty() &&
1285 40 : srcArray->GetDimensionCount() == dstArrayDims.size())
1286 : {
1287 80 : const auto anBlockSize = srcArray->GetBlockSize();
1288 80 : std::string osBlockSize;
1289 43 : for (auto v : anBlockSize)
1290 : {
1291 42 : if (v == 0)
1292 : {
1293 39 : osBlockSize.clear();
1294 39 : break;
1295 : }
1296 3 : if (!osBlockSize.empty())
1297 2 : osBlockSize += ',';
1298 3 : osBlockSize += std::to_string(v);
1299 : }
1300 40 : if (!osBlockSize.empty())
1301 1 : aosArrayCO.SetNameValue("BLOCKSIZE", osBlockSize.c_str());
1302 : }
1303 :
1304 : auto dstArray = poDstGroup->CreateVRTMDArray(dstArrayName, dstArrayDims,
1305 258 : outputType, aosArrayCO.List());
1306 129 : if (!dstArray)
1307 0 : return false;
1308 :
1309 129 : GUInt64 nCurCost = 0;
1310 129 : dstArray->CopyFromAllExceptValues(srcArray.get(), false, nCurCost, 0,
1311 : nullptr, nullptr);
1312 129 : if (bResampled)
1313 1 : dstArray->SetSpatialRef(tmpArray->GetSpatialRef().get());
1314 :
1315 129 : if (idxSliceSpec >= 0)
1316 : {
1317 144 : std::set<size_t> oSetParentDimIdxNotInArray;
1318 166 : for (size_t i = 0; i < srcArrayDims.size(); ++i)
1319 : {
1320 94 : oSetParentDimIdxNotInArray.insert(i);
1321 : }
1322 72 : const auto &viewSpec(viewSpecs[idxSliceSpec]);
1323 145 : for (size_t i = 0; i < tmpArrayDims.size(); ++i)
1324 : {
1325 73 : auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
1326 73 : if (iParentDim != static_cast<size_t>(-1))
1327 : {
1328 72 : oSetParentDimIdxNotInArray.erase(iParentDim);
1329 : }
1330 : }
1331 94 : for (const auto parentDimIdx : oSetParentDimIdxNotInArray)
1332 : {
1333 22 : const auto &srcDim(srcArrayDims[parentDimIdx]);
1334 : const auto nStartIdx =
1335 22 : viewSpec.m_parentRanges[parentDimIdx].m_nStartIdx;
1336 22 : if (nStartIdx < static_cast<GUInt64>(INT_MAX))
1337 : {
1338 : auto dstAttr = dstArray->CreateAttribute(
1339 44 : "DIM_" + srcDim->GetName() + "_INDEX", {},
1340 88 : GDALExtendedDataType::Create(GDT_Int32));
1341 22 : dstAttr->Write(static_cast<int>(nStartIdx));
1342 : }
1343 : else
1344 : {
1345 : auto dstAttr = dstArray->CreateAttribute(
1346 0 : "DIM_" + srcDim->GetName() + "_INDEX", {},
1347 0 : GDALExtendedDataType::CreateString());
1348 0 : dstAttr->Write(CPLSPrintf(CPL_FRMT_GUIB,
1349 : static_cast<GUIntBig>(nStartIdx)));
1350 : }
1351 :
1352 44 : auto srcIndexVar(srcDim->GetIndexingVariable());
1353 22 : if (srcIndexVar && srcIndexVar->GetDimensionCount() == 1)
1354 : {
1355 22 : const auto &dt(srcIndexVar->GetDataType());
1356 44 : std::vector<GByte> abyTmp(dt.GetSize());
1357 22 : const size_t nCount = 1;
1358 44 : if (srcIndexVar->Read(&nStartIdx, &nCount, nullptr, nullptr, dt,
1359 22 : &abyTmp[0], nullptr, 0))
1360 : {
1361 : {
1362 : auto dstAttr = dstArray->CreateAttribute(
1363 66 : "DIM_" + srcDim->GetName() + "_VALUE", {}, dt);
1364 22 : dstAttr->Write(abyTmp.data(), abyTmp.size());
1365 22 : dt.FreeDynamicMemory(&abyTmp[0]);
1366 : }
1367 :
1368 22 : const auto &unit(srcIndexVar->GetUnit());
1369 22 : if (!unit.empty())
1370 : {
1371 : auto dstAttr = dstArray->CreateAttribute(
1372 0 : "DIM_" + srcDim->GetName() + "_UNIT", {},
1373 0 : GDALExtendedDataType::CreateString());
1374 0 : dstAttr->Write(unit.c_str());
1375 : }
1376 : }
1377 : }
1378 : }
1379 : }
1380 :
1381 129 : double dfStart = 0.0;
1382 129 : double dfIncrement = 0.0;
1383 131 : if (!bSrcArrayAccessibleThroughSrcGroup &&
1384 2 : tmpArray->IsRegularlySpaced(dfStart, dfIncrement))
1385 : {
1386 : auto poSource = std::make_unique<VRTMDArraySourceRegularlySpaced>(
1387 2 : dfStart, dfIncrement);
1388 2 : dstArray->AddSource(std::move(poSource));
1389 : }
1390 : else
1391 : {
1392 127 : const auto dimCount(tmpArray->GetDimensionCount());
1393 254 : std::vector<GUInt64> anSrcOffset(dimCount);
1394 254 : std::vector<GUInt64> anCount(dimCount);
1395 274 : for (size_t i = 0; i < dimCount; ++i)
1396 : {
1397 147 : anCount[i] = tmpArrayDims[i]->GetSize();
1398 : }
1399 254 : std::vector<GUInt64> anStep(dimCount, 1);
1400 254 : std::vector<GUInt64> anDstOffset(dimCount);
1401 : auto poSource = std::make_unique<VRTMDArraySourceFromArray>(
1402 254 : dstArray.get(), false, false, poSrcDS->GetDescription(),
1403 254 : band < 0 ? srcArray->GetFullName() : std::string(),
1404 255 : band >= 1 ? CPLSPrintf("%d", band) : std::string(),
1405 127 : std::move(anTransposedAxis),
1406 253 : bResampled ? (viewExpr.empty()
1407 : ? std::string("resample=true")
1408 127 : : std::string("resample=true,").append(viewExpr))
1409 126 : : std::move(viewExpr),
1410 127 : std::move(anSrcOffset), std::move(anCount), std::move(anStep),
1411 254 : std::move(anDstOffset));
1412 127 : dstArray->AddSource(std::move(poSource));
1413 : }
1414 :
1415 129 : return true;
1416 : }
1417 :
1418 : /************************************************************************/
1419 : /* GetGroup() */
1420 : /************************************************************************/
1421 :
1422 : static std::shared_ptr<GDALGroup>
1423 6 : GetGroup(const std::shared_ptr<GDALGroup> &poRootGroup,
1424 : const std::string &fullName)
1425 : {
1426 12 : auto poCurGroup = poRootGroup;
1427 12 : CPLStringList aosTokens(CSLTokenizeString2(fullName.c_str(), "/", 0));
1428 10 : for (int i = 0; i < aosTokens.size(); i++)
1429 : {
1430 10 : auto poCurGroupNew = poCurGroup->OpenGroup(aosTokens[i], nullptr);
1431 5 : if (!poCurGroupNew)
1432 : {
1433 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
1434 : aosTokens[i]);
1435 1 : return nullptr;
1436 : }
1437 4 : poCurGroup = std::move(poCurGroupNew);
1438 : }
1439 5 : return poCurGroup;
1440 : }
1441 :
1442 : /************************************************************************/
1443 : /* CopyGroup() */
1444 : /************************************************************************/
1445 :
1446 17 : static bool CopyGroup(
1447 : DimensionRemapper &oDimRemapper,
1448 : const std::shared_ptr<VRTGroup> &poDstRootGroup,
1449 : std::shared_ptr<VRTGroup> &poDstGroup,
1450 : const std::shared_ptr<GDALGroup> &poSrcRootGroup,
1451 : const std::shared_ptr<GDALGroup> &poSrcGroup, GDALDataset *poSrcDS,
1452 : std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
1453 : std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
1454 : const GDALMultiDimTranslateOptions *psOptions, bool bRecursive)
1455 : {
1456 34 : const auto srcDims = poSrcGroup->GetDimensions();
1457 34 : std::map<std::string, std::string> mapSrcVariableNameToIndexedDimName;
1458 29 : for (const auto &dim : srcDims)
1459 : {
1460 15 : const auto poDimDesc = GetDimensionDesc(oDimRemapper, psOptions, dim);
1461 15 : if (poDimDesc == nullptr)
1462 3 : return false;
1463 12 : if (poDimDesc->bSlice)
1464 2 : continue;
1465 : auto dstDim =
1466 : poDstGroup->CreateDimension(dim->GetName(), dim->GetType(),
1467 10 : dim->GetDirection(), poDimDesc->nSize);
1468 10 : if (!dstDim)
1469 0 : return false;
1470 10 : mapSrcToDstDims[dim->GetFullName()] = dstDim;
1471 10 : mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
1472 20 : auto poIndexingVarSrc(dim->GetIndexingVariable());
1473 10 : if (poIndexingVarSrc)
1474 : {
1475 10 : mapSrcVariableNameToIndexedDimName[poIndexingVarSrc->GetName()] =
1476 20 : dim->GetFullName();
1477 : }
1478 : }
1479 :
1480 14 : if (!(poSrcGroup == poSrcRootGroup && psOptions->aosGroup.empty()))
1481 : {
1482 11 : auto attrs = poSrcGroup->GetAttributes();
1483 15 : for (const auto &attr : attrs)
1484 : {
1485 : auto dstAttr = poDstGroup->CreateAttribute(
1486 4 : attr->GetName(), attr->GetDimensionsSize(),
1487 8 : attr->GetDataType());
1488 4 : if (!dstAttr)
1489 : {
1490 0 : if (!psOptions->bStrict)
1491 0 : continue;
1492 0 : return false;
1493 : }
1494 4 : auto raw(attr->ReadAsRaw());
1495 4 : if (!dstAttr->Write(raw.data(), raw.size()) && !psOptions->bStrict)
1496 0 : return false;
1497 : }
1498 : }
1499 :
1500 : auto arrayNames =
1501 28 : poSrcGroup->GetMDArrayNames(psOptions->aosArrayOptions.List());
1502 40 : for (const auto &name : arrayNames)
1503 : {
1504 26 : if (!TranslateArray(oDimRemapper, nullptr, name, poSrcRootGroup,
1505 : poSrcGroup, poDstRootGroup, poDstGroup, poSrcDS,
1506 : mapSrcToDstDims, mapDstDimFullNames, psOptions))
1507 : {
1508 0 : return false;
1509 : }
1510 :
1511 : // If this array is the indexing variable of a dimension, link them
1512 : // together.
1513 52 : auto srcArray = poSrcGroup->OpenMDArray(name);
1514 26 : CPLAssert(srcArray);
1515 52 : auto dstArray = poDstGroup->OpenMDArray(name);
1516 26 : CPLAssert(dstArray);
1517 : auto oIterDimName =
1518 26 : mapSrcVariableNameToIndexedDimName.find(srcArray->GetName());
1519 26 : if (oIterDimName != mapSrcVariableNameToIndexedDimName.end())
1520 : {
1521 : auto oCorrespondingDimIter =
1522 10 : mapSrcToDstDims.find(oIterDimName->second);
1523 10 : if (oCorrespondingDimIter != mapSrcToDstDims.end())
1524 : {
1525 10 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1526 20 : oCorrespondingDimIter->second->SetIndexingVariable(
1527 10 : std::move(dstArray));
1528 : }
1529 : }
1530 : }
1531 :
1532 14 : if (bRecursive)
1533 : {
1534 14 : auto groupNames = poSrcGroup->GetGroupNames();
1535 20 : for (const auto &name : groupNames)
1536 : {
1537 6 : auto srcSubGroup = poSrcGroup->OpenGroup(name);
1538 6 : if (!srcSubGroup)
1539 : {
1540 0 : return false;
1541 : }
1542 6 : auto dstSubGroup = poDstGroup->CreateVRTGroup(name);
1543 6 : if (!dstSubGroup)
1544 : {
1545 0 : return false;
1546 : }
1547 6 : if (!CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
1548 : poSrcRootGroup, srcSubGroup, poSrcDS,
1549 : mapSrcToDstDims, mapDstDimFullNames, psOptions,
1550 : true))
1551 : {
1552 0 : return false;
1553 : }
1554 : }
1555 : }
1556 14 : return true;
1557 : }
1558 :
1559 : /************************************************************************/
1560 : /* ParseGroupSpec() */
1561 : /************************************************************************/
1562 :
1563 : // foo
1564 : // name=foo,dstname=bar,recursive=no
1565 7 : static bool ParseGroupSpec(const std::string &groupSpec, std::string &srcName,
1566 : std::string &dstName, bool &bRecursive)
1567 : {
1568 7 : bRecursive = true;
1569 7 : if (!STARTS_WITH(groupSpec.c_str(), "name="))
1570 : {
1571 5 : srcName = groupSpec;
1572 5 : return true;
1573 : }
1574 :
1575 4 : CPLStringList aosTokens(CSLTokenizeString2(groupSpec.c_str(), ",", 0));
1576 5 : for (int i = 0; i < aosTokens.size(); i++)
1577 : {
1578 4 : const std::string token(aosTokens[i]);
1579 4 : if (STARTS_WITH(token.c_str(), "name="))
1580 : {
1581 2 : srcName = token.substr(strlen("name="));
1582 : }
1583 2 : else if (STARTS_WITH(token.c_str(), "dstname="))
1584 : {
1585 1 : dstName = token.substr(strlen("dstname="));
1586 : }
1587 1 : else if (token == "recursive=no")
1588 : {
1589 0 : bRecursive = false;
1590 : }
1591 : else
1592 : {
1593 1 : CPLError(CE_Failure, CPLE_AppDefined,
1594 : "Unexpected group specification part: %s", token.c_str());
1595 1 : return false;
1596 : }
1597 : }
1598 1 : return true;
1599 : }
1600 :
1601 : /************************************************************************/
1602 : /* TranslateInternal() */
1603 : /************************************************************************/
1604 :
1605 106 : static bool TranslateInternal(std::shared_ptr<VRTGroup> &poDstRootGroup,
1606 : GDALDataset *poSrcDS,
1607 : const GDALMultiDimTranslateOptions *psOptions)
1608 : {
1609 :
1610 212 : auto poSrcRootGroup = poSrcDS->GetRootGroup();
1611 106 : if (poSrcRootGroup)
1612 : {
1613 102 : if (psOptions->aosGroup.empty())
1614 : {
1615 192 : auto attrs = poSrcRootGroup->GetAttributes();
1616 104 : for (const auto &attr : attrs)
1617 : {
1618 8 : if (attr->GetName() == "Conventions")
1619 2 : continue;
1620 : auto dstAttr = poDstRootGroup->CreateAttribute(
1621 6 : attr->GetName(), attr->GetDimensionsSize(),
1622 18 : attr->GetDataType());
1623 6 : if (dstAttr)
1624 : {
1625 12 : auto raw(attr->ReadAsRaw());
1626 6 : dstAttr->Write(raw.data(), raw.size());
1627 : }
1628 : }
1629 : }
1630 : }
1631 :
1632 212 : DimensionRemapper oDimRemapper;
1633 212 : std::map<std::string, std::shared_ptr<GDALDimension>> mapSrcToDstDims;
1634 212 : std::map<std::string, std::shared_ptr<GDALDimension>> mapDstDimFullNames;
1635 106 : if (!psOptions->aosGroup.empty())
1636 : {
1637 6 : if (poSrcRootGroup == nullptr)
1638 : {
1639 0 : CPLError(
1640 : CE_Failure, CPLE_AppDefined,
1641 : "No multidimensional source dataset: -group cannot be used");
1642 0 : return false;
1643 : }
1644 6 : if (psOptions->aosGroup.size() == 1)
1645 : {
1646 10 : std::string srcName;
1647 10 : std::string dstName;
1648 : bool bRecursive;
1649 5 : if (!ParseGroupSpec(psOptions->aosGroup[0], srcName, dstName,
1650 : bRecursive))
1651 1 : return false;
1652 8 : auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
1653 4 : if (!poSrcGroup)
1654 1 : return false;
1655 3 : return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
1656 : poSrcRootGroup, poSrcGroup, poSrcDS,
1657 : mapSrcToDstDims, mapDstDimFullNames, psOptions,
1658 3 : bRecursive);
1659 : }
1660 : else
1661 : {
1662 3 : for (const auto &osGroupSpec : psOptions->aosGroup)
1663 : {
1664 2 : std::string srcName;
1665 2 : std::string dstName;
1666 : bool bRecursive;
1667 2 : if (!ParseGroupSpec(osGroupSpec, srcName, dstName, bRecursive))
1668 0 : return false;
1669 2 : auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
1670 2 : if (!poSrcGroup)
1671 0 : return false;
1672 2 : if (dstName.empty())
1673 1 : dstName = poSrcGroup->GetName();
1674 2 : auto dstSubGroup = poDstRootGroup->CreateVRTGroup(dstName);
1675 4 : if (!dstSubGroup ||
1676 2 : !CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
1677 : poSrcRootGroup, poSrcGroup, poSrcDS,
1678 : mapSrcToDstDims, mapDstDimFullNames, psOptions,
1679 : bRecursive))
1680 : {
1681 0 : return false;
1682 : }
1683 : }
1684 : }
1685 : }
1686 100 : else if (!psOptions->aosArraySpec.empty())
1687 : {
1688 169 : for (const auto &arraySpec : psOptions->aosArraySpec)
1689 : {
1690 94 : if (!TranslateArray(oDimRemapper, nullptr, arraySpec,
1691 : poSrcRootGroup, poSrcRootGroup, poDstRootGroup,
1692 : poDstRootGroup, poSrcDS, mapSrcToDstDims,
1693 : mapDstDimFullNames, psOptions))
1694 : {
1695 19 : return false;
1696 : }
1697 : }
1698 : }
1699 : else
1700 : {
1701 6 : if (poSrcRootGroup == nullptr)
1702 : {
1703 0 : CPLError(CE_Failure, CPLE_AppDefined,
1704 : "No multidimensional source dataset");
1705 0 : return false;
1706 : }
1707 6 : return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
1708 : poSrcRootGroup, poSrcRootGroup, poSrcDS,
1709 6 : mapSrcToDstDims, mapDstDimFullNames, psOptions, true);
1710 : }
1711 :
1712 76 : return true;
1713 : }
1714 :
1715 : /************************************************************************/
1716 : /* CopyToNonMultiDimensionalDriver() */
1717 : /************************************************************************/
1718 :
1719 : static GDALDatasetH
1720 4 : CopyToNonMultiDimensionalDriver(GDALDriver *poDriver, const char *pszDest,
1721 : const std::shared_ptr<GDALGroup> &poRG,
1722 : const GDALMultiDimTranslateOptions *psOptions)
1723 : {
1724 4 : std::shared_ptr<GDALMDArray> srcArray;
1725 4 : if (psOptions && !psOptions->aosArraySpec.empty())
1726 : {
1727 3 : if (psOptions->aosArraySpec.size() != 1)
1728 : {
1729 0 : CPLError(CE_Failure, CPLE_NotSupported,
1730 : "For output to a non-multidimensional driver, only "
1731 : "one array should be specified");
1732 0 : return nullptr;
1733 : }
1734 6 : std::string srcArrayName;
1735 6 : std::string dstArrayName;
1736 3 : int band = -1;
1737 6 : std::vector<int> anTransposedAxis;
1738 6 : std::string viewExpr;
1739 : GDALExtendedDataType outputType(
1740 3 : GDALExtendedDataType::Create(GDT_Unknown));
1741 3 : bool bResampled = false;
1742 3 : ParseArraySpec(psOptions->aosArraySpec[0], srcArrayName, dstArrayName,
1743 : band, anTransposedAxis, viewExpr, outputType,
1744 : bResampled);
1745 3 : srcArray = poRG->OpenMDArray(dstArrayName);
1746 : }
1747 : else
1748 : {
1749 1 : auto srcArrayNames = poRG->GetMDArrayNames(
1750 1 : psOptions ? psOptions->aosArrayOptions.List() : nullptr);
1751 5 : for (const auto &srcArrayName : srcArrayNames)
1752 : {
1753 5 : auto tmpArray = poRG->OpenMDArray(srcArrayName);
1754 5 : if (tmpArray)
1755 : {
1756 5 : const auto &dims(tmpArray->GetDimensions());
1757 13 : if (!(dims.size() == 1 && dims[0]->GetIndexingVariable() &&
1758 8 : dims[0]->GetIndexingVariable()->GetName() ==
1759 : srcArrayName))
1760 : {
1761 2 : if (srcArray)
1762 : {
1763 1 : CPLError(CE_Failure, CPLE_AppDefined,
1764 : "Several arrays exist. Select one for "
1765 : "output to non-multidimensional driver");
1766 1 : return nullptr;
1767 : }
1768 1 : srcArray = std::move(tmpArray);
1769 : }
1770 : }
1771 : }
1772 : }
1773 3 : if (!srcArray)
1774 : {
1775 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find source array");
1776 0 : return nullptr;
1777 : }
1778 3 : size_t iXDim = static_cast<size_t>(-1);
1779 3 : size_t iYDim = static_cast<size_t>(-1);
1780 3 : const auto &dims(srcArray->GetDimensions());
1781 8 : for (size_t i = 0; i < dims.size(); ++i)
1782 : {
1783 5 : if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
1784 : {
1785 2 : iXDim = i;
1786 : }
1787 3 : else if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
1788 : {
1789 3 : iYDim = i;
1790 : }
1791 : }
1792 3 : if (dims.size() == 1)
1793 : {
1794 1 : iXDim = 0;
1795 : }
1796 2 : else if (dims.size() >= 2 && (iXDim == static_cast<size_t>(-1) ||
1797 : iYDim == static_cast<size_t>(-1)))
1798 : {
1799 0 : iXDim = dims.size() - 1;
1800 0 : iYDim = dims.size() - 2;
1801 : }
1802 : std::unique_ptr<GDALDataset> poTmpSrcDS(
1803 6 : srcArray->AsClassicDataset(iXDim, iYDim));
1804 3 : if (!poTmpSrcDS)
1805 0 : return nullptr;
1806 6 : return GDALDataset::ToHandle(poDriver->CreateCopy(
1807 : pszDest, poTmpSrcDS.get(), false,
1808 3 : psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
1809 : : nullptr,
1810 : psOptions ? psOptions->pfnProgress : nullptr,
1811 3 : psOptions ? psOptions->pProgressData : nullptr));
1812 : }
1813 :
1814 : /************************************************************************/
1815 : /* GDALMultiDimTranslate() */
1816 : /************************************************************************/
1817 :
1818 : /* clang-format off */
1819 : /**
1820 : * Converts raster data between different formats.
1821 : *
1822 : * This is the equivalent of the
1823 : * <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
1824 : *
1825 : * GDALMultiDimTranslateOptions* must be allocated and freed with
1826 : * GDALMultiDimTranslateOptionsNew() and GDALMultiDimTranslateOptionsFree()
1827 : * respectively. pszDest and hDstDS cannot be used at the same time.
1828 : *
1829 : * @param pszDest the destination dataset path or NULL.
1830 : * @param hDstDS the destination dataset or NULL.
1831 : * @param nSrcCount the number of input datasets.
1832 : * @param pahSrcDS the list of input datasets.
1833 : * @param psOptions the options struct returned by
1834 : * GDALMultiDimTranslateOptionsNew() or NULL.
1835 : * @param pbUsageError pointer to a integer output variable to store if any
1836 : * usage error has occurred or NULL.
1837 : * @return the output dataset (new dataset that must be closed using
1838 : * GDALClose(), or hDstDS is not NULL) or NULL in case of error.
1839 : *
1840 : * @since GDAL 3.1
1841 : */
1842 : /* clang-format on */
1843 :
1844 : GDALDatasetH
1845 123 : GDALMultiDimTranslate(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
1846 : GDALDatasetH *pahSrcDS,
1847 : const GDALMultiDimTranslateOptions *psOptions,
1848 : int *pbUsageError)
1849 : {
1850 123 : if (pbUsageError)
1851 111 : *pbUsageError = false;
1852 123 : if (nSrcCount != 1 || pahSrcDS[0] == nullptr)
1853 : {
1854 0 : CPLError(CE_Failure, CPLE_NotSupported,
1855 : "Only one source dataset is supported");
1856 0 : if (pbUsageError)
1857 0 : *pbUsageError = true;
1858 0 : return nullptr;
1859 : }
1860 :
1861 123 : if (hDstDS)
1862 : {
1863 0 : CPLError(CE_Failure, CPLE_NotSupported,
1864 : "Update of existing file not supported yet");
1865 0 : GDALClose(hDstDS);
1866 0 : return nullptr;
1867 : }
1868 :
1869 369 : CPLString osFormat(psOptions ? psOptions->osFormat : "");
1870 123 : if (pszDest == nullptr /* && hDstDS == nullptr */)
1871 : {
1872 0 : CPLError(CE_Failure, CPLE_NotSupported,
1873 : "Both pszDest and hDstDS are NULL.");
1874 0 : if (pbUsageError)
1875 0 : *pbUsageError = true;
1876 0 : return nullptr;
1877 : }
1878 :
1879 123 : GDALDriver *poDriver = nullptr;
1880 :
1881 : #ifdef this_is_dead_code_for_now
1882 : const bool bCloseOutDSOnError = hDstDS == nullptr;
1883 : if (pszDest == nullptr)
1884 : pszDest = GDALGetDescription(hDstDS);
1885 : #endif
1886 :
1887 123 : if (psOptions && psOptions->bOverwrite && !EQUAL(pszDest, ""))
1888 : {
1889 1 : VSIRmdirRecursive(pszDest);
1890 : }
1891 122 : else if (psOptions && psOptions->bNoOverwrite && !EQUAL(pszDest, ""))
1892 : {
1893 : VSIStatBufL sStat;
1894 10 : if (VSIStatL(pszDest, &sStat) == 0)
1895 : {
1896 0 : CPLError(CE_Failure, CPLE_AppDefined,
1897 : "File '%s' already exists. Specify the --overwrite "
1898 : "option to overwrite it.",
1899 : pszDest);
1900 0 : return nullptr;
1901 : }
1902 10 : else if (std::unique_ptr<GDALDataset>(GDALDataset::Open(pszDest)))
1903 : {
1904 0 : CPLError(CE_Failure, CPLE_AppDefined,
1905 : "Dataset '%s' already exists. Specify the --overwrite "
1906 : "option to overwrite it.",
1907 : pszDest);
1908 0 : return nullptr;
1909 : }
1910 : }
1911 :
1912 : #ifdef this_is_dead_code_for_now
1913 : if (hDstDS == nullptr)
1914 : #endif
1915 : {
1916 123 : if (osFormat.empty())
1917 : {
1918 110 : if (EQUAL(CPLGetExtensionSafe(pszDest).c_str(), "nc"))
1919 4 : osFormat = "netCDF";
1920 : else
1921 106 : osFormat = GetOutputDriverForRaster(pszDest);
1922 110 : if (osFormat.empty())
1923 : {
1924 1 : CPLError(CE_Failure, CPLE_AppDefined,
1925 : "Cannot determine output driver for dataset name '%s'",
1926 : pszDest);
1927 1 : return nullptr;
1928 : }
1929 : }
1930 122 : poDriver = GDALDriver::FromHandle(GDALGetDriverByName(osFormat));
1931 : CSLConstList papszDriverMD =
1932 122 : poDriver ? poDriver->GetMetadata() : nullptr;
1933 244 : if (poDriver == nullptr ||
1934 122 : (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_RASTER,
1935 0 : "FALSE")) &&
1936 0 : !CPLTestBool(CSLFetchNameValueDef(
1937 244 : papszDriverMD, GDAL_DCAP_MULTIDIM_RASTER, "FALSE"))) ||
1938 122 : (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_CREATE,
1939 0 : "FALSE")) &&
1940 0 : !CPLTestBool(CSLFetchNameValueDef(
1941 0 : papszDriverMD, GDAL_DCAP_CREATECOPY, "FALSE")) &&
1942 0 : !CPLTestBool(CSLFetchNameValueDef(
1943 0 : papszDriverMD, GDAL_DCAP_CREATE_MULTIDIMENSIONAL, "FALSE")) &&
1944 0 : !CPLTestBool(CSLFetchNameValueDef(
1945 : papszDriverMD, GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL,
1946 : "FALSE"))))
1947 : {
1948 0 : CPLError(CE_Failure, CPLE_NotSupported,
1949 : "Output driver `%s' not recognised or does not support "
1950 : "output file creation.",
1951 : osFormat.c_str());
1952 0 : return nullptr;
1953 : }
1954 : }
1955 :
1956 122 : GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
1957 :
1958 122 : std::unique_ptr<GDALDataset> poTmpDS;
1959 122 : GDALDataset *poTmpSrcDS = poSrcDS;
1960 244 : if (psOptions &&
1961 122 : (!psOptions->aosArraySpec.empty() || !psOptions->aosGroup.empty() ||
1962 22 : !psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty() ||
1963 17 : !psOptions->aosArrayOptions.empty()))
1964 : {
1965 : auto poVRTDS =
1966 106 : VRTDataset::CreateVRTMultiDimensional("", nullptr, nullptr);
1967 106 : CPLAssert(poVRTDS);
1968 :
1969 106 : auto poDstRootGroup = poVRTDS->GetRootVRTGroup();
1970 106 : CPLAssert(poDstRootGroup);
1971 :
1972 106 : if (!TranslateInternal(poDstRootGroup, poSrcDS, psOptions))
1973 : {
1974 : #ifdef this_is_dead_code_for_now
1975 : if (bCloseOutDSOnError)
1976 : #endif
1977 : {
1978 24 : GDALClose(hDstDS);
1979 24 : hDstDS = nullptr;
1980 : }
1981 24 : return nullptr;
1982 : }
1983 :
1984 82 : poTmpDS = std::move(poVRTDS);
1985 82 : poTmpSrcDS = poTmpDS.get();
1986 : }
1987 :
1988 98 : auto poRG(poTmpSrcDS->GetRootGroup());
1989 193 : if (poRG &&
1990 95 : poDriver->GetMetadataItem(GDAL_DCAP_CREATE_MULTIDIMENSIONAL) ==
1991 193 : nullptr &&
1992 4 : poDriver->GetMetadataItem(GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL) ==
1993 : nullptr)
1994 : {
1995 : #ifdef this_is_dead_code_for_now
1996 : if (hDstDS)
1997 : {
1998 : CPLError(CE_Failure, CPLE_NotSupported,
1999 : "Appending to non-multidimensional driver not supported.");
2000 : GDALClose(hDstDS);
2001 : hDstDS = nullptr;
2002 : return nullptr;
2003 : }
2004 : #endif
2005 : hDstDS =
2006 4 : CopyToNonMultiDimensionalDriver(poDriver, pszDest, poRG, psOptions);
2007 : }
2008 : else
2009 : {
2010 188 : hDstDS = GDALDataset::ToHandle(poDriver->CreateCopy(
2011 : pszDest, poTmpSrcDS, false,
2012 94 : psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
2013 : : nullptr,
2014 : psOptions ? psOptions->pfnProgress : nullptr,
2015 : psOptions ? psOptions->pProgressData : nullptr));
2016 : }
2017 :
2018 98 : return hDstDS;
2019 : }
2020 :
2021 : /************************************************************************/
2022 : /* GDALMultiDimTranslateOptionsNew() */
2023 : /************************************************************************/
2024 :
2025 : /**
2026 : * Allocates a GDALMultiDimTranslateOptions struct.
2027 : *
2028 : * @param papszArgv NULL terminated list of options (potentially including
2029 : * filename and open options too), or NULL. The accepted options are the ones of
2030 : * the <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
2031 : * @param psOptionsForBinary should be nullptr, unless called from
2032 : * gdalmdimtranslate_bin.cpp
2033 : * @return pointer to the allocated GDALMultiDimTranslateOptions struct. Must be
2034 : * freed with GDALMultiDimTranslateOptionsFree().
2035 : *
2036 : * @since GDAL 3.1
2037 : */
2038 :
2039 124 : GDALMultiDimTranslateOptions *GDALMultiDimTranslateOptionsNew(
2040 : char **papszArgv, GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
2041 : {
2042 :
2043 248 : auto psOptions = std::make_unique<GDALMultiDimTranslateOptions>();
2044 :
2045 : /* -------------------------------------------------------------------- */
2046 : /* Parse arguments. */
2047 : /* -------------------------------------------------------------------- */
2048 : try
2049 : {
2050 : auto argParser = GDALMultiDimTranslateAppOptionsGetParser(
2051 124 : psOptions.get(), psOptionsForBinary);
2052 :
2053 124 : argParser->parse_args_without_binary_name(papszArgv);
2054 :
2055 : // Check for invalid options:
2056 : // -scaleaxes is not compatible with -array = "view"
2057 : // -subset is not compatible with -array = "view"
2058 124 : if (std::find(psOptions->aosArraySpec.cbegin(),
2059 124 : psOptions->aosArraySpec.cend(),
2060 372 : "view") != psOptions->aosArraySpec.cend())
2061 : {
2062 0 : if (!psOptions->aosScaleFactor.empty())
2063 : {
2064 0 : CPLError(CE_Failure, CPLE_NotSupported,
2065 : "The -scaleaxes option is not compatible with the "
2066 : "-array \"view\" option.");
2067 0 : return nullptr;
2068 : }
2069 :
2070 0 : if (!psOptions->aosSubset.empty())
2071 : {
2072 0 : CPLError(CE_Failure, CPLE_NotSupported,
2073 : "The -subset option is not compatible with the -array "
2074 : "\"view\" option.");
2075 0 : return nullptr;
2076 : }
2077 : }
2078 : }
2079 0 : catch (const std::exception &error)
2080 : {
2081 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
2082 0 : return nullptr;
2083 : }
2084 :
2085 124 : if (psOptionsForBinary)
2086 : {
2087 : // Note: bUpdate is apparently never changed by the command line options
2088 3 : psOptionsForBinary->bUpdate = psOptions->bUpdate;
2089 3 : if (!psOptions->osFormat.empty())
2090 0 : psOptionsForBinary->osFormat = psOptions->osFormat;
2091 : }
2092 :
2093 124 : return psOptions.release();
2094 : }
2095 :
2096 : /************************************************************************/
2097 : /* GDALMultiDimTranslateOptionsFree() */
2098 : /************************************************************************/
2099 :
2100 : /**
2101 : * Frees the GDALMultiDimTranslateOptions struct.
2102 : *
2103 : * @param psOptions the options struct for GDALMultiDimTranslate().
2104 : *
2105 : * @since GDAL 3.1
2106 : */
2107 :
2108 123 : void GDALMultiDimTranslateOptionsFree(GDALMultiDimTranslateOptions *psOptions)
2109 : {
2110 123 : delete psOptions;
2111 123 : }
2112 :
2113 : /************************************************************************/
2114 : /* GDALMultiDimTranslateOptionsSetProgress() */
2115 : /************************************************************************/
2116 :
2117 : /**
2118 : * Set a progress function.
2119 : *
2120 : * @param psOptions the options struct for GDALMultiDimTranslate().
2121 : * @param pfnProgress the progress callback.
2122 : * @param pProgressData the user data for the progress callback.
2123 : *
2124 : * @since GDAL 3.1
2125 : */
2126 :
2127 15 : void GDALMultiDimTranslateOptionsSetProgress(
2128 : GDALMultiDimTranslateOptions *psOptions, GDALProgressFunc pfnProgress,
2129 : void *pProgressData)
2130 : {
2131 15 : psOptions->pfnProgress = pfnProgress;
2132 15 : psOptions->pProgressData = pProgressData;
2133 15 : }
|