Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalmdiminfo_lib.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL Utilities
4
 * Purpose:  Command line application to list info about a multidimensional
5
 *raster 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 "gdal_utils.h"
15
#include "gdal_utils_priv.h"
16
17
#include "cpl_enumerate.h"
18
#include "cpl_json.h"
19
#include "cpl_json_streaming_writer.h"
20
#include "gdal_priv.h"
21
#include "gdal_rat.h"
22
#include "gdalargumentparser.h"
23
24
#include <algorithm>
25
#include <limits>
26
#include <set>
27
28
static void DumpArray(const std::shared_ptr<GDALGroup> &rootGroup,
29
                      const std::shared_ptr<GDALMDArray> &array,
30
                      CPLJSonStreamingWriter &serializer,
31
                      const GDALMultiDimInfoOptions *psOptions,
32
                      std::set<std::string> &alreadyDumpedDimensions,
33
                      std::set<std::string> &alreadyDumpedArrays,
34
                      bool bOutputObjType, bool bOutputName,
35
                      bool bOutputOverviews);
36
37
/************************************************************************/
38
/*                       GDALMultiDimInfoOptions                        */
39
/************************************************************************/
40
41
struct GDALMultiDimInfoOptions
42
{
43
    bool bStdoutOutput = false;
44
    bool bSummary = false;
45
    bool bDetailed = false;
46
    bool bPretty = true;
47
    size_t nLimitValuesByDim = 0;
48
    CPLStringList aosArrayOptions{};
49
    std::string osArrayName{};
50
    bool bStats = false;
51
    std::string osFormat = "json";
52
};
53
54
/************************************************************************/
55
/*                           HasUniqueNames()                           */
56
/************************************************************************/
57
58
static bool HasUniqueNames(const std::vector<std::string> &oNames)
59
0
{
60
0
    std::set<std::string> oSetNames;
61
0
    for (const auto &subgroupName : oNames)
62
0
    {
63
0
        if (oSetNames.find(subgroupName) != oSetNames.end())
64
0
        {
65
0
            return false;
66
0
        }
67
0
        oSetNames.insert(subgroupName);
68
0
    }
69
0
    return true;
70
0
}
71
72
/************************************************************************/
73
/*                            DumpDataType()                            */
74
/************************************************************************/
75
76
static void DumpDataType(const GDALExtendedDataType &dt,
77
                         CPLJSonStreamingWriter &serializer)
78
0
{
79
0
    switch (dt.GetClass())
80
0
    {
81
0
        case GEDTC_STRING:
82
0
            serializer.Add("String");
83
0
            break;
84
85
0
        case GEDTC_NUMERIC:
86
0
        {
87
0
            auto poRAT = dt.GetRAT();
88
0
            if (poRAT)
89
0
            {
90
0
                auto objContext(serializer.MakeObjectContext());
91
0
                serializer.AddObjKey("name");
92
0
                serializer.Add(dt.GetName());
93
0
                serializer.AddObjKey("type");
94
0
                serializer.Add(GDALGetDataTypeName(dt.GetNumericDataType()));
95
0
                serializer.AddObjKey("attribute_table");
96
0
                auto arrayContext(serializer.MakeArrayContext());
97
0
                const int nRows = poRAT->GetRowCount();
98
0
                const int nCols = poRAT->GetColumnCount();
99
0
                for (int iRow = 0; iRow < nRows; ++iRow)
100
0
                {
101
0
                    auto obj2Context(serializer.MakeObjectContext());
102
0
                    for (int iCol = 0; iCol < nCols; ++iCol)
103
0
                    {
104
0
                        serializer.AddObjKey(poRAT->GetNameOfCol(iCol));
105
0
                        switch (poRAT->GetTypeOfCol(iCol))
106
0
                        {
107
0
                            case GFT_Integer:
108
0
                                serializer.Add(
109
0
                                    poRAT->GetValueAsInt(iRow, iCol));
110
0
                                break;
111
0
                            case GFT_Real:
112
0
                                serializer.Add(
113
0
                                    poRAT->GetValueAsDouble(iRow, iCol));
114
0
                                break;
115
0
                            case GFT_String:
116
0
                                serializer.Add(
117
0
                                    poRAT->GetValueAsString(iRow, iCol));
118
0
                                break;
119
0
                            case GFT_Boolean:
120
0
                                serializer.Add(
121
0
                                    poRAT->GetValueAsBoolean(iRow, iCol));
122
0
                                break;
123
0
                            case GFT_DateTime:
124
0
                            {
125
0
                                const auto sDateTime =
126
0
                                    poRAT->GetValueAsDateTime(iRow, iCol);
127
0
                                serializer.Add(
128
0
                                    GDALRasterAttributeTable::DateTimeToString(
129
0
                                        sDateTime));
130
0
                                break;
131
0
                            }
132
0
                            case GFT_WKBGeometry:
133
0
                            {
134
0
                                size_t nWKBSize = 0;
135
0
                                const GByte *pabyWKB =
136
0
                                    poRAT->GetValueAsWKBGeometry(iRow, iCol,
137
0
                                                                 nWKBSize);
138
0
                                std::string osWKT =
139
0
                                    GDALRasterAttributeTable::WKBGeometryToWKT(
140
0
                                        pabyWKB, nWKBSize);
141
0
                                if (osWKT.empty())
142
0
                                    serializer.AddNull();
143
0
                                else
144
0
                                    serializer.Add(osWKT);
145
0
                                break;
146
0
                            }
147
0
                        }
148
0
                    }
149
0
                }
150
0
            }
151
0
            else
152
0
            {
153
0
                serializer.Add(GDALGetDataTypeName(dt.GetNumericDataType()));
154
0
            }
155
0
            break;
156
0
        }
157
158
0
        case GEDTC_COMPOUND:
159
0
        {
160
0
            auto compoundContext(serializer.MakeObjectContext());
161
0
            serializer.AddObjKey("name");
162
0
            serializer.Add(dt.GetName());
163
0
            serializer.AddObjKey("size");
164
0
            serializer.Add(static_cast<unsigned>(dt.GetSize()));
165
0
            serializer.AddObjKey("components");
166
0
            const auto &components = dt.GetComponents();
167
0
            auto componentsContext(serializer.MakeArrayContext());
168
0
            for (const auto &comp : components)
169
0
            {
170
0
                auto compContext(serializer.MakeObjectContext());
171
0
                serializer.AddObjKey("name");
172
0
                serializer.Add(comp->GetName());
173
0
                serializer.AddObjKey("offset");
174
0
                serializer.Add(static_cast<unsigned>(comp->GetOffset()));
175
0
                serializer.AddObjKey("type");
176
0
                DumpDataType(comp->GetType(), serializer);
177
0
            }
178
0
            break;
179
0
        }
180
0
    }
181
0
}
182
183
/************************************************************************/
184
/*                             DumpValue()                              */
185
/************************************************************************/
186
187
template <typename T>
188
static void DumpValue(CPLJSonStreamingWriter &serializer, const void *bytes)
189
0
{
190
0
    T tmp;
191
0
    memcpy(&tmp, bytes, sizeof(T));
192
0
    serializer.Add(tmp);
193
0
}
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<unsigned char>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<signed char>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<short>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<unsigned short>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<int>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<unsigned int>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<long>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<unsigned long>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<cpl::Float16>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<float>(CPLJSonStreamingWriter&, void const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpValue<double>(CPLJSonStreamingWriter&, void const*)
194
195
/************************************************************************/
196
/*                          DumpComplexValue()                          */
197
/************************************************************************/
198
199
template <typename T>
200
static void DumpComplexValue(CPLJSonStreamingWriter &serializer,
201
                             const GByte *bytes)
202
0
{
203
0
    auto objectContext(serializer.MakeObjectContext());
204
0
    serializer.AddObjKey("real");
205
0
    DumpValue<T>(serializer, bytes);
206
0
    serializer.AddObjKey("imag");
207
0
    DumpValue<T>(serializer, bytes + sizeof(T));
208
0
}
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpComplexValue<short>(CPLJSonStreamingWriter&, unsigned char const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpComplexValue<int>(CPLJSonStreamingWriter&, unsigned char const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpComplexValue<cpl::Float16>(CPLJSonStreamingWriter&, unsigned char const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpComplexValue<float>(CPLJSonStreamingWriter&, unsigned char const*)
Unexecuted instantiation: gdalmdiminfo_lib.cpp:void DumpComplexValue<double>(CPLJSonStreamingWriter&, unsigned char const*)
209
210
/************************************************************************/
211
/*                             DumpValue()                              */
212
/************************************************************************/
213
214
static void DumpValue(CPLJSonStreamingWriter &serializer, const GByte *bytes,
215
                      const GDALDataType &eDT)
216
0
{
217
0
    switch (eDT)
218
0
    {
219
0
        case GDT_UInt8:
220
0
            DumpValue<GByte>(serializer, bytes);
221
0
            break;
222
0
        case GDT_Int8:
223
0
            DumpValue<GInt8>(serializer, bytes);
224
0
            break;
225
0
        case GDT_Int16:
226
0
            DumpValue<GInt16>(serializer, bytes);
227
0
            break;
228
0
        case GDT_UInt16:
229
0
            DumpValue<GUInt16>(serializer, bytes);
230
0
            break;
231
0
        case GDT_Int32:
232
0
            DumpValue<GInt32>(serializer, bytes);
233
0
            break;
234
0
        case GDT_UInt32:
235
0
            DumpValue<GUInt32>(serializer, bytes);
236
0
            break;
237
0
        case GDT_Int64:
238
0
            DumpValue<std::int64_t>(serializer, bytes);
239
0
            break;
240
0
        case GDT_UInt64:
241
0
            DumpValue<std::uint64_t>(serializer, bytes);
242
0
            break;
243
0
        case GDT_Float16:
244
0
            DumpValue<GFloat16>(serializer, bytes);
245
0
            break;
246
0
        case GDT_Float32:
247
0
            DumpValue<float>(serializer, bytes);
248
0
            break;
249
0
        case GDT_Float64:
250
0
            DumpValue<double>(serializer, bytes);
251
0
            break;
252
0
        case GDT_CInt16:
253
0
            DumpComplexValue<GInt16>(serializer, bytes);
254
0
            break;
255
0
        case GDT_CInt32:
256
0
            DumpComplexValue<GInt32>(serializer, bytes);
257
0
            break;
258
0
        case GDT_CFloat16:
259
0
            DumpComplexValue<GFloat16>(serializer, bytes);
260
0
            break;
261
0
        case GDT_CFloat32:
262
0
            DumpComplexValue<float>(serializer, bytes);
263
0
            break;
264
0
        case GDT_CFloat64:
265
0
            DumpComplexValue<double>(serializer, bytes);
266
0
            break;
267
0
        case GDT_Unknown:
268
0
        case GDT_TypeCount:
269
0
            CPLAssert(false);
270
0
            break;
271
0
    }
272
0
}
273
274
static void DumpValue(CPLJSonStreamingWriter &serializer, const GByte *values,
275
                      const GDALExtendedDataType &dt);
276
277
/************************************************************************/
278
/*                            DumpCompound()                            */
279
/************************************************************************/
280
281
static void DumpCompound(CPLJSonStreamingWriter &serializer,
282
                         const GByte *values, const GDALExtendedDataType &dt)
283
0
{
284
0
    CPLAssert(dt.GetClass() == GEDTC_COMPOUND);
285
0
    const auto &components = dt.GetComponents();
286
0
    auto objectContext(serializer.MakeObjectContext());
287
0
    for (const auto &comp : components)
288
0
    {
289
0
        serializer.AddObjKey(comp->GetName());
290
0
        DumpValue(serializer, values + comp->GetOffset(), comp->GetType());
291
0
    }
292
0
}
293
294
/************************************************************************/
295
/*                             DumpValue()                              */
296
/************************************************************************/
297
298
static void DumpValue(CPLJSonStreamingWriter &serializer, const GByte *values,
299
                      const GDALExtendedDataType &dt)
300
0
{
301
0
    switch (dt.GetClass())
302
0
    {
303
0
        case GEDTC_NUMERIC:
304
0
            DumpValue(serializer, values, dt.GetNumericDataType());
305
0
            break;
306
0
        case GEDTC_COMPOUND:
307
0
            DumpCompound(serializer, values, dt);
308
0
            break;
309
0
        case GEDTC_STRING:
310
0
        {
311
0
            const char *pszStr;
312
            // cppcheck-suppress pointerSize
313
0
            memcpy(&pszStr, values, sizeof(const char *));
314
0
            if (pszStr)
315
0
                serializer.Add(pszStr);
316
0
            else
317
0
                serializer.AddNull();
318
0
            break;
319
0
        }
320
0
    }
321
0
}
322
323
/************************************************************************/
324
/*                           SerializeJSON()                            */
325
/************************************************************************/
326
327
static void SerializeJSON(const CPLJSONObject &obj,
328
                          CPLJSonStreamingWriter &serializer)
329
0
{
330
0
    switch (obj.GetType())
331
0
    {
332
0
        case CPLJSONObject::Type::Unknown:
333
0
        {
334
0
            CPLAssert(false);
335
0
            break;
336
0
        }
337
338
0
        case CPLJSONObject::Type::Null:
339
0
        {
340
0
            serializer.AddNull();
341
0
            break;
342
0
        }
343
344
0
        case CPLJSONObject::Type::Object:
345
0
        {
346
0
            auto objectContext(serializer.MakeObjectContext());
347
0
            for (const auto &subobj : obj.GetChildren())
348
0
            {
349
0
                serializer.AddObjKey(subobj.GetName());
350
0
                SerializeJSON(subobj, serializer);
351
0
            }
352
0
            break;
353
0
        }
354
355
0
        case CPLJSONObject::Type::Array:
356
0
        {
357
0
            auto arrayContext(serializer.MakeArrayContext());
358
0
            const CPLJSONArray array = obj.ToArray();
359
0
            for (const auto &subobj : array)
360
0
            {
361
0
                SerializeJSON(subobj, serializer);
362
0
            }
363
0
            break;
364
0
        }
365
366
0
        case CPLJSONObject::Type::Boolean:
367
0
        {
368
0
            serializer.Add(obj.ToBool());
369
0
            break;
370
0
        }
371
372
0
        case CPLJSONObject::Type::String:
373
0
        {
374
0
            serializer.Add(obj.ToString());
375
0
            break;
376
0
        }
377
378
0
        case CPLJSONObject::Type::Integer:
379
0
        {
380
0
            serializer.Add(obj.ToInteger());
381
0
            break;
382
0
        }
383
384
0
        case CPLJSONObject::Type::Long:
385
0
        {
386
0
            serializer.Add(static_cast<int64_t>(obj.ToLong()));
387
0
            break;
388
0
        }
389
390
0
        case CPLJSONObject::Type::Double:
391
0
        {
392
0
            serializer.Add(obj.ToDouble());
393
0
            break;
394
0
        }
395
0
    }
396
0
}
397
398
/************************************************************************/
399
/*                           DumpAttrValue()                            */
400
/************************************************************************/
401
402
static void DumpAttrValue(const std::shared_ptr<GDALAttribute> &attr,
403
                          CPLJSonStreamingWriter &serializer)
404
0
{
405
0
    const auto &dt = attr->GetDataType();
406
0
    const size_t nEltCount(static_cast<size_t>(attr->GetTotalElementsCount()));
407
0
    switch (dt.GetClass())
408
0
    {
409
0
        case GEDTC_STRING:
410
0
        {
411
0
            if (nEltCount == 1)
412
0
            {
413
0
                const char *pszStr = attr->ReadAsString();
414
0
                if (pszStr)
415
0
                {
416
0
                    if (dt.GetSubType() == GEDTST_JSON)
417
0
                    {
418
0
                        CPLJSONDocument oDoc;
419
0
                        if (oDoc.LoadMemory(std::string(pszStr)))
420
0
                        {
421
0
                            SerializeJSON(oDoc.GetRoot(), serializer);
422
0
                        }
423
0
                        else
424
0
                        {
425
0
                            serializer.Add(pszStr);
426
0
                        }
427
0
                    }
428
0
                    else
429
0
                    {
430
0
                        serializer.Add(pszStr);
431
0
                    }
432
0
                }
433
0
                else
434
0
                {
435
0
                    serializer.AddNull();
436
0
                }
437
0
            }
438
0
            else
439
0
            {
440
0
                CPLStringList aosValues(attr->ReadAsStringArray());
441
0
                {
442
0
                    auto arrayContextValues(
443
0
                        serializer.MakeArrayContext(nEltCount < 10));
444
0
                    for (int i = 0; i < aosValues.size(); ++i)
445
0
                    {
446
0
                        serializer.Add(aosValues[i]);
447
0
                    }
448
0
                }
449
0
            }
450
0
            break;
451
0
        }
452
453
0
        case GEDTC_NUMERIC:
454
0
        {
455
0
            auto eDT = dt.GetNumericDataType();
456
0
            const auto rawValues(attr->ReadAsRaw());
457
0
            const GByte *bytePtr = rawValues.data();
458
0
            if (bytePtr)
459
0
            {
460
0
                const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
461
0
                if (nEltCount == 1)
462
0
                {
463
0
                    serializer.SetNewline(false);
464
0
                    DumpValue(serializer, rawValues.data(), eDT);
465
0
                    serializer.SetNewline(true);
466
0
                }
467
0
                else
468
0
                {
469
0
                    auto arrayContextValues(
470
0
                        serializer.MakeArrayContext(nEltCount < 10));
471
0
                    for (size_t i = 0; i < nEltCount; i++)
472
0
                    {
473
0
                        DumpValue(serializer, bytePtr, eDT);
474
0
                        bytePtr += nDTSize;
475
0
                    }
476
0
                }
477
0
            }
478
0
            else
479
0
            {
480
0
                serializer.AddNull();
481
0
            }
482
0
            break;
483
0
        }
484
485
0
        case GEDTC_COMPOUND:
486
0
        {
487
0
            auto rawValues(attr->ReadAsRaw());
488
0
            const GByte *bytePtr = rawValues.data();
489
0
            if (bytePtr)
490
0
            {
491
0
                if (nEltCount == 1)
492
0
                {
493
0
                    serializer.SetNewline(false);
494
0
                    DumpCompound(serializer, bytePtr, dt);
495
0
                    serializer.SetNewline(true);
496
0
                }
497
0
                else
498
0
                {
499
0
                    auto arrayContextValues(serializer.MakeArrayContext());
500
0
                    for (size_t i = 0; i < nEltCount; i++)
501
0
                    {
502
0
                        DumpCompound(serializer, bytePtr, dt);
503
0
                        bytePtr += dt.GetSize();
504
0
                    }
505
0
                }
506
0
            }
507
0
            else
508
0
            {
509
0
                serializer.AddNull();
510
0
            }
511
0
            break;
512
0
        }
513
0
    }
514
0
}
515
516
/************************************************************************/
517
/*                              DumpAttr()                              */
518
/************************************************************************/
519
520
static void DumpAttr(std::shared_ptr<GDALAttribute> attr,
521
                     CPLJSonStreamingWriter &serializer,
522
                     const GDALMultiDimInfoOptions *psOptions,
523
                     bool bOutputObjType, bool bOutputName)
524
0
{
525
0
    if (!bOutputObjType && !bOutputName && !psOptions->bDetailed)
526
0
    {
527
0
        DumpAttrValue(attr, serializer);
528
0
        return;
529
0
    }
530
531
0
    const auto &dt = attr->GetDataType();
532
0
    auto objectContext(serializer.MakeObjectContext());
533
0
    if (bOutputObjType)
534
0
    {
535
0
        serializer.AddObjKey("type");
536
0
        serializer.Add("attribute");
537
0
    }
538
0
    if (bOutputName)
539
0
    {
540
0
        serializer.AddObjKey("name");
541
0
        serializer.Add(attr->GetName());
542
0
    }
543
544
0
    if (psOptions->bDetailed)
545
0
    {
546
0
        serializer.AddObjKey("datatype");
547
0
        DumpDataType(dt, serializer);
548
549
0
        switch (dt.GetSubType())
550
0
        {
551
0
            case GEDTST_NONE:
552
0
                break;
553
0
            case GEDTST_JSON:
554
0
            {
555
0
                serializer.AddObjKey("subtype");
556
0
                serializer.Add("JSON");
557
0
                break;
558
0
            }
559
0
        }
560
561
0
        serializer.AddObjKey("value");
562
0
    }
563
564
0
    DumpAttrValue(attr, serializer);
565
0
}
566
567
/************************************************************************/
568
/*                             DumpAttrs()                              */
569
/************************************************************************/
570
571
static void DumpAttrs(const std::vector<std::shared_ptr<GDALAttribute>> &attrs,
572
                      CPLJSonStreamingWriter &serializer,
573
                      const GDALMultiDimInfoOptions *psOptions)
574
0
{
575
0
    std::vector<std::string> attributeNames;
576
0
    for (const auto &poAttr : attrs)
577
0
        attributeNames.emplace_back(poAttr->GetName());
578
0
    if (HasUniqueNames(attributeNames))
579
0
    {
580
0
        auto objectContext(serializer.MakeObjectContext());
581
0
        for (const auto &poAttr : attrs)
582
0
        {
583
0
            serializer.AddObjKey(poAttr->GetName());
584
0
            DumpAttr(poAttr, serializer, psOptions, false, false);
585
0
        }
586
0
    }
587
0
    else
588
0
    {
589
0
        auto arrayContext(serializer.MakeArrayContext());
590
0
        for (const auto &poAttr : attrs)
591
0
        {
592
0
            DumpAttr(poAttr, serializer, psOptions, false, true);
593
0
        }
594
0
    }
595
0
}
596
597
/************************************************************************/
598
/*                            DumpArrayRec()                            */
599
/************************************************************************/
600
601
static void DumpArrayRec(std::shared_ptr<GDALMDArray> array,
602
                         CPLJSonStreamingWriter &serializer, size_t nCurDim,
603
                         const std::vector<GUInt64> &dimSizes,
604
                         std::vector<GUInt64> &startIdx,
605
                         const GDALMultiDimInfoOptions *psOptions)
606
0
{
607
0
    do
608
0
    {
609
0
        auto arrayContext(serializer.MakeArrayContext());
610
0
        if (nCurDim + 1 == dimSizes.size())
611
0
        {
612
0
            const auto &dt(array->GetDataType());
613
0
            const auto nDTSize(dt.GetSize());
614
0
            const auto lambdaDumpValue =
615
0
                [&serializer, &dt, nDTSize](std::vector<GByte> &abyTmp,
616
0
                                            size_t nCount)
617
0
            {
618
0
                GByte *pabyPtr = &abyTmp[0];
619
0
                for (size_t i = 0; i < nCount; ++i)
620
0
                {
621
0
                    DumpValue(serializer, pabyPtr, dt);
622
0
                    dt.FreeDynamicMemory(pabyPtr);
623
0
                    pabyPtr += nDTSize;
624
0
                }
625
0
            };
626
627
0
            serializer.SetNewline(false);
628
0
            std::vector<size_t> count(dimSizes.size(), 1);
629
0
            if (psOptions->nLimitValuesByDim == 0 ||
630
0
                dimSizes.back() <= psOptions->nLimitValuesByDim)
631
0
            {
632
0
                const size_t nCount = static_cast<size_t>(dimSizes.back());
633
0
                if (nCount > 0)
634
0
                {
635
0
                    if (nCount != dimSizes.back() ||
636
0
                        nDTSize > std::numeric_limits<size_t>::max() / nCount)
637
0
                    {
638
0
                        serializer.Add("[too many values]");
639
0
                        break;
640
0
                    }
641
0
                    std::vector<GByte> abyTmp(nDTSize * nCount);
642
0
                    count.back() = nCount;
643
0
                    if (!array->Read(startIdx.data(), count.data(), nullptr,
644
0
                                     nullptr, dt, &abyTmp[0]))
645
0
                        break;
646
0
                    lambdaDumpValue(abyTmp, count.back());
647
0
                }
648
0
            }
649
0
            else
650
0
            {
651
0
                std::vector<GByte> abyTmp(
652
0
                    nDTSize * (psOptions->nLimitValuesByDim + 1) / 2);
653
0
                startIdx.back() = 0;
654
0
                size_t nStartCount = (psOptions->nLimitValuesByDim + 1) / 2;
655
0
                count.back() = nStartCount;
656
0
                if (!array->Read(startIdx.data(), count.data(), nullptr,
657
0
                                 nullptr, dt, &abyTmp[0]))
658
0
                    break;
659
0
                lambdaDumpValue(abyTmp, count.back());
660
0
                serializer.Add("[...]");
661
662
0
                count.back() = psOptions->nLimitValuesByDim / 2;
663
0
                if (count.back())
664
0
                {
665
0
                    startIdx.back() = dimSizes.back() - count.back();
666
0
                    if (!array->Read(startIdx.data(), count.data(), nullptr,
667
0
                                     nullptr, dt, &abyTmp[0]))
668
0
                        break;
669
0
                    lambdaDumpValue(abyTmp, count.back());
670
0
                }
671
0
            }
672
0
        }
673
0
        else
674
0
        {
675
0
            if (psOptions->nLimitValuesByDim == 0 ||
676
0
                dimSizes[nCurDim] <= psOptions->nLimitValuesByDim)
677
0
            {
678
0
                for (startIdx[nCurDim] = 0;
679
0
                     startIdx[nCurDim] < dimSizes[nCurDim]; ++startIdx[nCurDim])
680
0
                {
681
0
                    DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
682
0
                                 startIdx, psOptions);
683
0
                }
684
0
            }
685
0
            else
686
0
            {
687
0
                size_t nStartCount = (psOptions->nLimitValuesByDim + 1) / 2;
688
0
                for (startIdx[nCurDim] = 0; startIdx[nCurDim] < nStartCount;
689
0
                     ++startIdx[nCurDim])
690
0
                {
691
0
                    DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
692
0
                                 startIdx, psOptions);
693
0
                }
694
0
                serializer.Add("[...]");
695
0
                size_t nEndCount = psOptions->nLimitValuesByDim / 2;
696
0
                for (startIdx[nCurDim] = dimSizes[nCurDim] - nEndCount;
697
0
                     startIdx[nCurDim] < dimSizes[nCurDim]; ++startIdx[nCurDim])
698
0
                {
699
0
                    DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
700
0
                                 startIdx, psOptions);
701
0
                }
702
0
            }
703
0
        }
704
0
    } while (false);
705
0
    serializer.SetNewline(true);
706
0
}
707
708
/************************************************************************/
709
/*                           DumpDimensions()                           */
710
/************************************************************************/
711
712
static void
713
DumpDimensions(const std::shared_ptr<GDALGroup> &rootGroup,
714
               const std::vector<std::shared_ptr<GDALDimension>> &dims,
715
               CPLJSonStreamingWriter &serializer,
716
               const GDALMultiDimInfoOptions *psOptions,
717
               std::set<std::string> &alreadyDumpedDimensions)
718
0
{
719
0
    auto arrayContext(serializer.MakeArrayContext());
720
0
    for (const auto &dim : dims)
721
0
    {
722
0
        std::string osFullname(dim->GetFullName());
723
0
        if (alreadyDumpedDimensions.find(osFullname) !=
724
0
            alreadyDumpedDimensions.end())
725
0
        {
726
0
            serializer.Add(osFullname);
727
0
            continue;
728
0
        }
729
730
0
        auto dimObjectContext(serializer.MakeObjectContext());
731
0
        if (!osFullname.empty() && osFullname[0] == '/')
732
0
            alreadyDumpedDimensions.insert(osFullname);
733
734
0
        serializer.AddObjKey("name");
735
0
        serializer.Add(dim->GetName());
736
737
0
        serializer.AddObjKey("full_name");
738
0
        serializer.Add(osFullname);
739
740
0
        serializer.AddObjKey("size");
741
0
        serializer.Add(static_cast<std::uint64_t>(dim->GetSize()));
742
743
0
        const auto &type(dim->GetType());
744
0
        if (!type.empty())
745
0
        {
746
0
            serializer.AddObjKey("type");
747
0
            serializer.Add(type);
748
0
        }
749
750
0
        const auto &direction(dim->GetDirection());
751
0
        if (!direction.empty())
752
0
        {
753
0
            serializer.AddObjKey("direction");
754
0
            serializer.Add(direction);
755
0
        }
756
757
0
        auto poIndexingVariable(dim->GetIndexingVariable());
758
0
        if (poIndexingVariable)
759
0
        {
760
0
            serializer.AddObjKey("indexing_variable");
761
0
            bool isKnownFromRoot;
762
0
            {
763
                // For autotest/gdrivers/tiledb_multidim.py::test_tiledb_multidim_array_read_dim_label_and_spatial_ref
764
0
                CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
765
0
                isKnownFromRoot =
766
0
                    rootGroup->OpenMDArrayFromFullname(
767
0
                        poIndexingVariable->GetFullName()) != nullptr;
768
0
            }
769
0
            if (isKnownFromRoot)
770
0
            {
771
0
                serializer.Add(poIndexingVariable->GetFullName());
772
0
            }
773
0
            else
774
0
            {
775
0
                std::set<std::string> alreadyDumpedDimensionsLocal(
776
0
                    alreadyDumpedDimensions);
777
0
                alreadyDumpedDimensionsLocal.insert(std::move(osFullname));
778
0
                std::set<std::string> alreadyDumpedArrays;
779
780
0
                auto indexingVariableContext(serializer.MakeObjectContext());
781
0
                serializer.AddObjKey(poIndexingVariable->GetName());
782
0
                DumpArray(rootGroup, poIndexingVariable, serializer, psOptions,
783
0
                          alreadyDumpedDimensionsLocal, alreadyDumpedArrays,
784
0
                          /* bOutputObjType = */ false,
785
0
                          /* bOutputName = */ false,
786
0
                          /* bOutputOverviews = */ false);
787
0
            }
788
0
        }
789
0
    }
790
0
}
791
792
/************************************************************************/
793
/*                         DumpStructuralInfo()                         */
794
/************************************************************************/
795
796
static void DumpStructuralInfo(CSLConstList papszStructuralInfo,
797
                               CPLJSonStreamingWriter &serializer)
798
0
{
799
0
    auto objectContext(serializer.MakeObjectContext());
800
0
    int i = 1;
801
0
    for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(
802
0
             papszStructuralInfo, /* bReturnNullKeyIfNotNameValue = */ true))
803
0
    {
804
0
        if (pszKey)
805
0
        {
806
0
            serializer.AddObjKey(pszKey);
807
0
        }
808
0
        else
809
0
        {
810
0
            serializer.AddObjKey(CPLSPrintf("metadata_%d", i));
811
0
            ++i;
812
0
        }
813
0
        serializer.Add(pszValue);
814
0
    }
815
0
}
816
817
/************************************************************************/
818
/*                             DumpArray()                              */
819
/************************************************************************/
820
821
static void DumpArray(const std::shared_ptr<GDALGroup> &rootGroup,
822
                      const std::shared_ptr<GDALMDArray> &array,
823
                      CPLJSonStreamingWriter &serializer,
824
                      const GDALMultiDimInfoOptions *psOptions,
825
                      std::set<std::string> &alreadyDumpedDimensions,
826
                      std::set<std::string> &alreadyDumpedArrays,
827
                      bool bOutputObjType, bool bOutputName,
828
                      bool bOutputOverviews)
829
0
{
830
    // Protection against infinite recursion
831
0
    if (cpl::contains(alreadyDumpedArrays, array->GetFullName()))
832
0
    {
833
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s already visited",
834
0
                 array->GetFullName().c_str());
835
0
        return;
836
0
    }
837
0
    alreadyDumpedArrays.insert(array->GetFullName());
838
839
0
    auto objectContext(serializer.MakeObjectContext());
840
0
    if (bOutputObjType)
841
0
    {
842
0
        serializer.AddObjKey("type");
843
0
        serializer.Add("array");
844
0
    }
845
0
    if (bOutputName)
846
0
    {
847
0
        serializer.AddObjKey("name");
848
0
        serializer.Add(array->GetName());
849
0
    }
850
0
    else
851
0
    {
852
0
        serializer.AddObjKey("full_name");
853
0
        serializer.Add(array->GetFullName());
854
0
    }
855
856
0
    if (psOptions->bSummary)
857
0
        return;
858
859
0
    serializer.AddObjKey("datatype");
860
0
    const auto &dt(array->GetDataType());
861
0
    DumpDataType(dt, serializer);
862
863
0
    auto dims = array->GetDimensions();
864
0
    if (!dims.empty())
865
0
    {
866
0
        serializer.AddObjKey("dimensions");
867
0
        DumpDimensions(rootGroup, dims, serializer, psOptions,
868
0
                       alreadyDumpedDimensions);
869
870
0
        serializer.AddObjKey("dimension_size");
871
0
        auto arrayContext(serializer.MakeArrayContext());
872
0
        for (const auto &poDim : dims)
873
0
        {
874
0
            serializer.Add(static_cast<uint64_t>(poDim->GetSize()));
875
0
        }
876
0
    }
877
878
0
    bool hasNonNullBlockSize = false;
879
0
    const auto blockSize = array->GetBlockSize();
880
0
    for (auto v : blockSize)
881
0
    {
882
0
        if (v != 0)
883
0
        {
884
0
            hasNonNullBlockSize = true;
885
0
            break;
886
0
        }
887
0
    }
888
0
    if (hasNonNullBlockSize)
889
0
    {
890
0
        serializer.AddObjKey("block_size");
891
0
        auto arrayContext(serializer.MakeArrayContext());
892
0
        for (auto v : blockSize)
893
0
        {
894
0
            serializer.Add(static_cast<uint64_t>(v));
895
0
        }
896
0
    }
897
898
0
    CPLStringList aosOptions;
899
0
    if (psOptions->bDetailed)
900
0
        aosOptions.SetNameValue("SHOW_ALL", "YES");
901
0
    auto attrs = array->GetAttributes(aosOptions.List());
902
0
    if (!attrs.empty())
903
0
    {
904
0
        serializer.AddObjKey("attributes");
905
0
        DumpAttrs(attrs, serializer, psOptions);
906
0
    }
907
908
0
    const auto &unit = array->GetUnit();
909
0
    if (!unit.empty())
910
0
    {
911
0
        serializer.AddObjKey("unit");
912
0
        serializer.Add(unit);
913
0
    }
914
915
0
    auto nodata = array->GetRawNoDataValue();
916
0
    if (nodata)
917
0
    {
918
0
        serializer.AddObjKey("nodata_value");
919
0
        DumpValue(serializer, static_cast<const GByte *>(nodata), dt);
920
0
    }
921
922
0
    bool bValid = false;
923
0
    double dfOffset = array->GetOffset(&bValid);
924
0
    if (bValid)
925
0
    {
926
0
        serializer.AddObjKey("offset");
927
0
        serializer.Add(dfOffset);
928
0
    }
929
0
    double dfScale = array->GetScale(&bValid);
930
0
    if (bValid)
931
0
    {
932
0
        serializer.AddObjKey("scale");
933
0
        serializer.Add(dfScale);
934
0
    }
935
936
0
    auto srs = array->GetSpatialRef();
937
0
    if (srs)
938
0
    {
939
0
        char *pszWKT = nullptr;
940
0
        CPLStringList wktOptions;
941
0
        wktOptions.SetNameValue("FORMAT", "WKT2_2018");
942
0
        if (srs->exportToWkt(&pszWKT, wktOptions.List()) == OGRERR_NONE)
943
0
        {
944
0
            serializer.AddObjKey("srs");
945
0
            {
946
0
                auto srsContext(serializer.MakeObjectContext());
947
0
                serializer.AddObjKey("wkt");
948
0
                serializer.Add(pszWKT);
949
0
                serializer.AddObjKey("data_axis_to_srs_axis_mapping");
950
0
                {
951
0
                    auto dataAxisContext(serializer.MakeArrayContext(true));
952
0
                    auto mapping = srs->GetDataAxisToSRSAxisMapping();
953
0
                    for (const auto &axisNumber : mapping)
954
0
                        serializer.Add(axisNumber);
955
0
                }
956
0
            }
957
0
        }
958
0
        CPLFree(pszWKT);
959
0
    }
960
961
0
    auto papszStructuralInfo = array->GetStructuralInfo();
962
0
    if (papszStructuralInfo)
963
0
    {
964
0
        serializer.AddObjKey("structural_info");
965
0
        DumpStructuralInfo(papszStructuralInfo, serializer);
966
0
    }
967
968
0
    if (psOptions->bDetailed)
969
0
    {
970
0
        serializer.AddObjKey("values");
971
0
        if (dims.empty())
972
0
        {
973
0
            std::vector<GByte> abyTmp(dt.GetSize());
974
0
            array->Read(nullptr, nullptr, nullptr, nullptr, dt, &abyTmp[0]);
975
0
            DumpValue(serializer, &abyTmp[0], dt);
976
0
        }
977
0
        else
978
0
        {
979
0
            std::vector<GUInt64> startIdx(dims.size());
980
0
            std::vector<GUInt64> dimSizes;
981
0
            for (const auto &dim : dims)
982
0
                dimSizes.emplace_back(dim->GetSize());
983
0
            DumpArrayRec(array, serializer, 0, dimSizes, startIdx, psOptions);
984
0
        }
985
0
    }
986
987
0
    if (psOptions->bStats)
988
0
    {
989
0
        double dfMin = 0.0;
990
0
        double dfMax = 0.0;
991
0
        double dfMean = 0.0;
992
0
        double dfStdDev = 0.0;
993
0
        GUInt64 nValidCount = 0;
994
0
        if (array->GetStatistics(false, true, &dfMin, &dfMax, &dfMean,
995
0
                                 &dfStdDev, &nValidCount, nullptr,
996
0
                                 nullptr) == CE_None)
997
0
        {
998
0
            serializer.AddObjKey("statistics");
999
0
            auto statContext(serializer.MakeObjectContext());
1000
0
            if (nValidCount > 0)
1001
0
            {
1002
0
                serializer.AddObjKey("min");
1003
0
                serializer.Add(dfMin);
1004
1005
0
                serializer.AddObjKey("max");
1006
0
                serializer.Add(dfMax);
1007
1008
0
                serializer.AddObjKey("mean");
1009
0
                serializer.Add(dfMean);
1010
1011
0
                serializer.AddObjKey("stddev");
1012
0
                serializer.Add(dfStdDev);
1013
0
            }
1014
1015
0
            serializer.AddObjKey("valid_sample_count");
1016
0
            serializer.Add(static_cast<std::uint64_t>(nValidCount));
1017
0
        }
1018
0
    }
1019
1020
0
    if (bOutputOverviews)
1021
0
    {
1022
0
        const int nOverviews = array->GetOverviewCount();
1023
0
        if (nOverviews > 0)
1024
0
        {
1025
0
            serializer.AddObjKey("overviews");
1026
0
            auto overviewsContext(serializer.MakeArrayContext());
1027
0
            for (int i = 0; i < nOverviews; ++i)
1028
0
            {
1029
0
                if (auto poOvrArray = array->GetOverview(i))
1030
0
                {
1031
0
                    bool bIsStandalone = false;
1032
0
                    {
1033
0
                        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1034
0
                        bIsStandalone =
1035
0
                            rootGroup->OpenMDArrayFromFullname(
1036
0
                                poOvrArray->GetFullName()) == nullptr;
1037
0
                    }
1038
0
                    if (bIsStandalone)
1039
0
                    {
1040
0
                        DumpArray(rootGroup, poOvrArray, serializer, psOptions,
1041
0
                                  alreadyDumpedDimensions, alreadyDumpedArrays,
1042
0
                                  bOutputObjType, bOutputName,
1043
0
                                  bOutputOverviews);
1044
0
                    }
1045
0
                    else
1046
0
                    {
1047
0
                        serializer.Add(poOvrArray->GetFullName());
1048
0
                    }
1049
0
                }
1050
0
            }
1051
0
        }
1052
0
    }
1053
0
}
1054
1055
/************************************************************************/
1056
/*                             DumpArrays()                             */
1057
/************************************************************************/
1058
1059
static void DumpArrays(const std::shared_ptr<GDALGroup> &rootGroup,
1060
                       const std::shared_ptr<GDALGroup> &group,
1061
                       const std::vector<std::string> &arrayNames,
1062
                       CPLJSonStreamingWriter &serializer,
1063
                       const GDALMultiDimInfoOptions *psOptions,
1064
                       std::set<std::string> &alreadyDumpedDimensions,
1065
                       std::set<std::string> &alreadyDumpedArrays)
1066
0
{
1067
0
    std::set<std::string> oSetNames;
1068
0
    auto objectContext(serializer.MakeObjectContext());
1069
0
    for (const auto &name : arrayNames)
1070
0
    {
1071
0
        if (oSetNames.find(name) != oSetNames.end())
1072
0
            continue;  // should not happen on well behaved drivers
1073
0
        oSetNames.insert(name);
1074
0
        auto array = group->OpenMDArray(name);
1075
0
        if (array)
1076
0
        {
1077
0
            serializer.AddObjKey(array->GetName());
1078
0
            DumpArray(rootGroup, array, serializer, psOptions,
1079
0
                      alreadyDumpedDimensions, alreadyDumpedArrays, false,
1080
0
                      false, /* bOutputOverviews = */ true);
1081
0
        }
1082
0
    }
1083
0
}
1084
1085
/************************************************************************/
1086
/*                             DumpGroup()                              */
1087
/************************************************************************/
1088
1089
static void DumpGroup(const std::shared_ptr<GDALGroup> &rootGroup,
1090
                      const std::shared_ptr<GDALGroup> &group,
1091
                      const char *pszDriverName,
1092
                      CPLJSonStreamingWriter &serializer,
1093
                      const GDALMultiDimInfoOptions *psOptions,
1094
                      std::set<std::string> &alreadyDumpedDimensions,
1095
                      std::set<std::string> &alreadyDumpedArrays,
1096
                      bool bOutputObjType, bool bOutputName)
1097
0
{
1098
0
    auto objectContext(serializer.MakeObjectContext());
1099
0
    if (bOutputObjType)
1100
0
    {
1101
0
        serializer.AddObjKey("type");
1102
0
        serializer.Add("group");
1103
0
    }
1104
0
    if (pszDriverName)
1105
0
    {
1106
0
        serializer.AddObjKey("driver");
1107
0
        serializer.Add(pszDriverName);
1108
0
    }
1109
0
    if (bOutputName)
1110
0
    {
1111
0
        serializer.AddObjKey("name");
1112
0
        serializer.Add(group->GetName());
1113
1114
        // If the root group is not actually the root, print its full path
1115
0
        if (pszDriverName != nullptr && group->GetName() != "/")
1116
0
        {
1117
0
            serializer.AddObjKey("full_name");
1118
0
            serializer.Add(group->GetFullName());
1119
0
        }
1120
0
    }
1121
0
    else if (psOptions->bSummary)
1122
0
    {
1123
0
        serializer.AddObjKey("full_name");
1124
0
        serializer.Add(group->GetFullName());
1125
0
    }
1126
1127
0
    CPLStringList aosOptionsGetAttr;
1128
0
    if (psOptions->bDetailed)
1129
0
        aosOptionsGetAttr.SetNameValue("SHOW_ALL", "YES");
1130
0
    auto attrs = group->GetAttributes(aosOptionsGetAttr.List());
1131
0
    if (!psOptions->bSummary && !attrs.empty())
1132
0
    {
1133
0
        serializer.AddObjKey("attributes");
1134
0
        DumpAttrs(attrs, serializer, psOptions);
1135
0
    }
1136
1137
0
    auto dims = group->GetDimensions();
1138
0
    if (!psOptions->bSummary && !dims.empty())
1139
0
    {
1140
0
        serializer.AddObjKey("dimensions");
1141
0
        DumpDimensions(rootGroup, dims, serializer, psOptions,
1142
0
                       alreadyDumpedDimensions);
1143
0
    }
1144
1145
0
    const auto &types = group->GetDataTypes();
1146
0
    if (!psOptions->bSummary && !types.empty())
1147
0
    {
1148
0
        serializer.AddObjKey("datatypes");
1149
0
        auto arrayContext(serializer.MakeArrayContext());
1150
0
        for (const auto &dt : types)
1151
0
        {
1152
0
            DumpDataType(*(dt.get()), serializer);
1153
0
        }
1154
0
    }
1155
1156
0
    CPLStringList aosOptionsGetArray(psOptions->aosArrayOptions);
1157
0
    if (psOptions->bDetailed)
1158
0
        aosOptionsGetArray.SetNameValue("SHOW_ALL", "YES");
1159
0
    auto arrayNames = group->GetMDArrayNames(aosOptionsGetArray.List());
1160
0
    if (!arrayNames.empty())
1161
0
    {
1162
0
        serializer.AddObjKey("arrays");
1163
0
        DumpArrays(rootGroup, group, arrayNames, serializer, psOptions,
1164
0
                   alreadyDumpedDimensions, alreadyDumpedArrays);
1165
0
    }
1166
1167
0
    auto papszStructuralInfo = group->GetStructuralInfo();
1168
0
    if (!psOptions->bSummary && papszStructuralInfo)
1169
0
    {
1170
0
        serializer.AddObjKey("structural_info");
1171
0
        DumpStructuralInfo(papszStructuralInfo, serializer);
1172
0
    }
1173
1174
0
    auto subgroupNames = group->GetGroupNames();
1175
0
    if (!subgroupNames.empty())
1176
0
    {
1177
0
        serializer.AddObjKey("groups");
1178
0
        if (HasUniqueNames(subgroupNames))
1179
0
        {
1180
0
            auto groupContext(serializer.MakeObjectContext());
1181
0
            for (const auto &subgroupName : subgroupNames)
1182
0
            {
1183
0
                auto subgroup = group->OpenGroup(subgroupName);
1184
0
                if (subgroup)
1185
0
                {
1186
0
                    serializer.AddObjKey(subgroupName);
1187
0
                    DumpGroup(rootGroup, subgroup, nullptr, serializer,
1188
0
                              psOptions, alreadyDumpedDimensions,
1189
0
                              alreadyDumpedArrays, false, false);
1190
0
                }
1191
0
            }
1192
0
        }
1193
0
        else
1194
0
        {
1195
0
            auto arrayContext(serializer.MakeArrayContext());
1196
0
            for (const auto &subgroupName : subgroupNames)
1197
0
            {
1198
0
                auto subgroup = group->OpenGroup(subgroupName);
1199
0
                if (subgroup)
1200
0
                {
1201
0
                    DumpGroup(rootGroup, subgroup, nullptr, serializer,
1202
0
                              psOptions, alreadyDumpedDimensions,
1203
0
                              alreadyDumpedArrays, false, true);
1204
0
                }
1205
0
            }
1206
0
        }
1207
0
    }
1208
0
}
1209
1210
/************************************************************************/
1211
/*                     GDALMultiDimTextOutputDumper                     */
1212
/************************************************************************/
1213
1214
using TableType = std::vector<std::vector<std::string>>;
1215
1216
struct GDALMultiDimTextOutputDumper
1217
{
1218
    const GDALMultiDimInfoOptions &m_sOptions;
1219
    std::string m_osOutput{};
1220
1221
    explicit GDALMultiDimTextOutputDumper(
1222
        const GDALMultiDimInfoOptions *psOptions)
1223
0
        : m_sOptions(*psOptions)
1224
0
    {
1225
0
    }
1226
1227
    void AddLine(const std::string &osLine = std::string())
1228
0
    {
1229
0
        if (m_sOptions.bStdoutOutput)
1230
0
        {
1231
0
            printf("%s\n", osLine.c_str());
1232
0
        }
1233
0
        else
1234
0
        {
1235
0
            m_osOutput += osLine;
1236
0
            m_osOutput += '\n';
1237
0
        }
1238
0
    }
1239
1240
    void DumpTable(const TableType &lines, int nIndentSpaces = 2,
1241
                   const std::vector<size_t> &anColSizeIn = {},
1242
                   bool headerLineSeparator = true,
1243
                   bool bLeftPadNumbers = true);
1244
1245
    void
1246
    DumpAttributes(const std::vector<std::shared_ptr<GDALAttribute>> &apoAttrs,
1247
                   int nIndentSpaces);
1248
1249
    void DumpStructuralInfo(CSLConstList papszStructuralInfo,
1250
                            int nIndentSpaces);
1251
1252
    void DumpArray(const std::shared_ptr<GDALMDArray> &poArray);
1253
1254
    std::set<std::string>
1255
    DumpDimensionsSummary(const std::shared_ptr<GDALGroup> &group);
1256
1257
    void DumpArraysSummary(
1258
        const std::shared_ptr<GDALGroup> &group,
1259
        const std::vector<std::shared_ptr<GDALMDArray>> &apoArrays);
1260
1261
    void DrumpGroupsSummary(const std::shared_ptr<GDALGroup> &group);
1262
};
1263
1264
/************************************************************************/
1265
/*                             DumpTable()                              */
1266
/************************************************************************/
1267
1268
void GDALMultiDimTextOutputDumper::DumpTable(
1269
    const TableType &lines, int nIndentSpaces,
1270
    const std::vector<size_t> &anColSizeIn, bool headerLineSeparator,
1271
    bool bLeftPadNumbers)
1272
0
{
1273
    // Compute column max size if needed
1274
0
    std::vector<size_t> anColSizeTmp;
1275
0
    if (anColSizeIn.empty())
1276
0
    {
1277
0
        for (const auto &line : lines)
1278
0
        {
1279
0
            if (line.size() > anColSizeTmp.size())
1280
0
                anColSizeTmp.resize(line.size());
1281
0
            for (const auto [idxCol, col] : cpl::enumerate(line))
1282
0
            {
1283
0
                anColSizeTmp[idxCol] =
1284
0
                    std::max(anColSizeTmp[idxCol], col.size());
1285
0
            }
1286
0
        }
1287
0
    }
1288
0
    const auto &anColSize = anColSizeIn.empty() ? anColSizeTmp : anColSizeIn;
1289
1290
    // Check which columns are numeric-only
1291
0
    std::vector<bool> abIsNumbersOnly(anColSize.size(), true);
1292
0
    for (const auto [idxLine, line] : cpl::enumerate(lines))
1293
0
    {
1294
0
        CPLAssert(abIsNumbersOnly.size() >= line.size());
1295
0
        if (!headerLineSeparator || idxLine > 0)
1296
0
        {
1297
0
            for (const auto [idxCol, col] : cpl::enumerate(line))
1298
0
            {
1299
0
                abIsNumbersOnly[idxCol] =
1300
0
                    abIsNumbersOnly[idxCol] &&
1301
0
                    CPLGetValueType(col.c_str()) != CPL_VALUE_STRING;
1302
0
            }
1303
0
        }
1304
0
    }
1305
1306
    // Now actually print table
1307
0
    for (const auto [idxLine, line] : cpl::enumerate(lines))
1308
0
    {
1309
0
        CPLAssert(anColSize.size() >= line.size());
1310
0
        std::string osFormattedLine(nIndentSpaces, ' ');
1311
0
        for (const auto [idxCol, col] : cpl::enumerate(line))
1312
0
        {
1313
0
            if (idxCol > 0)
1314
0
                osFormattedLine += "  ";
1315
0
            if (idxLine == 0 && headerLineSeparator)
1316
0
            {
1317
0
                const size_t nLeftPadding =
1318
0
                    (anColSize[idxCol] - col.size()) / 2;
1319
0
                osFormattedLine += std::string(nLeftPadding, ' ');
1320
0
                osFormattedLine += col;
1321
0
                osFormattedLine += std::string(
1322
0
                    anColSize[idxCol] - col.size() - nLeftPadding, ' ');
1323
0
            }
1324
0
            else if (!bLeftPadNumbers || !abIsNumbersOnly[idxCol])
1325
0
            {
1326
0
                osFormattedLine += col;
1327
0
                osFormattedLine +=
1328
0
                    std::string(anColSize[idxCol] - col.size(), ' ');
1329
0
            }
1330
0
            else
1331
0
            {
1332
0
                osFormattedLine +=
1333
0
                    std::string(anColSize[idxCol] - col.size(), ' ');
1334
0
                osFormattedLine += col;
1335
0
            }
1336
0
        }
1337
0
        AddLine(osFormattedLine);
1338
1339
0
        if (idxLine == 0 && headerLineSeparator)
1340
0
        {
1341
0
            osFormattedLine = std::string(nIndentSpaces, ' ');
1342
0
            for (size_t idxCol = 0; idxCol < line.size(); ++idxCol)
1343
0
            {
1344
0
                if (idxCol > 0)
1345
0
                    osFormattedLine += "  ";
1346
0
                osFormattedLine += std::string(anColSize[idxCol], '-');
1347
0
            }
1348
0
            AddLine(osFormattedLine);
1349
0
        }
1350
0
    }
1351
0
}
1352
1353
/************************************************************************/
1354
/*                            TypeToString()                            */
1355
/************************************************************************/
1356
1357
static std::string TypeToString(const GDALExtendedDataType &dt,
1358
                                bool emitCompoundName = true)
1359
0
{
1360
0
    std::string ret;
1361
0
    switch (dt.GetClass())
1362
0
    {
1363
0
        case GEDTC_STRING:
1364
0
            ret = "String";
1365
0
            break;
1366
1367
0
        case GEDTC_NUMERIC:
1368
0
            ret = GDALGetDataTypeName(dt.GetNumericDataType());
1369
0
            break;
1370
1371
0
        case GEDTC_COMPOUND:
1372
0
        {
1373
0
            if (emitCompoundName && !dt.GetName().empty())
1374
0
            {
1375
0
                ret = dt.GetName();
1376
0
                ret += ": ";
1377
0
            }
1378
0
            ret += '{';
1379
0
            bool firstComp = true;
1380
0
            const auto &components = dt.GetComponents();
1381
0
            for (const auto &comp : components)
1382
0
            {
1383
0
                if (!firstComp)
1384
0
                    ret += ", ";
1385
0
                firstComp = false;
1386
0
                ret += comp->GetName();
1387
0
                ret += ": ";
1388
0
                ret += TypeToString(comp->GetType(), false);
1389
0
            }
1390
0
            ret += '}';
1391
0
            break;
1392
0
        }
1393
0
    }
1394
0
    return ret;
1395
0
}
1396
1397
/************************************************************************/
1398
/*                           DumpAttributes()                           */
1399
/************************************************************************/
1400
1401
void GDALMultiDimTextOutputDumper::DumpAttributes(
1402
    const std::vector<std::shared_ptr<GDALAttribute>> &apoAttrs,
1403
    int nIndentSpaces)
1404
0
{
1405
0
    if (!apoAttrs.empty())
1406
0
    {
1407
0
        AddLine();
1408
0
        AddLine(std::string(nIndentSpaces, ' ').append("Attributes:"));
1409
0
        TableType attrs;
1410
0
        attrs.push_back({"Name", "Type", "Value"});
1411
1412
0
        for (const auto &poAttr : apoAttrs)
1413
0
        {
1414
0
            CPLJSonStreamingWriter serializer(nullptr, nullptr);
1415
0
            serializer.SetPrettyFormatting(false);
1416
0
            DumpAttrValue(poAttr, serializer);
1417
0
            std::string osAttrVal = serializer.GetString();
1418
0
            constexpr size_t MAX_COLS = 80;
1419
0
            if (osAttrVal.size() <= MAX_COLS)
1420
0
            {
1421
0
                attrs.push_back({poAttr->GetName(),
1422
0
                                 TypeToString(poAttr->GetDataType()),
1423
0
                                 std::move(osAttrVal)});
1424
0
            }
1425
0
            else
1426
0
            {
1427
0
                bool bFirstLineAttr = true;
1428
0
                while (osAttrVal.size() > MAX_COLS)
1429
0
                {
1430
0
                    auto nLastBreak = osAttrVal.find_last_of(" ,\n", MAX_COLS);
1431
0
                    if (nLastBreak == std::string::npos)
1432
0
                        nLastBreak = osAttrVal.find_first_of(" ,\n");
1433
0
                    if (nLastBreak != std::string::npos)
1434
0
                    {
1435
0
                        auto osVal = osAttrVal.substr(0, nLastBreak);
1436
0
                        if (osAttrVal[nLastBreak] != ' ' &&
1437
0
                            osAttrVal[nLastBreak] != '\n')
1438
0
                            osVal += osAttrVal[nLastBreak];
1439
0
                        attrs.push_back(
1440
0
                            {bFirstLineAttr ? poAttr->GetName() : std::string(),
1441
0
                             bFirstLineAttr
1442
0
                                 ? TypeToString(poAttr->GetDataType())
1443
0
                                 : std::string(),
1444
0
                             std::move(osVal)});
1445
0
                        osAttrVal = osAttrVal.substr(nLastBreak + 1);
1446
0
                    }
1447
0
                    else
1448
0
                    {
1449
0
                        break;
1450
0
                    }
1451
0
                    bFirstLineAttr = false;
1452
0
                }
1453
0
                attrs.push_back(
1454
0
                    {bFirstLineAttr ? poAttr->GetName() : std::string(),
1455
0
                     bFirstLineAttr ? TypeToString(poAttr->GetDataType())
1456
0
                                    : std::string(),
1457
0
                     std::move(osAttrVal)});
1458
0
            }
1459
0
        }
1460
0
        DumpTable(attrs, nIndentSpaces + 2);
1461
0
    }
1462
0
}
1463
1464
/************************************************************************/
1465
/*                         DimsToShapeString()                          */
1466
/************************************************************************/
1467
1468
static std::string
1469
DimsToShapeString(const std::vector<std::shared_ptr<GDALDimension>> &dims)
1470
0
{
1471
0
    std::string s("[");
1472
0
    for (const auto &poDim : dims)
1473
0
    {
1474
0
        if (s.size() > 1)
1475
0
            s += ", ";
1476
0
        s += std::to_string(poDim->GetSize());
1477
0
    }
1478
0
    s += ']';
1479
0
    return s;
1480
0
}
1481
1482
/************************************************************************/
1483
/*                            DimsToString()                            */
1484
/************************************************************************/
1485
1486
static std::string
1487
DimsToString(const std::vector<std::shared_ptr<GDALDimension>> &dims,
1488
             const std::string &osSameDimGroup)
1489
0
{
1490
0
    std::string s("(");
1491
0
    for (const auto &poDim : dims)
1492
0
    {
1493
0
        if (s.size() > 1)
1494
0
            s += ", ";
1495
0
        else if (!osSameDimGroup.empty())
1496
0
        {
1497
0
            s += osSameDimGroup;
1498
0
            s += '/';
1499
0
        }
1500
0
        const std::string &osFullname = poDim->GetFullName();
1501
0
        if (!osFullname.empty())
1502
0
        {
1503
0
            s += osSameDimGroup.empty()
1504
0
                     ? osFullname
1505
0
                     : osFullname.substr(osSameDimGroup.size() + 1);
1506
0
        }
1507
0
        else
1508
0
        {
1509
0
            s += "(unnamed)";
1510
0
        }
1511
0
    }
1512
0
    s += ')';
1513
0
    return s;
1514
0
}
1515
1516
/************************************************************************/
1517
/*                         DimsToSameDimGroup()                         */
1518
/************************************************************************/
1519
1520
/** Return the prefix shared by all dimensions if there is one, or empty string
1521
 * otherwise.
1522
 */
1523
static std::string
1524
DimsToSameDimGroup(const std::vector<std::shared_ptr<GDALDimension>> &dims)
1525
0
{
1526
0
    std::string osSameDimGroup;
1527
0
    for (const auto &poDim : dims)
1528
0
    {
1529
0
        std::string osDimGroup = poDim->GetFullName();
1530
0
        const auto nPos = osDimGroup.rfind('/');
1531
0
        if (nPos != std::string::npos)
1532
0
        {
1533
0
            osDimGroup.resize(nPos);
1534
0
            if (osSameDimGroup.empty())
1535
0
            {
1536
0
                osSameDimGroup = std::move(osDimGroup);
1537
0
            }
1538
0
            else if (osSameDimGroup != osDimGroup)
1539
0
            {
1540
0
                return {};
1541
0
            }
1542
0
        }
1543
0
        else
1544
0
        {
1545
0
            return {};
1546
0
        }
1547
0
    }
1548
0
    return osSameDimGroup;
1549
0
}
1550
1551
/************************************************************************/
1552
/*                         BlockSizeToString()                          */
1553
/************************************************************************/
1554
1555
template <class T>
1556
static std::string BlockSizeToString(const std::vector<T> &anBlockSize,
1557
                                     bool *pbAllZero = nullptr)
1558
0
{
1559
0
    std::string s("[");
1560
0
    if (pbAllZero)
1561
0
        *pbAllZero = true;
1562
0
    for (const auto nSize : anBlockSize)
1563
0
    {
1564
0
        if (s.size() > 1)
1565
0
            s += ", ";
1566
0
        s += std::to_string(nSize);
1567
0
        if (pbAllZero)
1568
0
            *pbAllZero = *pbAllZero && nSize == 0;
1569
0
    }
1570
0
    s += ']';
1571
0
    return s;
1572
0
}
1573
1574
/************************************************************************/
1575
/*                    WriteToStdoutWithSpaceIndent()                    */
1576
/************************************************************************/
1577
1578
static void WriteToStdoutWithSpaceIndent(const char *pszText, void *pUserData)
1579
0
{
1580
0
    const int *pnIndent = static_cast<const int *>(pUserData);
1581
0
    printf("%s", pszText);
1582
0
    if (pszText[0] == '\n')
1583
0
        printf("%s", std::string(*pnIndent, ' ').c_str());
1584
0
}
1585
1586
/************************************************************************/
1587
/*          GDALMultiDimTextOutputDumper::DumpStructuralInfo()          */
1588
/************************************************************************/
1589
1590
void GDALMultiDimTextOutputDumper::DumpStructuralInfo(
1591
    CSLConstList papszStructuralInfo, int nIndentSpaces)
1592
0
{
1593
0
    AddLine();
1594
0
    AddLine(std::string(nIndentSpaces, ' ').append("Structural metadata:"));
1595
1596
0
    TableType info;
1597
1598
0
    int i = 1;
1599
0
    for (const auto &[pszKey, pszValue] :
1600
0
         cpl::IterateNameValue(papszStructuralInfo,
1601
0
                               /* bReturnNullKeyIfNotNameValue = */ true))
1602
0
    {
1603
0
        std::vector<std::string> line;
1604
0
        if (pszKey)
1605
0
        {
1606
0
            line.push_back(pszKey);
1607
0
        }
1608
0
        else
1609
0
        {
1610
0
            line.push_back(CPLSPrintf("metadata_%d", i));
1611
0
            ++i;
1612
0
        }
1613
0
        line.push_back(pszValue);
1614
0
        info.push_back(std::move(line));
1615
0
    }
1616
0
    DumpTable(info, nIndentSpaces + 2, {}, false);
1617
0
}
1618
1619
/************************************************************************/
1620
/*              GDALMultiDimTextOutputDumper::DumpArray()               */
1621
/************************************************************************/
1622
1623
void GDALMultiDimTextOutputDumper::DumpArray(
1624
    const std::shared_ptr<GDALMDArray> &poArray)
1625
0
{
1626
0
    AddLine();
1627
0
    AddLine("  - " + poArray->GetFullName() + ":");
1628
1629
0
    constexpr int INDENT_LEVEL = 6;
1630
1631
0
    TableType props;
1632
0
    const auto &dims = poArray->GetDimensions();
1633
0
    props.push_back(
1634
0
        {"Dimensions:", DimsToString(dims, DimsToSameDimGroup(dims))});
1635
0
    props.push_back({"Shape:", DimsToShapeString(dims)});
1636
0
    bool bAllZero = true;
1637
0
    std::string osChunkSize =
1638
0
        BlockSizeToString(poArray->GetBlockSize(), &bAllZero);
1639
0
    if (!bAllZero)
1640
0
        props.push_back({"Chunk size:", std::move(osChunkSize)});
1641
1642
0
    const auto &dt = poArray->GetDataType();
1643
0
    props.push_back({"Type:", TypeToString(dt)});
1644
0
    const std::string &osUnit = poArray->GetUnit();
1645
0
    if (!osUnit.empty())
1646
0
        props.push_back({"Unit:", osUnit});
1647
1648
0
    if (const auto nodata = poArray->GetRawNoDataValue())
1649
0
    {
1650
0
        CPLJSonStreamingWriter serializer(nullptr, nullptr);
1651
0
        serializer.SetPrettyFormatting(false);
1652
0
        DumpValue(serializer, static_cast<const GByte *>(nodata), dt);
1653
0
        props.push_back({"Nodata value:", serializer.GetString()});
1654
0
    }
1655
0
    DumpTable(props, INDENT_LEVEL, {},
1656
0
              /* headerLineSeparator =  */ false,
1657
0
              /* bLeftPadNumbers = */ false);
1658
1659
0
    DumpAttributes(poArray->GetAttributes(), INDENT_LEVEL);
1660
1661
0
    if (const auto poSRS = poArray->GetSpatialRef())
1662
0
    {
1663
0
        AddLine();
1664
0
        std::string osCRS;
1665
0
        EmitTextDisplayOfCRS(poSRS.get(), "AUTO", "Coordinate Reference System",
1666
0
                             [&osCRS](const std::string &s) { osCRS += s; });
1667
0
        const CPLStringList aosLines(
1668
0
            CSLTokenizeString2(osCRS.c_str(), "\n", 0));
1669
0
        for (const char *pszLine : aosLines)
1670
0
            AddLine(std::string(INDENT_LEVEL, ' ').append(pszLine));
1671
0
    }
1672
1673
0
    if (CSLConstList papszStructuralInfo = poArray->GetStructuralInfo())
1674
0
    {
1675
0
        DumpStructuralInfo(papszStructuralInfo, INDENT_LEVEL);
1676
0
    }
1677
1678
0
    if (m_sOptions.bStats)
1679
0
    {
1680
0
        double dfMin = 0.0;
1681
0
        double dfMax = 0.0;
1682
0
        double dfMean = 0.0;
1683
0
        double dfStdDev = 0.0;
1684
0
        GUInt64 nValidCount = 0;
1685
0
        if (poArray->GetStatistics(false, true, &dfMin, &dfMax, &dfMean,
1686
0
                                   &dfStdDev, &nValidCount, nullptr,
1687
0
                                   nullptr) == CE_None)
1688
0
        {
1689
0
            AddLine();
1690
0
            AddLine(std::string(INDENT_LEVEL, ' ').append("Statistics:"));
1691
1692
0
            TableType stats;
1693
0
            if (nValidCount > 0)
1694
0
            {
1695
0
                stats.push_back({"min", std::to_string(dfMin)});
1696
0
                stats.push_back({"max", std::to_string(dfMax)});
1697
0
                stats.push_back({"mean", std::to_string(dfMean)});
1698
0
                stats.push_back({"stddev", std::to_string(dfStdDev)});
1699
0
            }
1700
0
            stats.push_back(
1701
0
                {"valid sample count", std::to_string(nValidCount)});
1702
1703
0
            DumpTable(stats, INDENT_LEVEL + 2, {}, false);
1704
0
        }
1705
0
    }
1706
1707
0
    if (m_sOptions.bDetailed)
1708
0
    {
1709
0
        if (dims.empty())
1710
0
        {
1711
0
            std::vector<GByte> abyTmp(dt.GetSize());
1712
0
            poArray->Read(nullptr, nullptr, nullptr, nullptr, dt, &abyTmp[0]);
1713
0
            CPLJSonStreamingWriter serializer(nullptr, nullptr);
1714
0
            DumpValue(serializer, &abyTmp[0], dt);
1715
1716
0
            AddLine();
1717
0
            AddLine(std::string(INDENT_LEVEL, ' ')
1718
0
                        .append("Value: ")
1719
0
                        .append(serializer.GetString()));
1720
0
        }
1721
0
        else
1722
0
        {
1723
0
            AddLine();
1724
0
            AddLine(std::string(INDENT_LEVEL, ' ').append("Values:"));
1725
1726
0
            int nIndent = INDENT_LEVEL;
1727
0
            CPLJSonStreamingWriter serializer(m_sOptions.bStdoutOutput
1728
0
                                                  ? WriteToStdoutWithSpaceIndent
1729
0
                                                  : nullptr,
1730
0
                                              &nIndent);
1731
0
            std::vector<GUInt64> startIdx(dims.size());
1732
0
            std::vector<GUInt64> dimSizes;
1733
0
            for (const auto &dim : dims)
1734
0
                dimSizes.emplace_back(dim->GetSize());
1735
0
            if (m_sOptions.bStdoutOutput)
1736
0
                printf("%s", std::string(INDENT_LEVEL, ' ').c_str());
1737
0
            DumpArrayRec(poArray, serializer, 0, dimSizes, startIdx,
1738
0
                         &m_sOptions);
1739
0
            if (!m_sOptions.bStdoutOutput)
1740
0
            {
1741
0
                AddLine(std::string(INDENT_LEVEL, ' ')
1742
0
                            .append(CPLString(serializer.GetString())
1743
0
                                        .replaceAll(
1744
0
                                            '\n', std::string("\n").append(
1745
0
                                                      std::string(INDENT_LEVEL,
1746
0
                                                                  ' ')))));
1747
0
            }
1748
0
            else
1749
0
            {
1750
0
                AddLine();
1751
0
            }
1752
0
        }
1753
0
    }
1754
0
}
1755
1756
/************************************************************************/
1757
/*        GDALMultiDimTextOutputDumper:: DumpDimensionsSummary()        */
1758
/************************************************************************/
1759
1760
std::set<std::string> GDALMultiDimTextOutputDumper::DumpDimensionsSummary(
1761
    const std::shared_ptr<GDALGroup> &group)
1762
0
{
1763
0
    std::set<std::string> oSetIndexingVariablePaths;
1764
1765
0
    const auto dims = group->GetDimensionsRecursive();
1766
0
    if (!dims.empty())
1767
0
    {
1768
0
        AddLine();
1769
0
        AddLine("Dimensions:");
1770
0
        TableType lines;
1771
0
        lines.push_back({"Name (path)", "Size", "Type", "Direction"});
1772
0
        for (const auto &poDim : dims)
1773
0
        {
1774
0
            std::vector<std::string> line{
1775
0
                poDim->GetFullName(), std::to_string(poDim->GetSize()),
1776
0
                poDim->GetType(), poDim->GetDirection()};
1777
0
            lines.push_back(std::move(line));
1778
0
            const auto poIndexingVariable = poDim->GetIndexingVariable();
1779
0
            if (poIndexingVariable)
1780
0
            {
1781
0
                const auto &osIndexingVarPath =
1782
0
                    poIndexingVariable->GetFullName();
1783
0
                if (!osIndexingVarPath.empty() && osIndexingVarPath[0] == '/')
1784
0
                {
1785
0
                    oSetIndexingVariablePaths.insert(osIndexingVarPath);
1786
0
                }
1787
0
            }
1788
0
        }
1789
0
        DumpTable(lines);
1790
0
    }
1791
1792
0
    return oSetIndexingVariablePaths;
1793
0
}
1794
1795
/************************************************************************/
1796
/*          GDALMultiDimTextOutputDumper:: DumpArraysSummary()          */
1797
/************************************************************************/
1798
1799
void GDALMultiDimTextOutputDumper::DumpArraysSummary(
1800
    const std::shared_ptr<GDALGroup> &group,
1801
    const std::vector<std::shared_ptr<GDALMDArray>> &apoArrays)
1802
0
{
1803
0
    const std::set<std::string> oSetIndexingVariablePaths =
1804
0
        DumpDimensionsSummary(group);
1805
1806
0
    TableType linesCoordinateArrays;
1807
0
    TableType linesScalarArrays;
1808
0
    std::map<std::string, TableType> linesDataArrays;
1809
0
    for (const auto &poArray : apoArrays)
1810
0
    {
1811
0
        const auto &dims = poArray->GetDimensions();
1812
0
        if (cpl::contains(oSetIndexingVariablePaths, poArray->GetFullName()))
1813
0
        {
1814
0
            if (linesCoordinateArrays.empty())
1815
0
                linesCoordinateArrays.push_back(
1816
0
                    {"Name (path)", "Dimension", "Type", "Unit"});
1817
0
            std::string osDimension;
1818
0
            for (const auto &poDim : dims)
1819
0
            {
1820
0
                if (osDimension.empty())
1821
0
                    osDimension = '(';
1822
0
                else
1823
0
                    osDimension += ", ";
1824
0
                osDimension += poDim->GetName();
1825
0
            }
1826
0
            if (!osDimension.empty())
1827
0
                osDimension += ')';
1828
0
            std::vector<std::string> line{
1829
0
                poArray->GetFullName(), std::move(osDimension),
1830
0
                TypeToString(poArray->GetDataType()), poArray->GetUnit()};
1831
0
            linesCoordinateArrays.push_back(std::move(line));
1832
0
        }
1833
0
        else if (dims.empty())
1834
0
        {
1835
0
            if (linesScalarArrays.empty())
1836
0
                linesScalarArrays.push_back({"Name (path)", "Type", "Unit"});
1837
0
            std::vector<std::string> line{poArray->GetFullName(),
1838
0
                                          TypeToString(poArray->GetDataType()),
1839
0
                                          poArray->GetUnit()};
1840
0
            linesScalarArrays.push_back(std::move(line));
1841
0
        }
1842
0
        else
1843
0
        {
1844
0
            const std::string osSameDimGroup = DimsToSameDimGroup(dims);
1845
1846
0
            bool bAllZero = true;
1847
0
            std::string osChunkSize =
1848
0
                BlockSizeToString(poArray->GetBlockSize(), &bAllZero);
1849
0
            if (bAllZero)
1850
0
                osChunkSize = "(unknown)";
1851
1852
0
            std::vector<std::string> line{
1853
0
                poArray->GetFullName(), TypeToString(poArray->GetDataType()),
1854
0
                poArray->GetUnit(), DimsToShapeString(dims),
1855
0
                std::move(osChunkSize)};
1856
0
            linesDataArrays[DimsToString(dims, osSameDimGroup)].push_back(
1857
0
                std::move(line));
1858
0
        }
1859
0
    }
1860
1861
0
    if (!linesCoordinateArrays.empty())
1862
0
    {
1863
0
        AddLine();
1864
0
        AddLine("Coordinates (indexing variables):");
1865
0
        DumpTable(linesCoordinateArrays);
1866
0
    }
1867
1868
0
    if (!linesDataArrays.empty())
1869
0
    {
1870
0
        AddLine();
1871
0
        AddLine("Data variables:");
1872
1873
0
        TableType headerLine;
1874
0
        headerLine.push_back(
1875
0
            {"Name (path)", "Type", "Unit", "Shape", "Chunk size"});
1876
1877
0
        std::vector<size_t> anColSizeDataVars;
1878
0
        anColSizeDataVars.resize(headerLine[0].size());
1879
0
        for (const auto [iCol, col] : cpl::enumerate(headerLine[0]))
1880
0
            anColSizeDataVars[iCol] = col.size();
1881
0
        for (const auto &[_, lines] : linesDataArrays)
1882
0
        {
1883
0
            for (const auto &line : lines)
1884
0
            {
1885
0
                for (const auto [iCol, col] : cpl::enumerate(line))
1886
0
                {
1887
0
                    anColSizeDataVars[iCol] =
1888
0
                        std::max(anColSizeDataVars[iCol], col.size());
1889
0
                }
1890
0
            }
1891
0
        }
1892
1893
0
        DumpTable(headerLine, 2, anColSizeDataVars);
1894
0
        for (const auto &[path, lines] : linesDataArrays)
1895
0
        {
1896
0
            AddLine();
1897
0
            AddLine(" " + path + ":");
1898
0
            DumpTable(lines, 2, anColSizeDataVars, false);
1899
0
        }
1900
0
    }
1901
1902
0
    if (!linesScalarArrays.empty())
1903
0
    {
1904
0
        AddLine();
1905
0
        AddLine("Scalar arrays:");
1906
0
        DumpTable(linesScalarArrays);
1907
0
    }
1908
0
}
1909
1910
/************************************************************************/
1911
/*          GDALMultiDimTextOutputDumper::DrumpGroupsSummary()          */
1912
/************************************************************************/
1913
1914
void GDALMultiDimTextOutputDumper::DrumpGroupsSummary(
1915
    const std::shared_ptr<GDALGroup> &group)
1916
0
{
1917
0
    const auto apoGroups = group->GetGroupsRecursive();
1918
0
    if (!apoGroups.empty())
1919
0
    {
1920
0
        AddLine();
1921
0
        AddLine("Groups:");
1922
0
        std::vector<std::vector<std::string>> groupNames;
1923
0
        for (auto &poGroup : apoGroups)
1924
0
            groupNames.push_back(CPLStringList(
1925
0
                CSLTokenizeString2(poGroup->GetFullName().c_str(), "/", 0)));
1926
0
        std::sort(groupNames.begin(), groupNames.end());
1927
0
        for (const auto &groupName : groupNames)
1928
0
        {
1929
0
            std::string osLine("  ");
1930
0
            for (const auto &part : groupName)
1931
0
            {
1932
0
                osLine += '/';
1933
0
                osLine += part;
1934
0
            }
1935
0
            AddLine(osLine);
1936
0
        }
1937
0
    }
1938
0
}
1939
1940
/************************************************************************/
1941
/*                             DumpAsText()                             */
1942
/************************************************************************/
1943
1944
static char *DumpAsText(const std::shared_ptr<GDALGroup> &group,
1945
                        const char *pszDriverName,
1946
                        const GDALMultiDimInfoOptions *psOptions)
1947
0
{
1948
0
    GDALMultiDimTextOutputDumper dumper(psOptions);
1949
1950
0
    CPLStringList aosOptionsGetArray(psOptions->aosArrayOptions);
1951
0
    if (psOptions->bDetailed)
1952
0
        aosOptionsGetArray.SetNameValue("SHOW_ALL", "YES");
1953
1954
0
    const auto apoArrays =
1955
0
        group->GetMDArraysRecursive(nullptr, aosOptionsGetArray.List());
1956
1957
0
    if (psOptions->osArrayName.empty())
1958
0
    {
1959
0
        dumper.AddLine(
1960
0
            std::string("Driver: ")
1961
0
                .append(pszDriverName ? pszDriverName : "(unknown)"));
1962
1963
0
        if (CSLConstList papszStructuralInfo = group->GetStructuralInfo())
1964
0
        {
1965
0
            dumper.DumpStructuralInfo(papszStructuralInfo, 0);
1966
0
        }
1967
1968
0
        dumper.DumpArraysSummary(group, apoArrays);
1969
1970
0
        dumper.DrumpGroupsSummary(group);
1971
0
    }
1972
1973
0
    if ((!psOptions->bSummary || !psOptions->osArrayName.empty()) &&
1974
0
        !apoArrays.empty())
1975
0
    {
1976
0
        if (psOptions->osArrayName.empty())
1977
0
        {
1978
0
            dumper.DumpAttributes(group->GetAttributes(), 0);
1979
0
            dumper.AddLine();
1980
0
        }
1981
1982
0
        dumper.AddLine("Arrays:");
1983
0
        for (const auto &poArray : apoArrays)
1984
0
        {
1985
0
            if (psOptions->osArrayName.empty() ||
1986
0
                poArray->GetName() == psOptions->osArrayName ||
1987
0
                poArray->GetFullName() == psOptions->osArrayName)
1988
0
            {
1989
0
                dumper.DumpArray(poArray);
1990
0
            }
1991
0
        }
1992
0
    }
1993
1994
0
    if (psOptions->bStdoutOutput)
1995
0
    {
1996
0
        return VSIStrdup("ok");
1997
0
    }
1998
0
    else
1999
0
    {
2000
0
        return VSIStrdup(dumper.m_osOutput.c_str());
2001
0
    }
2002
0
}
2003
2004
/************************************************************************/
2005
/*                           WriteToStdout()                            */
2006
/************************************************************************/
2007
2008
static void WriteToStdout(const char *pszText, void *)
2009
0
{
2010
0
    printf("%s", pszText);
2011
0
}
2012
2013
/************************************************************************/
2014
/*                GDALMultiDimInfoAppOptionsGetParser()                 */
2015
/************************************************************************/
2016
2017
static std::unique_ptr<GDALArgumentParser> GDALMultiDimInfoAppOptionsGetParser(
2018
    GDALMultiDimInfoOptions *psOptions,
2019
    GDALMultiDimInfoOptionsForBinary *psOptionsForBinary)
2020
0
{
2021
0
    auto argParser = std::make_unique<GDALArgumentParser>(
2022
0
        "gdalmdiminfo", /* bForBinary=*/psOptionsForBinary != nullptr);
2023
2024
0
    argParser->add_description(
2025
0
        _("Lists various information about a GDAL multidimensional dataset."));
2026
2027
0
    argParser->add_epilog(_("For more details, consult "
2028
0
                            "https://gdal.org/programs/gdalmdiminfo.html"));
2029
0
    {
2030
0
        auto &group = argParser->add_mutually_exclusive_group();
2031
2032
0
        group.add_argument("-summary")
2033
0
            .flag()
2034
0
            .store_into(psOptions->bSummary)
2035
0
            .help(_("Report only group and array hierarchy, without detailed "
2036
0
                    "information on attributes or dimensions."));
2037
2038
0
        group.add_argument("-detailed")
2039
0
            .flag()
2040
0
            .store_into(psOptions->bDetailed)
2041
0
            .help(
2042
0
                _("Most verbose output. Report attribute data types and array "
2043
0
                  "values."));
2044
0
    }
2045
2046
0
    argParser->add_inverted_logic_flag(
2047
0
        "-nopretty", &psOptions->bPretty,
2048
0
        _("Outputs on a single line without any indentation."));
2049
2050
0
    argParser->add_argument("-array")
2051
0
        .metavar("<array_name>")
2052
0
        .store_into(psOptions->osArrayName)
2053
0
        .help(_("Name of the array, used to restrict the output to the "
2054
0
                "specified array."));
2055
2056
0
    argParser->add_argument("-limit")
2057
0
        .metavar("<number>")
2058
0
        .scan<'i', int>()
2059
0
        .store_into(psOptions->nLimitValuesByDim)
2060
0
        .help(_("Number of values in each dimension that is used to limit the "
2061
0
                "display of array values."));
2062
2063
0
    if (psOptionsForBinary)
2064
0
    {
2065
0
        argParser->add_open_options_argument(
2066
0
            psOptionsForBinary->aosOpenOptions);
2067
2068
0
        argParser->add_input_format_argument(
2069
0
            &psOptionsForBinary->aosAllowInputDrivers);
2070
2071
0
        argParser->add_argument("dataset_name")
2072
0
            .metavar("<dataset_name>")
2073
0
            .store_into(psOptionsForBinary->osFilename)
2074
0
            .help("Input dataset.");
2075
0
    }
2076
2077
0
    argParser->add_argument("-arrayoption")
2078
0
        .metavar("<NAME>=<VALUE>")
2079
0
        .append()
2080
0
        .action([psOptions](const std::string &s)
2081
0
                { psOptions->aosArrayOptions.AddString(s.c_str()); })
2082
0
        .help(_("Option passed to GDALGroup::GetMDArrayNames() to filter "
2083
0
                "reported arrays."));
2084
2085
0
    argParser->add_argument("-stats")
2086
0
        .flag()
2087
0
        .store_into(psOptions->bStats)
2088
0
        .help(_("Read and display image statistics."));
2089
2090
0
    argParser->add_argument("-format")
2091
0
        .hidden()
2092
0
        .store_into(psOptions->osFormat)
2093
0
        .help(_("Output format."));
2094
2095
    // Only used by gdalmdiminfo binary to write output to stdout instead of in a string, in JSON mode
2096
0
    argParser->add_argument("-stdout").flag().hidden().store_into(
2097
0
        psOptions->bStdoutOutput);
2098
2099
0
    return argParser;
2100
0
}
2101
2102
/************************************************************************/
2103
/*                 GDALMultiDimInfoAppGetParserUsage()                  */
2104
/************************************************************************/
2105
2106
std::string GDALMultiDimInfoAppGetParserUsage()
2107
0
{
2108
0
    try
2109
0
    {
2110
0
        GDALMultiDimInfoOptions sOptions;
2111
0
        GDALMultiDimInfoOptionsForBinary sOptionsForBinary;
2112
0
        auto argParser =
2113
0
            GDALMultiDimInfoAppOptionsGetParser(&sOptions, &sOptionsForBinary);
2114
0
        return argParser->usage();
2115
0
    }
2116
0
    catch (const std::exception &err)
2117
0
    {
2118
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
2119
0
                 err.what());
2120
0
        return std::string();
2121
0
    }
2122
0
}
2123
2124
/************************************************************************/
2125
/*                          GDALMultiDimInfo()                          */
2126
/************************************************************************/
2127
2128
/* clang-format off */
2129
/**
2130
 * Lists various information about a GDAL multidimensional dataset.
2131
 *
2132
 * This is the equivalent of the
2133
 * <a href="/programs/gdalmdiminfo.html">gdalmdiminfo</a>utility.
2134
 *
2135
 * GDALMultiDimInfoOptions* must be allocated and freed with
2136
 * GDALMultiDimInfoOptionsNew() and GDALMultiDimInfoOptionsFree() respectively.
2137
 *
2138
 * @param hDataset the dataset handle.
2139
 * @param psOptionsIn the options structure returned by
2140
 * GDALMultiDimInfoOptionsNew() or NULL.
2141
 * @return string corresponding to the information about the raster dataset
2142
 * (must be freed with CPLFree()), or NULL in case of error.
2143
 *
2144
 * @since GDAL 3.1
2145
 */
2146
/* clang-format on */
2147
2148
char *GDALMultiDimInfo(GDALDatasetH hDataset,
2149
                       const GDALMultiDimInfoOptions *psOptionsIn)
2150
0
{
2151
0
    if (hDataset == nullptr)
2152
0
        return nullptr;
2153
2154
0
    GDALMultiDimInfoOptions oOptionsDefault;
2155
0
    const GDALMultiDimInfoOptions *psOptions =
2156
0
        psOptionsIn ? psOptionsIn : &oOptionsDefault;
2157
0
    CPLJSonStreamingWriter serializer(
2158
0
        psOptions->bStdoutOutput ? WriteToStdout : nullptr, nullptr);
2159
0
    serializer.SetPrettyFormatting(psOptions->bPretty);
2160
0
    GDALDataset *poDS = GDALDataset::FromHandle(hDataset);
2161
0
    auto group = poDS->GetRootGroup();
2162
0
    if (!group)
2163
0
        return nullptr;
2164
2165
0
    std::set<std::string> alreadyDumpedDimensions;
2166
0
    std::set<std::string> alreadyDumpedArrays;
2167
2168
0
    try
2169
0
    {
2170
0
        const char *pszDriverName = nullptr;
2171
0
        auto poDriver = poDS->GetDriver();
2172
0
        if (poDriver)
2173
0
            pszDriverName = poDriver->GetDescription();
2174
2175
0
        if (psOptions->osFormat == "text")
2176
0
        {
2177
0
            return DumpAsText(group, pszDriverName, psOptions);
2178
0
        }
2179
0
        else
2180
0
        {
2181
0
            if (psOptions->osArrayName.empty())
2182
0
            {
2183
0
                DumpGroup(group, group, pszDriverName, serializer, psOptions,
2184
0
                          alreadyDumpedDimensions, alreadyDumpedArrays, true,
2185
0
                          true);
2186
0
            }
2187
0
            else
2188
0
            {
2189
0
                auto curGroup = group;
2190
0
                CPLStringList aosTokens(
2191
0
                    CSLTokenizeString2(psOptions->osArrayName.c_str(), "/", 0));
2192
0
                for (int i = 0; i < aosTokens.size() - 1; i++)
2193
0
                {
2194
0
                    auto curGroupNew = curGroup->OpenGroup(aosTokens[i]);
2195
0
                    if (!curGroupNew)
2196
0
                    {
2197
0
                        CPLError(CE_Failure, CPLE_AppDefined,
2198
0
                                 "Cannot find group %s", aosTokens[i]);
2199
0
                        return nullptr;
2200
0
                    }
2201
0
                    curGroup = std::move(curGroupNew);
2202
0
                }
2203
0
                const char *pszArrayName = aosTokens.back();
2204
0
                auto array(curGroup->OpenMDArray(pszArrayName));
2205
0
                if (!array)
2206
0
                {
2207
0
                    CPLError(CE_Failure, CPLE_AppDefined,
2208
0
                             "Cannot find array %s", pszArrayName);
2209
0
                    return nullptr;
2210
0
                }
2211
0
                DumpArray(group, array, serializer, psOptions,
2212
0
                          alreadyDumpedDimensions, alreadyDumpedArrays, true,
2213
0
                          true, true);
2214
0
            }
2215
2216
0
            if (psOptions->bStdoutOutput)
2217
0
            {
2218
0
                printf("\n");
2219
0
                return VSIStrdup("ok");
2220
0
            }
2221
0
            else
2222
0
            {
2223
0
                return VSIStrdup(serializer.GetString().c_str());
2224
0
            }
2225
0
        }
2226
0
    }
2227
0
    catch (const std::exception &e)
2228
0
    {
2229
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
2230
0
        return nullptr;
2231
0
    }
2232
0
}
2233
2234
/************************************************************************/
2235
/*                     GDALMultiDimInfoOptionsNew()                     */
2236
/************************************************************************/
2237
2238
/**
2239
 * Allocates a GDALMultiDimInfo struct.
2240
 *
2241
 * @param papszArgv NULL terminated list of options (potentially including
2242
 * filename and open options too), or NULL. The accepted options are the ones of
2243
 * the <a href="/programs/gdalmdiminfo.html">gdalmdiminfo</a> utility.
2244
 * @param psOptionsForBinary should be nullptr, unless called from
2245
 * gdalmultidiminfo_bin.cpp
2246
 * @return pointer to the allocated GDALMultiDimInfoOptions struct. Must be
2247
 * freed with GDALMultiDimInfoOptionsFree().
2248
 *
2249
 * @since GDAL 3.1
2250
 */
2251
2252
GDALMultiDimInfoOptions *
2253
GDALMultiDimInfoOptionsNew(char **papszArgv,
2254
                           GDALMultiDimInfoOptionsForBinary *psOptionsForBinary)
2255
0
{
2256
0
    auto psOptions = std::make_unique<GDALMultiDimInfoOptions>();
2257
2258
    /* -------------------------------------------------------------------- */
2259
    /*      Parse arguments.                                                */
2260
    /* -------------------------------------------------------------------- */
2261
2262
0
    CPLStringList aosArgv;
2263
2264
0
    if (papszArgv)
2265
0
    {
2266
0
        const int nArgc = CSLCount(papszArgv);
2267
0
        for (int i = 0; i < nArgc; i++)
2268
0
            aosArgv.AddString(papszArgv[i]);
2269
0
    }
2270
2271
0
    try
2272
0
    {
2273
0
        auto argParser = GDALMultiDimInfoAppOptionsGetParser(
2274
0
            psOptions.get(), psOptionsForBinary);
2275
0
        argParser->parse_args_without_binary_name(aosArgv);
2276
0
    }
2277
0
    catch (const std::exception &err)
2278
0
    {
2279
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
2280
0
                 err.what());
2281
0
        return nullptr;
2282
0
    }
2283
2284
0
    return psOptions.release();
2285
0
}
2286
2287
/************************************************************************/
2288
/*                    GDALMultiDimInfoOptionsFree()                     */
2289
/************************************************************************/
2290
2291
/**
2292
 * Frees the GDALMultiDimInfoOptions struct.
2293
 *
2294
 * @param psOptions the options struct for GDALMultiDimInfo().
2295
 *
2296
 * @since GDAL 3.1
2297
 */
2298
2299
void GDALMultiDimInfoOptionsFree(GDALMultiDimInfoOptions *psOptions)
2300
0
{
2301
0
    delete psOptions;
2302
0
}