LCOV - code coverage report
Current view: top level - apps - gdalmdiminfo_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 947 1129 83.9 %
Date: 2026-06-23 16:35:19 Functions: 49 56 87.5 %

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

Generated by: LCOV version 1.14