• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.95
/apps/gdalmdimtranslate_lib.cpp
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(
117✔
51
    GDALMultiDimTranslateOptions *psOptions,
52
    GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
53
{
54
    auto argParser = std::make_unique<GDALArgumentParser>(
55
        "gdalmdimtranslate", /* bForBinary=*/psOptionsForBinary != nullptr);
117✔
56

57
    argParser->add_description(
117✔
58
        _("Converts multidimensional data between different formats, and "
59
          "performs subsetting."));
117✔
60

61
    argParser->add_epilog(
117✔
62
        _("For more details, consult "
63
          "https://gdal.org/programs/gdalmdimtranslate.html"));
117✔
64

65
    if (psOptionsForBinary)
117✔
66
    {
67
        argParser->add_input_format_argument(
68
            &psOptionsForBinary->aosAllowInputDrivers);
3✔
69
    }
70

71
    argParser->add_output_format_argument(psOptions->osFormat);
117✔
72

73
    argParser->add_creation_options_argument(psOptions->aosCreateOptions);
117✔
74

75
    auto &group = argParser->add_mutually_exclusive_group();
117✔
76
    group.add_argument("-array")
117✔
77
        .metavar("<array_spec>")
234✔
78
        .append()
117✔
79
        .store_into(psOptions->aosArraySpec)
117✔
80
        .help(_(
81
            "Select a single array instead of converting the whole dataset."));
117✔
82

83
    argParser->add_argument("-arrayoption")
117✔
84
        .metavar("<NAME>=<VALUE>")
234✔
85
        .append()
117✔
86
        .action([psOptions](const std::string &s)
1✔
87
                { psOptions->aosArrayOptions.AddString(s.c_str()); })
118✔
88
        .help(_("Option passed to GDALGroup::GetMDArrayNames() to filter "
89
                "arrays."));
117✔
90

91
    group.add_argument("-group")
117✔
92
        .metavar("<group_spec>")
234✔
93
        .append()
117✔
94
        .store_into(psOptions->aosGroup)
117✔
95
        .help(_(
96
            "Select a single group instead of converting the whole dataset."));
117✔
97

98
    // Note: this is mutually exclusive with "view" option in -array
99
    argParser->add_argument("-subset")
117✔
100
        .metavar("<subset_spec>")
234✔
101
        .append()
117✔
102
        .store_into(psOptions->aosSubset)
117✔
103
        .help(_("Select a subset of the data."));
117✔
104

105
    // Note: this is mutually exclusive with "view" option in -array
106
    argParser->add_argument("-scaleaxes")
117✔
107
        .metavar("<scaleaxes_spec>")
234✔
108
        .action(
109
            [psOptions](const std::string &s)
4✔
110
            {
111
                CPLStringList aosScaleFactors(
112
                    CSLTokenizeString2(s.c_str(), ",", 0));
4✔
113
                for (int j = 0; j < aosScaleFactors.size(); j++)
4✔
114
                {
115
                    psOptions->aosScaleFactor.push_back(aosScaleFactors[j]);
2✔
116
                }
117
            })
119✔
118
        .help(
119
            _("Applies a integral scale factor to one or several dimensions."));
117✔
120

121
    argParser->add_argument("-strict")
117✔
122
        .flag()
117✔
123
        .store_into(psOptions->bStrict)
117✔
124
        .help(_("Turn warnings into failures."));
117✔
125

126
    // Undocumented option used by gdal mdim convert
127
    argParser->add_argument("--overwrite")
117✔
128
        .store_into(psOptions->bOverwrite)
117✔
129
        .hidden();
117✔
130

131
    // Undocumented option used by gdal mdim convert
132
    argParser->add_argument("--no-overwrite")
117✔
133
        .store_into(psOptions->bNoOverwrite)
117✔
134
        .hidden();
117✔
135

136
    if (psOptionsForBinary)
117✔
137
    {
138
        argParser->add_open_options_argument(
139
            psOptionsForBinary->aosOpenOptions);
3✔
140

141
        argParser->add_argument("src_dataset")
3✔
142
            .metavar("<src_dataset>")
6✔
143
            .store_into(psOptionsForBinary->osSource)
3✔
144
            .help(_("The source dataset name."));
3✔
145

146
        argParser->add_argument("dst_dataset")
3✔
147
            .metavar("<dst_dataset>")
6✔
148
            .store_into(psOptionsForBinary->osDest)
3✔
149
            .help(_("The destination file name."));
3✔
150

151
        argParser->add_quiet_argument(&psOptionsForBinary->bQuiet);
3✔
152
    }
153

154
    return argParser;
117✔
155
}
156

157
/************************************************************************/
158
/*            GDALMultiDimTranslateAppGetParserUsage()                  */
159
/************************************************************************/
160

161
std::string GDALMultiDimTranslateAppGetParserUsage()
×
162
{
163
    try
164
    {
165
        GDALMultiDimTranslateOptions sOptions;
×
166
        GDALMultiDimTranslateOptionsForBinary sOptionsForBinary;
×
167
        auto argParser = GDALMultiDimTranslateAppOptionsGetParser(
168
            &sOptions, &sOptionsForBinary);
×
169
        return argParser->usage();
×
170
    }
171
    catch (const std::exception &err)
×
172
    {
173
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
×
174
                 err.what());
×
175
        return std::string();
×
176
    }
177
}
178

179
/************************************************************************/
180
/*                        FindMinMaxIdxNumeric()                        */
181
/************************************************************************/
182

183
static void FindMinMaxIdxNumeric(const GDALMDArray *var, double *pdfTmp,
40✔
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
    if (nCount >= 2)
40✔
192
    {
193
        bool bReversed = false;
40✔
194
        if (pdfTmp[0] > pdfTmp[nCount - 1])
40✔
195
        {
196
            bReversed = true;
19✔
197
            std::reverse(pdfTmp, pdfTmp + nCount);
19✔
198
        }
199
        if (nStartIdx > 0 && bLastWasReversed != bReversed)
40✔
200
        {
201
            CPLError(CE_Failure, CPLE_AppDefined,
×
202
                     "Variable %s is non monotonic", var->GetName().c_str());
×
203
            bEmpty = true;
×
204
            return;
×
205
        }
206
        bLastWasReversed = bReversed;
40✔
207

208
        if (!bFoundMinIdx)
40✔
209
        {
210
            if (bReversed && nStartIdx == 0 && dfMin > pdfTmp[nCount - 1])
40✔
211
            {
212
                bEmpty = true;
2✔
213
                return;
2✔
214
            }
215
            else if (!bReversed && dfMin < pdfTmp[0] - EPS)
38✔
216
            {
217
                if (bSlice)
6✔
218
                {
219
                    bEmpty = true;
1✔
220
                    return;
1✔
221
                }
222
                bFoundMinIdx = true;
5✔
223
                nMinIdx = nStartIdx;
5✔
224
            }
225
            else if (dfMin >= pdfTmp[0] - EPS &&
32✔
226
                     dfMin <= pdfTmp[nCount - 1] + EPS)
28✔
227
            {
228
                for (size_t i = 0; i < nCount; i++)
130✔
229
                {
230
                    if (dfMin <= pdfTmp[i] + EPS)
130✔
231
                    {
232
                        bFoundMinIdx = true;
26✔
233
                        nMinIdx = nStartIdx + (bReversed ? nCount - 1 - i : i);
26✔
234
                        break;
26✔
235
                    }
236
                }
237
                CPLAssert(bFoundMinIdx);
26✔
238
            }
239
        }
240
        if (!bFoundMaxIdx)
37✔
241
        {
242
            if (bReversed && nStartIdx == 0 && dfMax > pdfTmp[nCount - 1])
37✔
243
            {
244
                if (bSlice)
2✔
245
                {
246
                    bEmpty = true;
×
247
                    return;
×
248
                }
249
                bFoundMaxIdx = true;
2✔
250
                nMaxIdx = 0;
2✔
251
            }
252
            else if (!bReversed && dfMax < pdfTmp[0] - EPS)
35✔
253
            {
254
                if (nStartIdx == 0)
1✔
255
                {
256
                    bEmpty = true;
1✔
257
                    return;
1✔
258
                }
259
                bFoundMaxIdx = true;
×
260
                nMaxIdx = nStartIdx - 1;
×
261
            }
262
            else if (dfMax > pdfTmp[0] - EPS &&
34✔
263
                     dfMax <= pdfTmp[nCount - 1] + EPS)
32✔
264
            {
265
                for (size_t i = 1; i < nCount; i++)
156✔
266
                {
267
                    if (dfMax <= pdfTmp[i] - EPS)
148✔
268
                    {
269
                        bFoundMaxIdx = true;
18✔
270
                        nMaxIdx = nStartIdx +
18✔
271
                                  (bReversed ? nCount - 1 - (i - 1) : i - 1);
18✔
272
                        break;
18✔
273
                    }
274
                }
275
                if (!bFoundMaxIdx)
26✔
276
                {
277
                    bFoundMaxIdx = true;
8✔
278
                    nMaxIdx = nStartIdx + (bReversed ? 0 : nCount - 1);
8✔
279
                }
280
            }
281
        }
282
    }
283
    else
284
    {
285
        if (!bFoundMinIdx)
×
286
        {
287
            if (dfMin <= pdfTmp[0] + EPS)
×
288
            {
289
                bFoundMinIdx = true;
×
290
                nMinIdx = nStartIdx;
×
291
            }
292
            else if (bLastWasReversed && nStartIdx > 0)
×
293
            {
294
                bFoundMinIdx = true;
×
295
                nMinIdx = nStartIdx - 1;
×
296
            }
297
        }
298
        if (!bFoundMaxIdx)
×
299
        {
300
            if (dfMax >= pdfTmp[0] - EPS)
×
301
            {
302
                bFoundMaxIdx = true;
×
303
                nMaxIdx = nStartIdx;
×
304
            }
305
            else if (!bLastWasReversed && nStartIdx > 0)
×
306
            {
307
                bFoundMaxIdx = true;
×
308
                nMaxIdx = nStartIdx - 1;
×
309
            }
310
        }
311
    }
312
}
313

314
/************************************************************************/
315
/*                        FindMinMaxIdxString()                         */
316
/************************************************************************/
317

318
static void FindMinMaxIdxString(const GDALMDArray *var, const char **ppszTmp,
40✔
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
    bool bFoundNull = false;
40✔
327
    for (size_t i = 0; i < nCount; i++)
200✔
328
    {
329
        if (ppszTmp[i] == nullptr)
160✔
330
        {
331
            bFoundNull = true;
×
332
            break;
×
333
        }
334
    }
335
    if (bFoundNull)
40✔
336
    {
337
        CPLError(CE_Failure, CPLE_AppDefined,
×
338
                 "Variable %s contains null strings", var->GetName().c_str());
×
339
        bEmpty = true;
×
340
        return;
×
341
    }
342
    if (nCount >= 2)
40✔
343
    {
344
        bool bReversed = false;
40✔
345
        if (std::string(ppszTmp[0]) > std::string(ppszTmp[nCount - 1]))
40✔
346
        {
347
            bReversed = true;
19✔
348
            std::reverse(ppszTmp, ppszTmp + nCount);
19✔
349
        }
350
        if (nStartIdx > 0 && bLastWasReversed != bReversed)
40✔
351
        {
352
            CPLError(CE_Failure, CPLE_AppDefined,
×
353
                     "Variable %s is non monotonic", var->GetName().c_str());
×
354
            bEmpty = true;
×
355
            return;
×
356
        }
357
        bLastWasReversed = bReversed;
40✔
358

359
        if (!bFoundMinIdx)
40✔
360
        {
361
            if (bReversed && nStartIdx == 0 &&
59✔
362
                osMin > std::string(ppszTmp[nCount - 1]))
59✔
363
            {
364
                bEmpty = true;
2✔
365
                return;
2✔
366
            }
367
            else if (!bReversed && osMin < std::string(ppszTmp[0]))
38✔
368
            {
369
                if (bSlice)
5✔
370
                {
371
                    bEmpty = true;
1✔
372
                    return;
1✔
373
                }
374
                bFoundMinIdx = true;
4✔
375
                nMinIdx = nStartIdx;
4✔
376
            }
377
            else if (osMin >= std::string(ppszTmp[0]) &&
94✔
378
                     osMin <= std::string(ppszTmp[nCount - 1]))
61✔
379
            {
380
                for (size_t i = 0; i < nCount; i++)
68✔
381
                {
382
                    if (osMin <= std::string(ppszTmp[i]))
68✔
383
                    {
384
                        bFoundMinIdx = true;
26✔
385
                        nMinIdx = nStartIdx + (bReversed ? nCount - 1 - i : i);
26✔
386
                        break;
26✔
387
                    }
388
                }
389
                CPLAssert(bFoundMinIdx);
26✔
390
            }
391
        }
392
        if (!bFoundMaxIdx)
37✔
393
        {
394
            if (bReversed && nStartIdx == 0 &&
54✔
395
                osMax > std::string(ppszTmp[nCount - 1]))
54✔
396
            {
397
                if (bSlice)
3✔
398
                {
399
                    bEmpty = true;
×
400
                    return;
×
401
                }
402
                bFoundMaxIdx = true;
3✔
403
                nMaxIdx = 0;
3✔
404
            }
405
            else if (!bReversed && osMax < std::string(ppszTmp[0]))
34✔
406
            {
407
                if (nStartIdx == 0)
1✔
408
                {
409
                    bEmpty = true;
1✔
410
                    return;
1✔
411
                }
412
                bFoundMaxIdx = true;
×
413
                nMaxIdx = nStartIdx - 1;
×
414
            }
415
            else if (osMax == std::string(ppszTmp[0]))
33✔
416
            {
417
                bFoundMaxIdx = true;
6✔
418
                nMaxIdx = nStartIdx + (bReversed ? nCount - 1 : 0);
6✔
419
            }
420
            else if (osMax > std::string(ppszTmp[0]) &&
79✔
421
                     osMax <= std::string(ppszTmp[nCount - 1]))
52✔
422
            {
423
                for (size_t i = 1; i < nCount; i++)
42✔
424
                {
425
                    if (osMax <= std::string(ppszTmp[i]))
42✔
426
                    {
427
                        bFoundMaxIdx = true;
20✔
428
                        if (osMax == std::string(ppszTmp[i]))
20✔
429
                            nMaxIdx =
16✔
430
                                nStartIdx + (bReversed ? nCount - 1 - i : i);
16✔
431
                        else
432
                            nMaxIdx =
4✔
433
                                nStartIdx +
4✔
434
                                (bReversed ? nCount - 1 - (i - 1) : i - 1);
4✔
435
                        break;
20✔
436
                    }
437
                }
438
                CPLAssert(bFoundMaxIdx);
20✔
439
            }
440
        }
441
    }
442
    else
443
    {
444
        if (!bFoundMinIdx)
×
445
        {
446
            if (osMin <= std::string(ppszTmp[0]))
×
447
            {
448
                bFoundMinIdx = true;
×
449
                nMinIdx = nStartIdx;
×
450
            }
451
            else if (bLastWasReversed && nStartIdx > 0)
×
452
            {
453
                bFoundMinIdx = true;
×
454
                nMinIdx = nStartIdx - 1;
×
455
            }
456
        }
457
        if (!bFoundMaxIdx)
×
458
        {
459
            if (osMax >= std::string(ppszTmp[0]))
×
460
            {
461
                bFoundMaxIdx = true;
×
462
                nMaxIdx = nStartIdx;
×
463
            }
464
            else if (!bLastWasReversed && nStartIdx > 0)
×
465
            {
466
                bFoundMaxIdx = true;
×
467
                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
GetDimensionDesc(DimensionRemapper &oDimRemapper,
207✔
493
                 const GDALMultiDimTranslateOptions *psOptions,
494
                 const std::shared_ptr<GDALDimension> &poDim)
495
{
496
    std::string osKey(poDim->GetFullName());
414✔
497
    osKey +=
498
        CPLSPrintf("_" CPL_FRMT_GUIB, static_cast<GUIntBig>(poDim->GetSize()));
207✔
499
    auto oIter = oDimRemapper.oMap.find(osKey);
207✔
500
    if (oIter != oDimRemapper.oMap.end() &&
317✔
501
        oIter->second.nOriSize == poDim->GetSize())
110✔
502
    {
503
        return &(oIter->second);
110✔
504
    }
505
    DimensionDesc desc;
97✔
506
    desc.nSize = poDim->GetSize();
97✔
507
    desc.nOriSize = desc.nSize;
97✔
508

509
    CPLString osRadix(poDim->GetName());
194✔
510
    osRadix += '(';
97✔
511
    for (const auto &subset : psOptions->aosSubset)
107✔
512
    {
513
        if (STARTS_WITH(subset.c_str(), osRadix.c_str()))
93✔
514
        {
515
            auto var = poDim->GetIndexingVariable();
83✔
516
            if (!var || var->GetDimensionCount() != 1 ||
166✔
517
                var->GetDimensions()[0]->GetSize() != poDim->GetSize())
83✔
518
            {
519
                CPLError(CE_Failure, CPLE_AppDefined,
×
520
                         "Dimension %s has a subset specification, but lacks "
521
                         "a single dimension indexing variable",
522
                         poDim->GetName().c_str());
×
523
                return nullptr;
×
524
            }
525
            if (subset.back() != ')')
83✔
526
            {
527
                CPLError(CE_Failure, CPLE_AppDefined,
2✔
528
                         "Missing ')' in subset specification.");
529
                return nullptr;
2✔
530
            }
531
            CPLStringList aosTokens(CSLTokenizeString2(
532
                subset
533
                    .substr(osRadix.size(), subset.size() - 1 - osRadix.size())
81✔
534
                    .c_str(),
535
                ",", CSLT_HONOURSTRINGS));
81✔
536
            if (aosTokens.size() == 1)
81✔
537
            {
538
                desc.bSlice = true;
26✔
539
            }
540
            if (aosTokens.size() != 1 && aosTokens.size() != 2)
81✔
541
            {
542
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
543
                         "Invalid number of valus in subset specification.");
544
                return nullptr;
1✔
545
            }
546

547
            const bool bIsNumeric =
548
                var->GetDataType().GetClass() == GEDTC_NUMERIC;
80✔
549
            const GDALExtendedDataType dt(
550
                bIsNumeric ? GDALExtendedDataType::Create(GDT_Float64)
551
                           : GDALExtendedDataType::CreateString());
80✔
552

553
            double dfMin = 0;
80✔
554
            double dfMax = 0;
80✔
555
            std::string osMin;
80✔
556
            std::string osMax;
80✔
557
            if (bIsNumeric)
80✔
558
            {
559
                if (CPLGetValueType(aosTokens[0]) == CPL_VALUE_STRING ||
80✔
560
                    (aosTokens.size() == 2 &&
40✔
561
                     CPLGetValueType(aosTokens[1]) == CPL_VALUE_STRING))
28✔
562
                {
563
                    CPLError(CE_Failure, CPLE_AppDefined,
×
564
                             "Non numeric bound in subset specification.");
565
                    return nullptr;
×
566
                }
567
                dfMin = CPLAtof(aosTokens[0]);
40✔
568
                dfMax = dfMin;
40✔
569
                if (aosTokens.size() == 2)
40✔
570
                    dfMax = CPLAtof(aosTokens[1]);
28✔
571
                if (dfMin > dfMax)
40✔
572
                    std::swap(dfMin, dfMax);
×
573
            }
574
            else
575
            {
576
                osMin = aosTokens[0];
40✔
577
                osMax = osMin;
40✔
578
                if (aosTokens.size() == 2)
40✔
579
                    osMax = aosTokens[1];
26✔
580
                if (osMin > osMax)
40✔
581
                    std::swap(osMin, osMax);
×
582
            }
583

584
            const size_t nDTSize(dt.GetSize());
80✔
585
            const size_t nMaxChunkSize = static_cast<size_t>(std::min(
586
                static_cast<GUInt64>(10 * 1000 * 1000), poDim->GetSize()));
80✔
587
            std::vector<GByte> abyTmp(nDTSize * nMaxChunkSize);
80✔
588
            double *pdfTmp = reinterpret_cast<double *>(&abyTmp[0]);
80✔
589
            const char **ppszTmp = reinterpret_cast<const char **>(&abyTmp[0]);
80✔
590
            GUInt64 nStartIdx = 0;
80✔
591
            const double EPS = std::max(std::max(1e-10, fabs(dfMin) / 1e10),
160✔
592
                                        fabs(dfMax) / 1e10);
80✔
593
            bool bFoundMinIdx = false;
80✔
594
            bool bFoundMaxIdx = false;
80✔
595
            GUInt64 nMinIdx = 0;
80✔
596
            GUInt64 nMaxIdx = 0;
80✔
597
            bool bLastWasReversed = false;
80✔
598
            bool bEmpty = false;
80✔
599
            while (true)
600
            {
601
                const size_t nCount = static_cast<size_t>(
602
                    std::min(static_cast<GUInt64>(nMaxChunkSize),
200✔
603
                             poDim->GetSize() - nStartIdx));
100✔
604
                if (nCount == 0)
100✔
605
                    break;
20✔
606
                const GUInt64 anStartId[] = {nStartIdx};
80✔
607
                const size_t anCount[] = {nCount};
80✔
608
                if (!var->Read(anStartId, anCount, nullptr, nullptr, dt,
160✔
609
                               &abyTmp[0], nullptr, 0))
80✔
610
                {
611
                    return nullptr;
×
612
                }
613
                if (bIsNumeric)
80✔
614
                {
615
                    FindMinMaxIdxNumeric(
40✔
616
                        var.get(), pdfTmp, nCount, nStartIdx, dfMin, dfMax,
40✔
617
                        desc.bSlice, bFoundMinIdx, nMinIdx, bFoundMaxIdx,
40✔
618
                        nMaxIdx, bLastWasReversed, bEmpty, EPS);
619
                }
620
                else
621
                {
622
                    FindMinMaxIdxString(var.get(), ppszTmp, nCount, nStartIdx,
40✔
623
                                        osMin, osMax, desc.bSlice, bFoundMinIdx,
40✔
624
                                        nMinIdx, bFoundMaxIdx, nMaxIdx,
625
                                        bLastWasReversed, bEmpty);
626
                }
627
                if (dt.NeedsFreeDynamicMemory())
80✔
628
                {
629
                    for (size_t i = 0; i < nCount; i++)
200✔
630
                    {
631
                        dt.FreeDynamicMemory(&abyTmp[i * nDTSize]);
160✔
632
                    }
633
                }
634
                if (bEmpty || (bFoundMinIdx && bFoundMaxIdx) ||
80✔
635
                    nCount < nMaxChunkSize)
636
                {
637
                    break;
638
                }
639
                nStartIdx += nMaxChunkSize;
20✔
640
            }
20✔
641

642
            // cppcheck-suppress knownConditionTrueFalse
643
            if (!bLastWasReversed)
80✔
644
            {
645
                if (!bFoundMinIdx)
42✔
646
                    bEmpty = true;
6✔
647
                else if (!bFoundMaxIdx)
36✔
648
                    nMaxIdx = poDim->GetSize() - 1;
9✔
649
                else
650
                    bEmpty = nMaxIdx < nMinIdx;
27✔
651
            }
652
            else
653
            {
654
                if (!bFoundMaxIdx)
38✔
655
                    bEmpty = true;
8✔
656
                else if (!bFoundMinIdx)
30✔
657
                    nMinIdx = poDim->GetSize() - 1;
5✔
658
                else
659
                    bEmpty = nMinIdx < nMaxIdx;
25✔
660
            }
661
            if (bEmpty)
80✔
662
            {
663
                CPLError(CE_Failure, CPLE_AppDefined,
16✔
664
                         "Subset specification results in an empty set");
665
                return nullptr;
16✔
666
            }
667

668
            // cppcheck-suppress knownConditionTrueFalse
669
            if (!bLastWasReversed)
64✔
670
            {
671
                CPLAssert(nMaxIdx >= nMinIdx);
34✔
672
                desc.nStartIdx = nMinIdx;
34✔
673
                desc.nSize = nMaxIdx - nMinIdx + 1;
34✔
674
            }
675
            else
676
            {
677
                CPLAssert(nMaxIdx <= nMinIdx);
30✔
678
                desc.nStartIdx = nMaxIdx;
30✔
679
                desc.nSize = nMinIdx - nMaxIdx + 1;
30✔
680
            }
681

682
            break;
64✔
683
        }
684
    }
685

686
    for (const auto &scaleFactor : psOptions->aosScaleFactor)
82✔
687
    {
688
        if (STARTS_WITH(scaleFactor.c_str(), osRadix.c_str()))
6✔
689
        {
690
            if (scaleFactor.back() != ')')
2✔
691
            {
692
                CPLError(CE_Failure, CPLE_AppDefined,
×
693
                         "Missing ')' in scalefactor specification.");
694
                return nullptr;
×
695
            }
696
            std::string osScaleFactor(scaleFactor.substr(
697
                osRadix.size(), scaleFactor.size() - 1 - osRadix.size()));
2✔
698
            int nScaleFactor = atoi(osScaleFactor.c_str());
2✔
699
            if (CPLGetValueType(osScaleFactor.c_str()) != CPL_VALUE_INTEGER ||
2✔
700
                nScaleFactor <= 0)
701
            {
702
                CPLError(CE_Failure, CPLE_NotSupported,
×
703
                         "Only positive integer scale factor is supported");
704
                return nullptr;
×
705
            }
706
            desc.nSize /= nScaleFactor;
2✔
707
            if (desc.nSize == 0)
2✔
708
                desc.nSize = 1;
×
709
            desc.nStep *= nScaleFactor;
2✔
710
            break;
2✔
711
        }
712
    }
713

714
    oDimRemapper.oMap[osKey] = desc;
78✔
715
    return &oDimRemapper.oMap[osKey];
78✔
716
}
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,
147✔
725
                           std::string &dstName, int &band,
726
                           std::vector<int> &anTransposedAxis,
727
                           std::string &viewExpr,
728
                           GDALExtendedDataType &outputType, bool &bResampled)
729
{
730
    if (!STARTS_WITH(arraySpec.c_str(), "name=") &&
281✔
731
        !STARTS_WITH(arraySpec.c_str(), "band="))
134✔
732
    {
733
        srcName = arraySpec;
133✔
734
        dstName = arraySpec;
133✔
735
        auto pos = dstName.rfind('/');
133✔
736
        if (pos != std::string::npos)
133✔
737
            dstName = dstName.substr(pos + 1);
22✔
738
        return true;
133✔
739
    }
740

741
    std::vector<std::string> tokens;
28✔
742
    std::string curToken;
28✔
743
    bool bInArray = false;
14✔
744
    for (size_t i = 0; i < arraySpec.size(); ++i)
588✔
745
    {
746
        if (!bInArray && arraySpec[i] == ',')
574✔
747
        {
748
            tokens.emplace_back(std::move(curToken));
18✔
749
            curToken = std::string();
18✔
750
        }
751
        else
752
        {
753
            if (arraySpec[i] == '[')
556✔
754
            {
755
                bInArray = true;
6✔
756
            }
757
            else if (arraySpec[i] == ']')
550✔
758
            {
759
                bInArray = false;
6✔
760
            }
761
            curToken += arraySpec[i];
556✔
762
        }
763
    }
764
    if (!curToken.empty())
14✔
765
    {
766
        tokens.emplace_back(std::move(curToken));
14✔
767
    }
768
    for (const auto &token : tokens)
44✔
769
    {
770
        if (STARTS_WITH(token.c_str(), "name="))
32✔
771
        {
772
            srcName = token.substr(strlen("name="));
13✔
773
            if (dstName.empty())
13✔
774
                dstName = srcName;
13✔
775
        }
776
        else if (STARTS_WITH(token.c_str(), "band="))
19✔
777
        {
778
            band = atoi(token.substr(strlen("band=")).c_str());
1✔
779
            if (dstName.empty())
1✔
780
                dstName = CPLSPrintf("Band%d", band);
1✔
781
        }
782
        else if (STARTS_WITH(token.c_str(), "dstname="))
18✔
783
        {
784
            dstName = token.substr(strlen("dstname="));
10✔
785
        }
786
        else if (STARTS_WITH(token.c_str(), "transpose="))
8✔
787
        {
788
            auto transposeExpr = token.substr(strlen("transpose="));
2✔
789
            if (transposeExpr.size() < 3 || transposeExpr[0] != '[' ||
4✔
790
                transposeExpr.back() != ']')
2✔
791
            {
792
                CPLError(CE_Failure, CPLE_AppDefined,
×
793
                         "Invalid value for transpose");
794
                return false;
×
795
            }
796
            transposeExpr = transposeExpr.substr(1, transposeExpr.size() - 2);
2✔
797
            CPLStringList aosAxis(
798
                CSLTokenizeString2(transposeExpr.c_str(), ",", 0));
2✔
799
            for (int i = 0; i < aosAxis.size(); ++i)
5✔
800
            {
801
                int iAxis = atoi(aosAxis[i]);
4✔
802
                // check for non-integer characters
803
                if (iAxis == 0)
4✔
804
                {
805
                    if (!EQUAL(aosAxis[i], "0"))
2✔
806
                    {
807
                        CPLError(CE_Failure, CPLE_AppDefined,
1✔
808
                                 "Invalid value for axis in transpose: %s",
809
                                 aosAxis[i]);
810
                        return false;
1✔
811
                    }
812
                }
813

814
                anTransposedAxis.push_back(iAxis);
3✔
815
            }
816
        }
817
        else if (STARTS_WITH(token.c_str(), "view="))
6✔
818
        {
819
            viewExpr = token.substr(strlen("view="));
4✔
820
        }
821
        else if (STARTS_WITH(token.c_str(), "ot="))
2✔
822
        {
823
            auto outputTypeStr = token.substr(strlen("ot="));
×
824
            if (outputTypeStr == "String")
×
825
                outputType = GDALExtendedDataType::CreateString();
×
826
            else
827
            {
828
                auto eDT = GDALGetDataTypeByName(outputTypeStr.c_str());
×
829
                if (eDT == GDT_Unknown)
×
830
                    return false;
×
831
                outputType = GDALExtendedDataType::Create(eDT);
×
832
            }
833
        }
834
        else if (STARTS_WITH(token.c_str(), "resample="))
2✔
835
        {
836
            bResampled = CPLTestBool(token.c_str() + strlen("resample="));
1✔
837
        }
838
        else
839
        {
840
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
841
                     "Unexpected array specification part: %s", token.c_str());
842
            return false;
1✔
843
        }
844
    }
845
    return true;
12✔
846
}
847

848
/************************************************************************/
849
/*                           TranslateArray()                           */
850
/************************************************************************/
851

852
static bool TranslateArray(
144✔
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<GDALGroup> &poDstRootGroup,
859
    std::shared_ptr<GDALGroup> &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
    std::string srcArrayName;
288✔
865
    std::string dstArrayName;
288✔
866
    int band = -1;
144✔
867
    std::vector<int> anTransposedAxis;
288✔
868
    std::string viewExpr;
288✔
869
    bool bResampled = false;
144✔
870
    GDALExtendedDataType outputType(GDALExtendedDataType::Create(GDT_Unknown));
288✔
871
    if (!ParseArraySpec(arraySpec, srcArrayName, dstArrayName, band,
144✔
872
                        anTransposedAxis, viewExpr, outputType, bResampled))
873
    {
874
        return false;
2✔
875
    }
876

877
    std::shared_ptr<GDALMDArray> srcArray;
142✔
878
    bool bSrcArrayAccessibleThroughSrcGroup = true;
142✔
879
    if (poSrcRootGroup && poSrcGroup)
142✔
880
    {
881
        if (!srcArrayName.empty() && srcArrayName[0] == '/')
141✔
882
            srcArray = poSrcRootGroup->OpenMDArrayFromFullname(srcArrayName);
28✔
883
        else
884
            srcArray = poSrcGroup->OpenMDArray(srcArrayName);
113✔
885
        if (!srcArray)
141✔
886
        {
887
            if (poSrcArrayIn && poSrcArrayIn->GetFullName() == arraySpec)
3✔
888
            {
889
                bSrcArrayAccessibleThroughSrcGroup = false;
2✔
890
                srcArray = poSrcArrayIn;
2✔
891
            }
892
            else
893
            {
894
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find array %s",
1✔
895
                         srcArrayName.c_str());
896
                return false;
1✔
897
            }
898
        }
899
    }
900
    else
901
    {
902
        auto poBand = poSrcDS->GetRasterBand(band);
1✔
903
        if (!poBand)
1✔
904
            return false;
×
905
        srcArray = poBand->AsMDArray();
1✔
906
    }
907

908
    auto tmpArray = srcArray;
282✔
909

910
    if (bResampled)
141✔
911
    {
912
        auto newTmpArray =
913
            tmpArray->GetResampled(std::vector<std::shared_ptr<GDALDimension>>(
3✔
914
                                       tmpArray->GetDimensionCount()),
1✔
915
                                   GRIORA_NearestNeighbour, nullptr, nullptr);
2✔
916
        if (!newTmpArray)
1✔
917
            return false;
×
918
        tmpArray = std::move(newTmpArray);
1✔
919
    }
920

921
    if (!anTransposedAxis.empty())
141✔
922
    {
923
        auto newTmpArray = tmpArray->Transpose(anTransposedAxis);
1✔
924
        if (!newTmpArray)
1✔
925
            return false;
×
926
        tmpArray = std::move(newTmpArray);
1✔
927
    }
928
    const auto &srcArrayDims(tmpArray->GetDimensions());
141✔
929
    std::map<std::shared_ptr<GDALDimension>, std::shared_ptr<GDALDimension>>
930
        oMapSubsetDimToSrcDim;
282✔
931

932
    std::vector<GDALMDArray::ViewSpec> viewSpecs;
282✔
933
    if (!viewExpr.empty())
141✔
934
    {
935
        if (!psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty())
4✔
936
        {
937
            CPLError(CE_Failure, CPLE_NotSupported,
×
938
                     "View specification not supported when used together "
939
                     "with subset and/or scalefactor options");
940
            return false;
×
941
        }
942
        auto newTmpArray = tmpArray->GetView(viewExpr, true, viewSpecs);
4✔
943
        if (!newTmpArray)
4✔
944
            return false;
×
945
        tmpArray = std::move(newTmpArray);
4✔
946
    }
947
    else if (!psOptions->aosSubset.empty() ||
184✔
948
             !psOptions->aosScaleFactor.empty())
47✔
949
    {
950
        bool bHasModifiedDim = false;
98✔
951
        viewExpr = '[';
98✔
952
        for (size_t i = 0; i < srcArrayDims.size(); ++i)
194✔
953
        {
954
            const auto &srcDim(srcArrayDims[i]);
112✔
955
            const auto poDimDesc =
956
                GetDimensionDesc(oDimRemapper, psOptions, srcDim);
112✔
957
            if (poDimDesc == nullptr)
112✔
958
                return false;
16✔
959
            if (i > 0)
96✔
960
                viewExpr += ',';
14✔
961
            if (!poDimDesc->bSlice && poDimDesc->nStartIdx == 0 &&
76✔
962
                poDimDesc->nStep == 1 && poDimDesc->nSize == srcDim->GetSize())
172✔
963
            {
964
                viewExpr += ":";
28✔
965
            }
966
            else
967
            {
968
                bHasModifiedDim = true;
68✔
969
                viewExpr += CPLSPrintf(
970
                    CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStartIdx));
68✔
971
                if (!poDimDesc->bSlice)
68✔
972
                {
973
                    viewExpr += ':';
48✔
974
                    viewExpr +=
975
                        CPLSPrintf(CPL_FRMT_GUIB,
976
                                   static_cast<GUInt64>(poDimDesc->nStartIdx +
48✔
977
                                                        poDimDesc->nSize *
48✔
978
                                                            poDimDesc->nStep));
48✔
979
                    viewExpr += ':';
48✔
980
                    viewExpr += CPLSPrintf(
981
                        CPL_FRMT_GUIB, static_cast<GUInt64>(poDimDesc->nStep));
48✔
982
                }
983
            }
984
        }
985
        viewExpr += ']';
82✔
986
        if (bHasModifiedDim)
82✔
987
        {
988
            auto tmpArrayNew = tmpArray->GetView(viewExpr, false, viewSpecs);
66✔
989
            if (!tmpArrayNew)
66✔
990
                return false;
×
991
            tmpArray = std::move(tmpArrayNew);
66✔
992
            size_t j = 0;
66✔
993
            const auto &tmpArrayDims(tmpArray->GetDimensions());
66✔
994
            for (size_t i = 0; i < srcArrayDims.size(); ++i)
146✔
995
            {
996
                const auto &srcDim(srcArrayDims[i]);
80✔
997
                const auto poDimDesc =
998
                    GetDimensionDesc(oDimRemapper, psOptions, srcDim);
80✔
999
                if (poDimDesc == nullptr)
80✔
1000
                    return false;
×
1001
                if (poDimDesc->bSlice)
80✔
1002
                    continue;
20✔
1003
                CPLAssert(j < tmpArrayDims.size());
60✔
1004
                oMapSubsetDimToSrcDim[tmpArrayDims[j]] = srcDim;
60✔
1005
                j++;
60✔
1006
            }
1007
        }
1008
        else
1009
        {
1010
            viewExpr.clear();
16✔
1011
        }
1012
    }
1013

1014
    int idxSliceSpec = -1;
125✔
1015
    for (size_t i = 0; i < viewSpecs.size(); ++i)
195✔
1016
    {
1017
        if (viewSpecs[i].m_osFieldName.empty())
70✔
1018
        {
1019
            if (idxSliceSpec >= 0)
70✔
1020
            {
1021
                idxSliceSpec = -1;
×
1022
                break;
×
1023
            }
1024
            else
1025
            {
1026
                idxSliceSpec = static_cast<int>(i);
70✔
1027
            }
1028
        }
1029
    }
1030

1031
    // Map source dimensions to target dimensions
1032
    std::vector<std::shared_ptr<GDALDimension>> dstArrayDims;
250✔
1033
    const auto &tmpArrayDims(tmpArray->GetDimensions());
125✔
1034
    for (size_t i = 0; i < tmpArrayDims.size(); ++i)
265✔
1035
    {
1036
        const auto &srcDim(tmpArrayDims[i]);
140✔
1037
        std::string srcDimFullName(srcDim->GetFullName());
140✔
1038

1039
        std::shared_ptr<GDALDimension> dstDim;
×
1040
        {
1041
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
280✔
1042
            if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
140✔
1043
            {
1044
                dstDim =
1045
                    poDstRootGroup->OpenDimensionFromFullname(srcDimFullName);
81✔
1046
            }
1047
        }
1048
        if (dstDim)
140✔
1049
        {
1050
            dstArrayDims.emplace_back(dstDim);
45✔
1051
            continue;
45✔
1052
        }
1053

1054
        auto oIter = mapSrcToDstDims.find(srcDimFullName);
95✔
1055
        if (oIter != mapSrcToDstDims.end())
95✔
1056
        {
1057
            dstArrayDims.emplace_back(oIter->second);
2✔
1058
            continue;
2✔
1059
        }
1060
        auto oIterRealSrcDim = oMapSubsetDimToSrcDim.find(srcDim);
93✔
1061
        if (oIterRealSrcDim != oMapSubsetDimToSrcDim.end())
93✔
1062
        {
1063
            srcDimFullName = oIterRealSrcDim->second->GetFullName();
52✔
1064
            oIter = mapSrcToDstDims.find(srcDimFullName);
52✔
1065
            if (oIter != mapSrcToDstDims.end())
52✔
1066
            {
1067
                dstArrayDims.emplace_back(oIter->second);
10✔
1068
                continue;
10✔
1069
            }
1070
        }
1071

1072
        const auto nDimSize = srcDim->GetSize();
83✔
1073
        std::string newDimNameFullName(srcDimFullName);
83✔
1074
        std::string newDimName(srcDim->GetName());
83✔
1075
        int nIncr = 2;
83✔
1076
        std::string osDstGroupFullName(poDstGroup->GetFullName());
83✔
1077
        if (osDstGroupFullName == "/")
83✔
1078
            osDstGroupFullName.clear();
81✔
1079
        auto oIter2 = mapDstDimFullNames.find(osDstGroupFullName + '/' +
166✔
1080
                                              srcDim->GetName());
166✔
1081
        while (oIter2 != mapDstDimFullNames.end() &&
88✔
1082
               oIter2->second->GetSize() != nDimSize)
4✔
1083
        {
1084
            newDimName = srcDim->GetName() + CPLSPrintf("_%d", nIncr);
1✔
1085
            newDimNameFullName = osDstGroupFullName + '/' + srcDim->GetName() +
2✔
1086
                                 CPLSPrintf("_%d", nIncr);
1✔
1087
            nIncr++;
1✔
1088
            oIter2 = mapDstDimFullNames.find(newDimNameFullName);
1✔
1089
        }
1090
        if (oIter2 != mapDstDimFullNames.end() &&
86✔
1091
            oIter2->second->GetSize() == nDimSize)
3✔
1092
        {
1093
            dstArrayDims.emplace_back(oIter2->second);
3✔
1094
            continue;
3✔
1095
        }
1096

1097
        dstDim = poDstGroup->CreateDimension(newDimName, srcDim->GetType(),
240✔
1098
                                             srcDim->GetDirection(), nDimSize);
160✔
1099
        if (!dstDim)
80✔
1100
            return false;
×
1101
        if (!srcDimFullName.empty() && srcDimFullName[0] == '/')
80✔
1102
        {
1103
            mapSrcToDstDims[srcDimFullName] = dstDim;
72✔
1104
        }
1105
        mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
80✔
1106
        dstArrayDims.emplace_back(dstDim);
80✔
1107

1108
        std::shared_ptr<GDALMDArray> srcIndexVar;
×
1109
        GDALMDArray::Range range;
80✔
1110
        range.m_nStartIdx = 0;
80✔
1111
        range.m_nIncr = 1;
80✔
1112
        std::string indexingVarSpec;
80✔
1113
        if (idxSliceSpec >= 0)
80✔
1114
        {
1115
            const auto &viewSpec(viewSpecs[idxSliceSpec]);
49✔
1116
            auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
49✔
1117
            if (iParentDim != static_cast<size_t>(-1) &&
48✔
1118
                (srcIndexVar =
1119
                     srcArrayDims[iParentDim]->GetIndexingVariable()) !=
97✔
1120
                    nullptr &&
46✔
1121
                srcIndexVar->GetDimensionCount() == 1 &&
143✔
1122
                srcIndexVar->GetFullName() != srcArray->GetFullName())
46✔
1123
            {
1124
                CPLAssert(iParentDim < viewSpec.m_parentRanges.size());
10✔
1125
                range = viewSpec.m_parentRanges[iParentDim];
10✔
1126
                indexingVarSpec = "name=" + srcIndexVar->GetFullName();
10✔
1127
                indexingVarSpec += ",dstname=" + newDimName;
10✔
1128
                if (psOptions->aosSubset.empty() &&
20✔
1129
                    psOptions->aosScaleFactor.empty())
10✔
1130
                {
1131
                    if (range.m_nStartIdx != 0 || range.m_nIncr != 1 ||
7✔
1132
                        srcArrayDims[iParentDim]->GetSize() !=
3✔
1133
                            srcDim->GetSize())
3✔
1134
                    {
1135
                        indexingVarSpec += ",view=[";
1✔
1136
                        if (range.m_nIncr > 0 ||
2✔
1137
                            range.m_nStartIdx != srcDim->GetSize() - 1)
1✔
1138
                        {
1139
                            indexingVarSpec +=
1140
                                CPLSPrintf(CPL_FRMT_GUIB, range.m_nStartIdx);
×
1141
                        }
1142
                        indexingVarSpec += ':';
1✔
1143
                        if (range.m_nIncr > 0)
1✔
1144
                        {
1145
                            const auto nEndIdx =
1146
                                range.m_nStartIdx +
×
1147
                                range.m_nIncr * srcDim->GetSize();
×
1148
                            indexingVarSpec +=
1149
                                CPLSPrintf(CPL_FRMT_GUIB, nEndIdx);
×
1150
                        }
1151
                        else if (range.m_nStartIdx >
2✔
1152
                                 -range.m_nIncr * srcDim->GetSize())
1✔
1153
                        {
1154
                            const auto nEndIdx =
1155
                                range.m_nStartIdx +
×
1156
                                range.m_nIncr * srcDim->GetSize();
×
1157
                            indexingVarSpec +=
1158
                                CPLSPrintf(CPL_FRMT_GUIB, nEndIdx - 1);
×
1159
                        }
1160
                        indexingVarSpec += ':';
1✔
1161
                        indexingVarSpec +=
1162
                            CPLSPrintf(CPL_FRMT_GIB, range.m_nIncr);
1✔
1163
                        indexingVarSpec += ']';
1✔
1164
                    }
1165
                }
1166
            }
1167
        }
1168
        else
1169
        {
1170
            srcIndexVar = srcDim->GetIndexingVariable();
31✔
1171
            if (srcIndexVar)
31✔
1172
            {
1173
                indexingVarSpec = srcIndexVar->GetFullName();
29✔
1174
            }
1175
        }
1176
        if (srcIndexVar && !indexingVarSpec.empty() &&
119✔
1177
            srcIndexVar->GetFullName() != srcArray->GetFullName())
39✔
1178
        {
1179
            if (poSrcRootGroup)
30✔
1180
            {
1181
                if (!TranslateArray(oDimRemapper, srcIndexVar, indexingVarSpec,
28✔
1182
                                    poSrcRootGroup, poSrcGroup, poDstRootGroup,
1183
                                    poDstGroup, poSrcDS, mapSrcToDstDims,
1184
                                    mapDstDimFullNames, psOptions))
1185
                {
1186
                    return false;
×
1187
                }
1188
            }
1189
            else
1190
            {
1191
                GDALGeoTransform gt;
2✔
1192
                if (poSrcDS->GetGeoTransform(gt) == CE_None && gt[2] == 0.0 &&
4✔
1193
                    gt[4] == 0.0)
2✔
1194
                {
1195
                    auto var = std::dynamic_pointer_cast<VRTMDArray>(
1196
                        poDstGroup->CreateMDArray(
8✔
1197
                            newDimName, {dstDim},
1198
                            GDALExtendedDataType::Create(GDT_Float64)));
8✔
1199
                    if (var)
2✔
1200
                    {
1201
                        const double dfStart =
1202
                            srcIndexVar->GetName() == "X"
2✔
1203
                                ? gt[0] + (range.m_nStartIdx + 0.5) * gt[1]
2✔
1204
                                : gt[3] + (range.m_nStartIdx + 0.5) * gt[5];
1✔
1205
                        const double dfIncr =
1206
                            (srcIndexVar->GetName() == "X" ? gt[1] : gt[5]) *
2✔
1207
                            range.m_nIncr;
2✔
1208
                        std::unique_ptr<VRTMDArraySourceRegularlySpaced>
1209
                            poSource(new VRTMDArraySourceRegularlySpaced(
1210
                                dfStart, dfIncr));
2✔
1211
                        var->AddSource(std::move(poSource));
2✔
1212
                    }
1213
                }
1214
            }
1215

1216
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
60✔
1217
            auto poDstIndexingVar(poDstGroup->OpenMDArray(newDimName));
60✔
1218
            if (poDstIndexingVar)
30✔
1219
                dstDim->SetIndexingVariable(std::move(poDstIndexingVar));
29✔
1220
        }
1221
    }
1222
    if (outputType.GetClass() == GEDTC_NUMERIC &&
250✔
1223
        outputType.GetNumericDataType() == GDT_Unknown)
125✔
1224
    {
1225
        outputType = GDALExtendedDataType(tmpArray->GetDataType());
125✔
1226
    }
1227
    auto dstArray =
1228
        poDstGroup->CreateMDArray(dstArrayName, dstArrayDims, outputType);
250✔
1229
    auto dstArrayVRT = std::dynamic_pointer_cast<VRTMDArray>(dstArray);
250✔
1230
    if (!dstArrayVRT)
125✔
1231
        return false;
×
1232

1233
    GUInt64 nCurCost = 0;
125✔
1234
    dstArray->CopyFromAllExceptValues(srcArray.get(), false, nCurCost, 0,
125✔
1235
                                      nullptr, nullptr);
1236
    if (bResampled)
125✔
1237
        dstArray->SetSpatialRef(tmpArray->GetSpatialRef().get());
1✔
1238

1239
    if (idxSliceSpec >= 0)
125✔
1240
    {
1241
        std::set<size_t> oSetParentDimIdxNotInArray;
140✔
1242
        for (size_t i = 0; i < srcArrayDims.size(); ++i)
158✔
1243
        {
1244
            oSetParentDimIdxNotInArray.insert(i);
88✔
1245
        }
1246
        const auto &viewSpec(viewSpecs[idxSliceSpec]);
70✔
1247
        for (size_t i = 0; i < tmpArrayDims.size(); ++i)
138✔
1248
        {
1249
            auto iParentDim = viewSpec.m_mapDimIdxToParentDimIdx[i];
68✔
1250
            if (iParentDim != static_cast<size_t>(-1))
68✔
1251
            {
1252
                oSetParentDimIdxNotInArray.erase(iParentDim);
67✔
1253
            }
1254
        }
1255
        for (const auto parentDimIdx : oSetParentDimIdxNotInArray)
91✔
1256
        {
1257
            const auto &srcDim(srcArrayDims[parentDimIdx]);
21✔
1258
            const auto nStartIdx =
1259
                viewSpec.m_parentRanges[parentDimIdx].m_nStartIdx;
21✔
1260
            if (nStartIdx < static_cast<GUInt64>(INT_MAX))
21✔
1261
            {
1262
                auto dstAttr = dstArray->CreateAttribute(
21✔
1263
                    "DIM_" + srcDim->GetName() + "_INDEX", {},
42✔
1264
                    GDALExtendedDataType::Create(GDT_Int32));
84✔
1265
                dstAttr->Write(static_cast<int>(nStartIdx));
21✔
1266
            }
1267
            else
1268
            {
1269
                auto dstAttr = dstArray->CreateAttribute(
×
1270
                    "DIM_" + srcDim->GetName() + "_INDEX", {},
×
1271
                    GDALExtendedDataType::CreateString());
×
1272
                dstAttr->Write(CPLSPrintf(CPL_FRMT_GUIB,
×
1273
                                          static_cast<GUIntBig>(nStartIdx)));
1274
            }
1275

1276
            auto srcIndexVar(srcDim->GetIndexingVariable());
42✔
1277
            if (srcIndexVar && srcIndexVar->GetDimensionCount() == 1)
21✔
1278
            {
1279
                const auto &dt(srcIndexVar->GetDataType());
21✔
1280
                std::vector<GByte> abyTmp(dt.GetSize());
42✔
1281
                const size_t nCount = 1;
21✔
1282
                if (srcIndexVar->Read(&nStartIdx, &nCount, nullptr, nullptr, dt,
42✔
1283
                                      &abyTmp[0], nullptr, 0))
21✔
1284
                {
1285
                    {
1286
                        auto dstAttr = dstArray->CreateAttribute(
21✔
1287
                            "DIM_" + srcDim->GetName() + "_VALUE", {}, dt);
63✔
1288
                        dstAttr->Write(abyTmp.data(), abyTmp.size());
21✔
1289
                        dt.FreeDynamicMemory(&abyTmp[0]);
21✔
1290
                    }
1291

1292
                    const auto &unit(srcIndexVar->GetUnit());
21✔
1293
                    if (!unit.empty())
21✔
1294
                    {
1295
                        auto dstAttr = dstArray->CreateAttribute(
×
1296
                            "DIM_" + srcDim->GetName() + "_UNIT", {},
×
1297
                            GDALExtendedDataType::CreateString());
×
1298
                        dstAttr->Write(unit.c_str());
×
1299
                    }
1300
                }
1301
            }
1302
        }
1303
    }
1304

1305
    double dfStart = 0.0;
125✔
1306
    double dfIncrement = 0.0;
125✔
1307
    if (!bSrcArrayAccessibleThroughSrcGroup &&
127✔
1308
        tmpArray->IsRegularlySpaced(dfStart, dfIncrement))
2✔
1309
    {
1310
        auto poSource = std::make_unique<VRTMDArraySourceRegularlySpaced>(
1311
            dfStart, dfIncrement);
2✔
1312
        dstArrayVRT->AddSource(std::move(poSource));
2✔
1313
    }
1314
    else
1315
    {
1316
        const auto dimCount(tmpArray->GetDimensionCount());
123✔
1317
        std::vector<GUInt64> anSrcOffset(dimCount);
246✔
1318
        std::vector<GUInt64> anCount(dimCount);
246✔
1319
        for (size_t i = 0; i < dimCount; ++i)
261✔
1320
        {
1321
            anCount[i] = tmpArrayDims[i]->GetSize();
138✔
1322
        }
1323
        std::vector<GUInt64> anStep(dimCount, 1);
246✔
1324
        std::vector<GUInt64> anDstOffset(dimCount);
246✔
1325
        std::unique_ptr<VRTMDArraySourceFromArray> poSource(
1326
            new VRTMDArraySourceFromArray(
1327
                dstArrayVRT.get(), false, false, poSrcDS->GetDescription(),
123✔
1328
                band < 0 ? srcArray->GetFullName() : std::string(),
246✔
1329
                band >= 1 ? CPLSPrintf("%d", band) : std::string(),
247✔
1330
                std::move(anTransposedAxis),
123✔
1331
                bResampled
1332
                    ? (viewExpr.empty()
368✔
1333
                           ? std::string("resample=true")
1334
                           : std::string("resample=true,").append(viewExpr))
123✔
1335
                    : std::move(viewExpr),
122✔
1336
                std::move(anSrcOffset), std::move(anCount), std::move(anStep),
123✔
1337
                std::move(anDstOffset)));
369✔
1338
        dstArrayVRT->AddSource(std::move(poSource));
123✔
1339
    }
1340

1341
    return true;
125✔
1342
}
1343

1344
/************************************************************************/
1345
/*                               GetGroup()                             */
1346
/************************************************************************/
1347

1348
static std::shared_ptr<GDALGroup>
1349
GetGroup(const std::shared_ptr<GDALGroup> &poRootGroup,
6✔
1350
         const std::string &fullName)
1351
{
1352
    auto poCurGroup = poRootGroup;
12✔
1353
    CPLStringList aosTokens(CSLTokenizeString2(fullName.c_str(), "/", 0));
12✔
1354
    for (int i = 0; i < aosTokens.size(); i++)
10✔
1355
    {
1356
        auto poCurGroupNew = poCurGroup->OpenGroup(aosTokens[i], nullptr);
10✔
1357
        if (!poCurGroupNew)
5✔
1358
        {
1359
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
1✔
1360
                     aosTokens[i]);
1361
            return nullptr;
1✔
1362
        }
1363
        poCurGroup = std::move(poCurGroupNew);
4✔
1364
    }
1365
    return poCurGroup;
5✔
1366
}
1367

1368
/************************************************************************/
1369
/*                                CopyGroup()                           */
1370
/************************************************************************/
1371

1372
static bool CopyGroup(
17✔
1373
    DimensionRemapper &oDimRemapper,
1374
    const std::shared_ptr<GDALGroup> &poDstRootGroup,
1375
    std::shared_ptr<GDALGroup> &poDstGroup,
1376
    const std::shared_ptr<GDALGroup> &poSrcRootGroup,
1377
    const std::shared_ptr<GDALGroup> &poSrcGroup, GDALDataset *poSrcDS,
1378
    std::map<std::string, std::shared_ptr<GDALDimension>> &mapSrcToDstDims,
1379
    std::map<std::string, std::shared_ptr<GDALDimension>> &mapDstDimFullNames,
1380
    const GDALMultiDimTranslateOptions *psOptions, bool bRecursive)
1381
{
1382
    const auto srcDims = poSrcGroup->GetDimensions();
34✔
1383
    std::map<std::string, std::string> mapSrcVariableNameToIndexedDimName;
34✔
1384
    for (const auto &dim : srcDims)
29✔
1385
    {
1386
        const auto poDimDesc = GetDimensionDesc(oDimRemapper, psOptions, dim);
15✔
1387
        if (poDimDesc == nullptr)
15✔
1388
            return false;
3✔
1389
        if (poDimDesc->bSlice)
12✔
1390
            continue;
2✔
1391
        auto dstDim =
1392
            poDstGroup->CreateDimension(dim->GetName(), dim->GetType(),
10✔
1393
                                        dim->GetDirection(), poDimDesc->nSize);
10✔
1394
        if (!dstDim)
10✔
1395
            return false;
×
1396
        mapSrcToDstDims[dim->GetFullName()] = dstDim;
10✔
1397
        mapDstDimFullNames[dstDim->GetFullName()] = dstDim;
10✔
1398
        auto poIndexingVarSrc(dim->GetIndexingVariable());
20✔
1399
        if (poIndexingVarSrc)
10✔
1400
        {
1401
            mapSrcVariableNameToIndexedDimName[poIndexingVarSrc->GetName()] =
10✔
1402
                dim->GetFullName();
20✔
1403
        }
1404
    }
1405

1406
    if (!(poSrcGroup == poSrcRootGroup && psOptions->aosGroup.empty()))
14✔
1407
    {
1408
        auto attrs = poSrcGroup->GetAttributes();
11✔
1409
        for (const auto &attr : attrs)
15✔
1410
        {
1411
            auto dstAttr = poDstGroup->CreateAttribute(
4✔
1412
                attr->GetName(), attr->GetDimensionsSize(),
4✔
1413
                attr->GetDataType());
8✔
1414
            if (!dstAttr)
4✔
1415
            {
1416
                if (!psOptions->bStrict)
×
1417
                    continue;
×
1418
                return false;
×
1419
            }
1420
            auto raw(attr->ReadAsRaw());
4✔
1421
            if (!dstAttr->Write(raw.data(), raw.size()) && !psOptions->bStrict)
4✔
1422
                return false;
×
1423
        }
1424
    }
1425

1426
    auto arrayNames =
1427
        poSrcGroup->GetMDArrayNames(psOptions->aosArrayOptions.List());
28✔
1428
    for (const auto &name : arrayNames)
40✔
1429
    {
1430
        if (!TranslateArray(oDimRemapper, nullptr, name, poSrcRootGroup,
26✔
1431
                            poSrcGroup, poDstRootGroup, poDstGroup, poSrcDS,
1432
                            mapSrcToDstDims, mapDstDimFullNames, psOptions))
1433
        {
1434
            return false;
×
1435
        }
1436

1437
        // If this array is the indexing variable of a dimension, link them
1438
        // together.
1439
        auto srcArray = poSrcGroup->OpenMDArray(name);
52✔
1440
        CPLAssert(srcArray);
26✔
1441
        auto dstArray = poDstGroup->OpenMDArray(name);
52✔
1442
        CPLAssert(dstArray);
26✔
1443
        auto oIterDimName =
1444
            mapSrcVariableNameToIndexedDimName.find(srcArray->GetName());
26✔
1445
        if (oIterDimName != mapSrcVariableNameToIndexedDimName.end())
26✔
1446
        {
1447
            auto oCorrespondingDimIter =
1448
                mapSrcToDstDims.find(oIterDimName->second);
10✔
1449
            if (oCorrespondingDimIter != mapSrcToDstDims.end())
10✔
1450
            {
1451
                CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
10✔
1452
                oCorrespondingDimIter->second->SetIndexingVariable(
20✔
1453
                    std::move(dstArray));
10✔
1454
            }
1455
        }
1456
    }
1457

1458
    if (bRecursive)
14✔
1459
    {
1460
        auto groupNames = poSrcGroup->GetGroupNames();
14✔
1461
        for (const auto &name : groupNames)
20✔
1462
        {
1463
            auto srcSubGroup = poSrcGroup->OpenGroup(name);
6✔
1464
            if (!srcSubGroup)
6✔
1465
            {
1466
                return false;
×
1467
            }
1468
            auto dstSubGroup = poDstGroup->CreateGroup(name);
6✔
1469
            if (!dstSubGroup)
6✔
1470
            {
1471
                return false;
×
1472
            }
1473
            if (!CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
6✔
1474
                           poSrcRootGroup, srcSubGroup, poSrcDS,
1475
                           mapSrcToDstDims, mapDstDimFullNames, psOptions,
1476
                           true))
1477
            {
1478
                return false;
×
1479
            }
1480
        }
1481
    }
1482
    return true;
14✔
1483
}
1484

1485
/************************************************************************/
1486
/*                           ParseGroupSpec()                           */
1487
/************************************************************************/
1488

1489
// foo
1490
// name=foo,dstname=bar,recursive=no
1491
static bool ParseGroupSpec(const std::string &groupSpec, std::string &srcName,
7✔
1492
                           std::string &dstName, bool &bRecursive)
1493
{
1494
    bRecursive = true;
7✔
1495
    if (!STARTS_WITH(groupSpec.c_str(), "name="))
7✔
1496
    {
1497
        srcName = groupSpec;
5✔
1498
        return true;
5✔
1499
    }
1500

1501
    CPLStringList aosTokens(CSLTokenizeString2(groupSpec.c_str(), ",", 0));
4✔
1502
    for (int i = 0; i < aosTokens.size(); i++)
5✔
1503
    {
1504
        const std::string token(aosTokens[i]);
4✔
1505
        if (STARTS_WITH(token.c_str(), "name="))
4✔
1506
        {
1507
            srcName = token.substr(strlen("name="));
2✔
1508
        }
1509
        else if (STARTS_WITH(token.c_str(), "dstname="))
2✔
1510
        {
1511
            dstName = token.substr(strlen("dstname="));
1✔
1512
        }
1513
        else if (token == "recursive=no")
1✔
1514
        {
1515
            bRecursive = false;
×
1516
        }
1517
        else
1518
        {
1519
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
1520
                     "Unexpected group specification part: %s", token.c_str());
1521
            return false;
1✔
1522
        }
1523
    }
1524
    return true;
1✔
1525
}
1526

1527
/************************************************************************/
1528
/*                           TranslateInternal()                        */
1529
/************************************************************************/
1530

1531
static bool TranslateInternal(std::shared_ptr<GDALGroup> &poDstRootGroup,
102✔
1532
                              GDALDataset *poSrcDS,
1533
                              const GDALMultiDimTranslateOptions *psOptions)
1534
{
1535

1536
    auto poSrcRootGroup = poSrcDS->GetRootGroup();
204✔
1537
    if (poSrcRootGroup)
102✔
1538
    {
1539
        if (psOptions->aosGroup.empty())
101✔
1540
        {
1541
            auto attrs = poSrcRootGroup->GetAttributes();
190✔
1542
            for (const auto &attr : attrs)
99✔
1543
            {
1544
                if (attr->GetName() == "Conventions")
4✔
1545
                    continue;
1✔
1546
                auto dstAttr = poDstRootGroup->CreateAttribute(
3✔
1547
                    attr->GetName(), attr->GetDimensionsSize(),
3✔
1548
                    attr->GetDataType());
9✔
1549
                if (dstAttr)
3✔
1550
                {
1551
                    auto raw(attr->ReadAsRaw());
6✔
1552
                    dstAttr->Write(raw.data(), raw.size());
3✔
1553
                }
1554
            }
1555
        }
1556
    }
1557

1558
    DimensionRemapper oDimRemapper;
204✔
1559
    std::map<std::string, std::shared_ptr<GDALDimension>> mapSrcToDstDims;
204✔
1560
    std::map<std::string, std::shared_ptr<GDALDimension>> mapDstDimFullNames;
204✔
1561
    if (!psOptions->aosGroup.empty())
102✔
1562
    {
1563
        if (poSrcRootGroup == nullptr)
6✔
1564
        {
1565
            CPLError(
×
1566
                CE_Failure, CPLE_AppDefined,
1567
                "No multidimensional source dataset: -group cannot be used");
1568
            return false;
×
1569
        }
1570
        if (psOptions->aosGroup.size() == 1)
6✔
1571
        {
1572
            std::string srcName;
10✔
1573
            std::string dstName;
10✔
1574
            bool bRecursive;
1575
            if (!ParseGroupSpec(psOptions->aosGroup[0], srcName, dstName,
5✔
1576
                                bRecursive))
1577
                return false;
1✔
1578
            auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
8✔
1579
            if (!poSrcGroup)
4✔
1580
                return false;
1✔
1581
            return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
3✔
1582
                             poSrcRootGroup, poSrcGroup, poSrcDS,
1583
                             mapSrcToDstDims, mapDstDimFullNames, psOptions,
1584
                             bRecursive);
3✔
1585
        }
1586
        else
1587
        {
1588
            for (const auto &osGroupSpec : psOptions->aosGroup)
3✔
1589
            {
1590
                std::string srcName;
2✔
1591
                std::string dstName;
2✔
1592
                bool bRecursive;
1593
                if (!ParseGroupSpec(osGroupSpec, srcName, dstName, bRecursive))
2✔
1594
                    return false;
×
1595
                auto poSrcGroup = GetGroup(poSrcRootGroup, srcName);
2✔
1596
                if (!poSrcGroup)
2✔
1597
                    return false;
×
1598
                if (dstName.empty())
2✔
1599
                    dstName = poSrcGroup->GetName();
1✔
1600
                auto dstSubGroup = poDstRootGroup->CreateGroup(dstName);
2✔
1601
                if (!dstSubGroup ||
4✔
1602
                    !CopyGroup(oDimRemapper, poDstRootGroup, dstSubGroup,
2✔
1603
                               poSrcRootGroup, poSrcGroup, poSrcDS,
1604
                               mapSrcToDstDims, mapDstDimFullNames, psOptions,
1605
                               bRecursive))
1606
                {
1607
                    return false;
×
1608
                }
1609
            }
1610
        }
1611
    }
1612
    else if (!psOptions->aosArraySpec.empty())
96✔
1613
    {
1614
        for (const auto &arraySpec : psOptions->aosArraySpec)
161✔
1615
        {
1616
            if (!TranslateArray(oDimRemapper, nullptr, arraySpec,
90✔
1617
                                poSrcRootGroup, poSrcRootGroup, poDstRootGroup,
1618
                                poDstRootGroup, poSrcDS, mapSrcToDstDims,
1619
                                mapDstDimFullNames, psOptions))
1620
            {
1621
                return false;
19✔
1622
            }
1623
        }
1624
    }
1625
    else
1626
    {
1627
        if (poSrcRootGroup == nullptr)
6✔
1628
        {
1629
            CPLError(CE_Failure, CPLE_AppDefined,
×
1630
                     "No multidimensional source dataset");
1631
            return false;
×
1632
        }
1633
        return CopyGroup(oDimRemapper, poDstRootGroup, poDstRootGroup,
6✔
1634
                         poSrcRootGroup, poSrcRootGroup, poSrcDS,
1635
                         mapSrcToDstDims, mapDstDimFullNames, psOptions, true);
6✔
1636
    }
1637

1638
    return true;
72✔
1639
}
1640

1641
/************************************************************************/
1642
/*                      CopyToNonMultiDimensionalDriver()               */
1643
/************************************************************************/
1644

1645
static GDALDatasetH
1646
CopyToNonMultiDimensionalDriver(GDALDriver *poDriver, const char *pszDest,
4✔
1647
                                const std::shared_ptr<GDALGroup> &poRG,
1648
                                const GDALMultiDimTranslateOptions *psOptions)
1649
{
1650
    std::shared_ptr<GDALMDArray> srcArray;
4✔
1651
    if (psOptions && !psOptions->aosArraySpec.empty())
4✔
1652
    {
1653
        if (psOptions->aosArraySpec.size() != 1)
3✔
1654
        {
1655
            CPLError(CE_Failure, CPLE_NotSupported,
×
1656
                     "For output to a non-multidimensional driver, only "
1657
                     "one array should be specified");
1658
            return nullptr;
×
1659
        }
1660
        std::string srcArrayName;
6✔
1661
        std::string dstArrayName;
6✔
1662
        int band = -1;
3✔
1663
        std::vector<int> anTransposedAxis;
6✔
1664
        std::string viewExpr;
6✔
1665
        GDALExtendedDataType outputType(
1666
            GDALExtendedDataType::Create(GDT_Unknown));
3✔
1667
        bool bResampled = false;
3✔
1668
        ParseArraySpec(psOptions->aosArraySpec[0], srcArrayName, dstArrayName,
3✔
1669
                       band, anTransposedAxis, viewExpr, outputType,
1670
                       bResampled);
1671
        srcArray = poRG->OpenMDArray(dstArrayName);
3✔
1672
    }
1673
    else
1674
    {
1675
        auto srcArrayNames = poRG->GetMDArrayNames(
1✔
1676
            psOptions ? psOptions->aosArrayOptions.List() : nullptr);
1✔
1677
        for (const auto &srcArrayName : srcArrayNames)
4✔
1678
        {
1679
            auto tmpArray = poRG->OpenMDArray(srcArrayName);
4✔
1680
            if (tmpArray)
4✔
1681
            {
1682
                const auto &dims(tmpArray->GetDimensions());
4✔
1683
                if (!(dims.size() == 1 && dims[0]->GetIndexingVariable() &&
10✔
1684
                      dims[0]->GetIndexingVariable()->GetName() ==
6✔
1685
                          srcArrayName))
1686
                {
1687
                    if (srcArray)
2✔
1688
                    {
1689
                        CPLError(CE_Failure, CPLE_AppDefined,
1✔
1690
                                 "Several arrays exist. Select one for "
1691
                                 "output to non-multidimensional driver");
1692
                        return nullptr;
1✔
1693
                    }
1694
                    srcArray = std::move(tmpArray);
1✔
1695
                }
1696
            }
1697
        }
1698
    }
1699
    if (!srcArray)
3✔
1700
    {
1701
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find source array");
×
1702
        return nullptr;
×
1703
    }
1704
    size_t iXDim = static_cast<size_t>(-1);
3✔
1705
    size_t iYDim = static_cast<size_t>(-1);
3✔
1706
    const auto &dims(srcArray->GetDimensions());
3✔
1707
    for (size_t i = 0; i < dims.size(); ++i)
8✔
1708
    {
1709
        if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
5✔
1710
        {
1711
            iXDim = i;
2✔
1712
        }
1713
        else if (dims[i]->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
3✔
1714
        {
1715
            iYDim = i;
3✔
1716
        }
1717
    }
1718
    if (dims.size() == 1)
3✔
1719
    {
1720
        iXDim = 0;
1✔
1721
    }
1722
    else if (dims.size() >= 2 && (iXDim == static_cast<size_t>(-1) ||
2✔
1723
                                  iYDim == static_cast<size_t>(-1)))
1724
    {
1725
        iXDim = dims.size() - 1;
×
1726
        iYDim = dims.size() - 2;
×
1727
    }
1728
    std::unique_ptr<GDALDataset> poTmpSrcDS(
1729
        srcArray->AsClassicDataset(iXDim, iYDim));
6✔
1730
    if (!poTmpSrcDS)
3✔
1731
        return nullptr;
×
1732
    return GDALDataset::ToHandle(poDriver->CreateCopy(
6✔
1733
        pszDest, poTmpSrcDS.get(), false,
1734
        psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
3✔
1735
                  : nullptr,
1736
        psOptions ? psOptions->pfnProgress : nullptr,
1737
        psOptions ? psOptions->pProgressData : nullptr));
3✔
1738
}
1739

1740
/************************************************************************/
1741
/*                        GDALMultiDimTranslate()                       */
1742
/************************************************************************/
1743

1744
/* clang-format off */
1745
/**
1746
 * Converts raster data between different formats.
1747
 *
1748
 * This is the equivalent of the
1749
 * <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
1750
 *
1751
 * GDALMultiDimTranslateOptions* must be allocated and freed with
1752
 * GDALMultiDimTranslateOptionsNew() and GDALMultiDimTranslateOptionsFree()
1753
 * respectively. pszDest and hDstDS cannot be used at the same time.
1754
 *
1755
 * @param pszDest the destination dataset path or NULL.
1756
 * @param hDstDS the destination dataset or NULL.
1757
 * @param nSrcCount the number of input datasets.
1758
 * @param pahSrcDS the list of input datasets.
1759
 * @param psOptions the options struct returned by
1760
 * GDALMultiDimTranslateOptionsNew() or NULL.
1761
 * @param pbUsageError pointer to a integer output variable to store if any
1762
 * usage error has occurred or NULL.
1763
 * @return the output dataset (new dataset that must be closed using
1764
 * GDALClose(), or hDstDS is not NULL) or NULL in case of error.
1765
 *
1766
 * @since GDAL 3.1
1767
 */
1768
/* clang-format on */
1769

1770
GDALDatasetH
1771
GDALMultiDimTranslate(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
116✔
1772
                      GDALDatasetH *pahSrcDS,
1773
                      const GDALMultiDimTranslateOptions *psOptions,
1774
                      int *pbUsageError)
1775
{
1776
    if (pbUsageError)
116✔
1777
        *pbUsageError = false;
106✔
1778
    if (nSrcCount != 1 || pahSrcDS[0] == nullptr)
116✔
1779
    {
1780
        CPLError(CE_Failure, CPLE_NotSupported,
×
1781
                 "Only one source dataset is supported");
1782
        if (pbUsageError)
×
1783
            *pbUsageError = true;
×
1784
        return nullptr;
×
1785
    }
1786

1787
    if (hDstDS)
116✔
1788
    {
1789
        CPLError(CE_Failure, CPLE_NotSupported,
×
1790
                 "Update of existing file not supported yet");
1791
        GDALClose(hDstDS);
×
1792
        return nullptr;
×
1793
    }
1794

1795
    CPLString osFormat(psOptions ? psOptions->osFormat : "");
348✔
1796
    if (pszDest == nullptr /* && hDstDS == nullptr */)
116✔
1797
    {
1798
        CPLError(CE_Failure, CPLE_NotSupported,
×
1799
                 "Both pszDest and hDstDS are NULL.");
1800
        if (pbUsageError)
×
1801
            *pbUsageError = true;
×
1802
        return nullptr;
×
1803
    }
1804

1805
    GDALDriver *poDriver = nullptr;
116✔
1806

1807
#ifdef this_is_dead_code_for_now
1808
    const bool bCloseOutDSOnError = hDstDS == nullptr;
1809
    if (pszDest == nullptr)
1810
        pszDest = GDALGetDescription(hDstDS);
1811
#endif
1812

1813
    if (psOptions && psOptions->bOverwrite && !EQUAL(pszDest, ""))
116✔
1814
    {
1815
        VSIRmdirRecursive(pszDest);
1✔
1816
    }
1817
    else if (psOptions && psOptions->bNoOverwrite && !EQUAL(pszDest, ""))
115✔
1818
    {
1819
        VSIStatBufL sStat;
1820
        if (VSIStatL(pszDest, &sStat) == 0)
8✔
1821
        {
1822
            CPLError(CE_Failure, CPLE_AppDefined,
×
1823
                     "File '%s' already exists. Specify the --overwrite "
1824
                     "option to overwrite it.",
1825
                     pszDest);
1826
            return nullptr;
×
1827
        }
1828
        else if (std::unique_ptr<GDALDataset>(GDALDataset::Open(pszDest)))
8✔
1829
        {
1830
            CPLError(CE_Failure, CPLE_AppDefined,
×
1831
                     "Dataset '%s' already exists. Specify the --overwrite "
1832
                     "option to overwrite it.",
1833
                     pszDest);
1834
            return nullptr;
×
1835
        }
1836
    }
1837

1838
#ifdef this_is_dead_code_for_now
1839
    if (hDstDS == nullptr)
1840
#endif
1841
    {
1842
        if (osFormat.empty())
116✔
1843
        {
1844
            if (EQUAL(CPLGetExtensionSafe(pszDest).c_str(), "nc"))
107✔
1845
                osFormat = "netCDF";
2✔
1846
            else
1847
                osFormat = GetOutputDriverForRaster(pszDest);
105✔
1848
            if (osFormat.empty())
107✔
1849
            {
1850
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1851
                         "Cannot determine output driver for dataset name '%s'",
1852
                         pszDest);
1853
                return nullptr;
1✔
1854
            }
1855
        }
1856
        poDriver = GDALDriver::FromHandle(GDALGetDriverByName(osFormat));
115✔
1857
        char **papszDriverMD = poDriver ? poDriver->GetMetadata() : nullptr;
115✔
1858
        if (poDriver == nullptr ||
230✔
1859
            (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_RASTER,
115✔
1860
                                               "FALSE")) &&
×
1861
             !CPLTestBool(CSLFetchNameValueDef(
×
1862
                 papszDriverMD, GDAL_DCAP_MULTIDIM_RASTER, "FALSE"))) ||
230✔
1863
            (!CPLTestBool(CSLFetchNameValueDef(papszDriverMD, GDAL_DCAP_CREATE,
115✔
1864
                                               "FALSE")) &&
×
1865
             !CPLTestBool(CSLFetchNameValueDef(
×
1866
                 papszDriverMD, GDAL_DCAP_CREATECOPY, "FALSE")) &&
×
1867
             !CPLTestBool(CSLFetchNameValueDef(
×
1868
                 papszDriverMD, GDAL_DCAP_CREATE_MULTIDIMENSIONAL, "FALSE")) &&
×
1869
             !CPLTestBool(CSLFetchNameValueDef(
×
1870
                 papszDriverMD, GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL,
1871
                 "FALSE"))))
1872
        {
1873
            CPLError(CE_Failure, CPLE_NotSupported,
×
1874
                     "Output driver `%s' not recognised or does not support "
1875
                     "output file creation.",
1876
                     osFormat.c_str());
1877
            return nullptr;
×
1878
        }
1879
    }
1880

1881
    GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
115✔
1882

1883
    std::unique_ptr<GDALDataset> poTmpDS;
115✔
1884
    GDALDataset *poTmpSrcDS = poSrcDS;
115✔
1885
    if (psOptions &&
230✔
1886
        (!psOptions->aosArraySpec.empty() || !psOptions->aosGroup.empty() ||
115✔
1887
         !psOptions->aosSubset.empty() || !psOptions->aosScaleFactor.empty() ||
19✔
1888
         !psOptions->aosArrayOptions.empty()))
14✔
1889
    {
1890
        poTmpDS.reset(VRTDataset::CreateMultiDimensional("", nullptr, nullptr));
102✔
1891
        CPLAssert(poTmpDS);
102✔
1892
        poTmpSrcDS = poTmpDS.get();
102✔
1893

1894
        auto poDstRootGroup = poTmpDS->GetRootGroup();
102✔
1895
        CPLAssert(poDstRootGroup);
102✔
1896

1897
        if (!TranslateInternal(poDstRootGroup, poSrcDS, psOptions))
102✔
1898
        {
1899
#ifdef this_is_dead_code_for_now
1900
            if (bCloseOutDSOnError)
1901
#endif
1902
            {
1903
                GDALClose(hDstDS);
24✔
1904
                hDstDS = nullptr;
24✔
1905
            }
1906
            return nullptr;
24✔
1907
        }
1908
    }
1909

1910
    auto poRG(poTmpSrcDS->GetRootGroup());
91✔
1911
    if (poRG &&
180✔
1912
        poDriver->GetMetadataItem(GDAL_DCAP_CREATE_MULTIDIMENSIONAL) ==
89✔
1913
            nullptr &&
180✔
1914
        poDriver->GetMetadataItem(GDAL_DCAP_CREATECOPY_MULTIDIMENSIONAL) ==
4✔
1915
            nullptr)
1916
    {
1917
#ifdef this_is_dead_code_for_now
1918
        if (hDstDS)
1919
        {
1920
            CPLError(CE_Failure, CPLE_NotSupported,
1921
                     "Appending to non-multidimensional driver not supported.");
1922
            GDALClose(hDstDS);
1923
            hDstDS = nullptr;
1924
            return nullptr;
1925
        }
1926
#endif
1927
        hDstDS =
1928
            CopyToNonMultiDimensionalDriver(poDriver, pszDest, poRG, psOptions);
4✔
1929
    }
1930
    else
1931
    {
1932
        hDstDS = GDALDataset::ToHandle(poDriver->CreateCopy(
174✔
1933
            pszDest, poTmpSrcDS, false,
1934
            psOptions ? const_cast<char **>(psOptions->aosCreateOptions.List())
87✔
1935
                      : nullptr,
1936
            psOptions ? psOptions->pfnProgress : nullptr,
1937
            psOptions ? psOptions->pProgressData : nullptr));
1938
    }
1939

1940
    return hDstDS;
91✔
1941
}
1942

1943
/************************************************************************/
1944
/*                     GDALMultiDimTranslateOptionsNew()                */
1945
/************************************************************************/
1946

1947
/**
1948
 * Allocates a GDALMultiDimTranslateOptions struct.
1949
 *
1950
 * @param papszArgv NULL terminated list of options (potentially including
1951
 * filename and open options too), or NULL. The accepted options are the ones of
1952
 * the <a href="/programs/gdalmdimtranslate.html">gdalmdimtranslate</a> utility.
1953
 * @param psOptionsForBinary should be nullptr, unless called from
1954
 * gdalmdimtranslate_bin.cpp
1955
 * @return pointer to the allocated GDALMultiDimTranslateOptions struct. Must be
1956
 * freed with GDALMultiDimTranslateOptionsFree().
1957
 *
1958
 * @since GDAL 3.1
1959
 */
1960

1961
GDALMultiDimTranslateOptions *GDALMultiDimTranslateOptionsNew(
117✔
1962
    char **papszArgv, GDALMultiDimTranslateOptionsForBinary *psOptionsForBinary)
1963
{
1964

1965
    auto psOptions = std::make_unique<GDALMultiDimTranslateOptions>();
234✔
1966

1967
    /* -------------------------------------------------------------------- */
1968
    /*      Parse arguments.                                                */
1969
    /* -------------------------------------------------------------------- */
1970
    try
1971
    {
1972
        auto argParser = GDALMultiDimTranslateAppOptionsGetParser(
1973
            psOptions.get(), psOptionsForBinary);
117✔
1974

1975
        argParser->parse_args_without_binary_name(papszArgv);
117✔
1976

1977
        // Check for invalid options:
1978
        // -scaleaxes is not compatible with -array = "view"
1979
        // -subset is not compatible with -array = "view"
1980
        if (std::find(psOptions->aosArraySpec.cbegin(),
117✔
1981
                      psOptions->aosArraySpec.cend(),
117✔
1982
                      "view") != psOptions->aosArraySpec.cend())
351✔
1983
        {
1984
            if (!psOptions->aosScaleFactor.empty())
×
1985
            {
1986
                CPLError(CE_Failure, CPLE_NotSupported,
×
1987
                         "The -scaleaxes option is not compatible with the "
1988
                         "-array \"view\" option.");
1989
                return nullptr;
×
1990
            }
1991

1992
            if (!psOptions->aosSubset.empty())
×
1993
            {
1994
                CPLError(CE_Failure, CPLE_NotSupported,
×
1995
                         "The -subset option is not compatible with the -array "
1996
                         "\"view\" option.");
1997
                return nullptr;
×
1998
            }
1999
        }
2000
    }
2001
    catch (const std::exception &error)
×
2002
    {
2003
        CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
×
2004
        return nullptr;
×
2005
    }
2006

2007
    if (psOptionsForBinary)
117✔
2008
    {
2009
        // Note: bUpdate is apparently never changed by the command line options
2010
        psOptionsForBinary->bUpdate = psOptions->bUpdate;
3✔
2011
        if (!psOptions->osFormat.empty())
3✔
2012
            psOptionsForBinary->osFormat = psOptions->osFormat;
×
2013
    }
2014

2015
    return psOptions.release();
117✔
2016
}
2017

2018
/************************************************************************/
2019
/*                     GDALMultiDimTranslateOptionsFree()               */
2020
/************************************************************************/
2021

2022
/**
2023
 * Frees the GDALMultiDimTranslateOptions struct.
2024
 *
2025
 * @param psOptions the options struct for GDALMultiDimTranslate().
2026
 *
2027
 * @since GDAL 3.1
2028
 */
2029

2030
void GDALMultiDimTranslateOptionsFree(GDALMultiDimTranslateOptions *psOptions)
116✔
2031
{
2032
    delete psOptions;
116✔
2033
}
116✔
2034

2035
/************************************************************************/
2036
/*               GDALMultiDimTranslateOptionsSetProgress()              */
2037
/************************************************************************/
2038

2039
/**
2040
 * Set a progress function.
2041
 *
2042
 * @param psOptions the options struct for GDALMultiDimTranslate().
2043
 * @param pfnProgress the progress callback.
2044
 * @param pProgressData the user data for the progress callback.
2045
 *
2046
 * @since GDAL 3.1
2047
 */
2048

2049
void GDALMultiDimTranslateOptionsSetProgress(
13✔
2050
    GDALMultiDimTranslateOptions *psOptions, GDALProgressFunc pfnProgress,
2051
    void *pProgressData)
2052
{
2053
    psOptions->pfnProgress = pfnProgress;
13✔
2054
    psOptions->pProgressData = pProgressData;
13✔
2055
}
13✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc