Coverage Report

Created: 2026-02-14 06:52

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