LCOV - code coverage report
Current view: top level - gcore/multidim - gdalmultidim_group.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 357 444 80.4 %
Date: 2026-04-15 22:10:00 Functions: 23 31 74.2 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Name:     gdalmultidim_group.cpp
       4             :  * Project:  GDAL Core
       5             :  * Purpose:  Implementation of GDALGroup class
       6             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_float.h"
      15             : #include "gdal_multidim.h"
      16             : 
      17             : #include <list>
      18             : #include <map>
      19             : #include <queue>
      20             : #include <set>
      21             : 
      22             : /************************************************************************/
      23             : /*                             GDALGroup()                              */
      24             : /************************************************************************/
      25             : 
      26             : //! @cond Doxygen_Suppress
      27       13627 : GDALGroup::GDALGroup(const std::string &osParentName, const std::string &osName,
      28       13627 :                      const std::string &osContext)
      29       13627 :     : m_osName(osParentName.empty() ? "/" : osName),
      30             :       m_osFullName(
      31       27254 :           !osParentName.empty()
      32       20510 :               ? ((osParentName == "/" ? "/" : osParentName + "/") + osName)
      33             :               : "/"),
      34       34137 :       m_osContext(osContext)
      35             : {
      36       13627 : }
      37             : 
      38             : //! @endcond
      39             : 
      40             : /************************************************************************/
      41             : /*                             ~GDALGroup()                             */
      42             : /************************************************************************/
      43             : 
      44             : GDALGroup::~GDALGroup() = default;
      45             : 
      46             : /************************************************************************/
      47             : /*                          GetMDArrayNames()                           */
      48             : /************************************************************************/
      49             : 
      50             : /** Return the list of multidimensional array names contained in this group.
      51             :  *
      52             :  * @note Driver implementation: optionally implemented. If implemented,
      53             :  * OpenMDArray() should also be implemented.
      54             :  *
      55             :  * Drivers known to implement it: MEM, netCDF.
      56             :  *
      57             :  * This is the same as the C function GDALGroupGetMDArrayNames().
      58             :  *
      59             :  * @param papszOptions Driver specific options determining how arrays
      60             :  * should be retrieved. Pass nullptr for default behavior.
      61             :  *
      62             :  * @return the array names.
      63             :  */
      64             : std::vector<std::string>
      65           3 : GDALGroup::GetMDArrayNames(CPL_UNUSED CSLConstList papszOptions) const
      66             : {
      67           3 :     return {};
      68             : }
      69             : 
      70             : /************************************************************************/
      71             : /*                    GetMDArrayFullNamesRecursive()                    */
      72             : /************************************************************************/
      73             : 
      74             : /** Return the list of multidimensional array full names contained in this
      75             :  * group and its subgroups.
      76             :  *
      77             :  * This is the same as the C function GDALGroupGetMDArrayFullNamesRecursive().
      78             :  *
      79             :  * @param papszGroupOptions Driver specific options determining how groups
      80             :  * should be retrieved. Pass nullptr for default behavior.
      81             :  * @param papszArrayOptions Driver specific options determining how arrays
      82             :  * should be retrieved. Pass nullptr for default behavior.
      83             :  *
      84             :  * @return the array full names.
      85             :  *
      86             :  * @since 3.11
      87             :  */
      88             : std::vector<std::string>
      89          10 : GDALGroup::GetMDArrayFullNamesRecursive(CSLConstList papszGroupOptions,
      90             :                                         CSLConstList papszArrayOptions) const
      91             : {
      92          10 :     std::vector<std::string> ret;
      93          20 :     std::list<std::shared_ptr<GDALGroup>> stackGroups;
      94          10 :     stackGroups.push_back(nullptr);  // nullptr means this
      95          23 :     while (!stackGroups.empty())
      96             :     {
      97          26 :         std::shared_ptr<GDALGroup> groupPtr = std::move(stackGroups.front());
      98          13 :         stackGroups.erase(stackGroups.begin());
      99          13 :         const GDALGroup *poCurGroup = groupPtr ? groupPtr.get() : this;
     100          31 :         for (const std::string &arrayName :
     101          75 :              poCurGroup->GetMDArrayNames(papszArrayOptions))
     102             :         {
     103          62 :             std::string osFullName = poCurGroup->GetFullName();
     104          31 :             if (!osFullName.empty() && osFullName.back() != '/')
     105           3 :                 osFullName += '/';
     106          31 :             osFullName += arrayName;
     107          31 :             ret.push_back(std::move(osFullName));
     108             :         }
     109          13 :         auto insertionPoint = stackGroups.begin();
     110           3 :         for (const auto &osSubGroup :
     111          19 :              poCurGroup->GetGroupNames(papszGroupOptions))
     112             :         {
     113           6 :             auto poSubGroup = poCurGroup->OpenGroup(osSubGroup);
     114           3 :             if (poSubGroup)
     115           3 :                 stackGroups.insert(insertionPoint, std::move(poSubGroup));
     116             :         }
     117             :     }
     118             : 
     119          20 :     return ret;
     120             : }
     121             : 
     122             : /************************************************************************/
     123             : /*                            OpenMDArray()                             */
     124             : /************************************************************************/
     125             : 
     126             : /** Open and return a multidimensional array.
     127             :  *
     128             :  * @note Driver implementation: optionally implemented. If implemented,
     129             :  * GetMDArrayNames() should also be implemented.
     130             :  *
     131             :  * Drivers known to implement it: MEM, netCDF.
     132             :  *
     133             :  * This is the same as the C function GDALGroupOpenMDArray().
     134             :  *
     135             :  * @param osName Array name.
     136             :  * @param papszOptions Driver specific options determining how the array should
     137             :  * be opened.  Pass nullptr for default behavior.
     138             :  *
     139             :  * @return the array, or nullptr.
     140             :  */
     141             : std::shared_ptr<GDALMDArray>
     142           0 : GDALGroup::OpenMDArray(CPL_UNUSED const std::string &osName,
     143             :                        CPL_UNUSED CSLConstList papszOptions) const
     144             : {
     145           0 :     return nullptr;
     146             : }
     147             : 
     148             : /************************************************************************/
     149             : /*                           GetGroupNames()                            */
     150             : /************************************************************************/
     151             : 
     152             : /** Return the list of sub-groups contained in this group.
     153             :  *
     154             :  * @note Driver implementation: optionally implemented. If implemented,
     155             :  * OpenGroup() should also be implemented.
     156             :  *
     157             :  * Drivers known to implement it: MEM, netCDF.
     158             :  *
     159             :  * This is the same as the C function GDALGroupGetGroupNames().
     160             :  *
     161             :  * @param papszOptions Driver specific options determining how groups
     162             :  * should be retrieved. Pass nullptr for default behavior.
     163             :  *
     164             :  * @return the group names.
     165             :  */
     166             : std::vector<std::string>
     167           7 : GDALGroup::GetGroupNames(CPL_UNUSED CSLConstList papszOptions) const
     168             : {
     169           7 :     return {};
     170             : }
     171             : 
     172             : /************************************************************************/
     173             : /*                             OpenGroup()                              */
     174             : /************************************************************************/
     175             : 
     176             : /** Open and return a sub-group.
     177             :  *
     178             :  * @note Driver implementation: optionally implemented. If implemented,
     179             :  * GetGroupNames() should also be implemented.
     180             :  *
     181             :  * Drivers known to implement it: MEM, netCDF.
     182             :  *
     183             :  * This is the same as the C function GDALGroupOpenGroup().
     184             :  *
     185             :  * @param osName Sub-group name.
     186             :  * @param papszOptions Driver specific options determining how the sub-group
     187             :  * should be opened.  Pass nullptr for default behavior.
     188             :  *
     189             :  * @return the group, or nullptr.
     190             :  */
     191             : std::shared_ptr<GDALGroup>
     192           4 : GDALGroup::OpenGroup(CPL_UNUSED const std::string &osName,
     193             :                      CPL_UNUSED CSLConstList papszOptions) const
     194             : {
     195           4 :     return nullptr;
     196             : }
     197             : 
     198             : /************************************************************************/
     199             : /*                        GetVectorLayerNames()                         */
     200             : /************************************************************************/
     201             : 
     202             : /** Return the list of layer names contained in this group.
     203             :  *
     204             :  * @note Driver implementation: optionally implemented. If implemented,
     205             :  * OpenVectorLayer() should also be implemented.
     206             :  *
     207             :  * Drivers known to implement it: OpenFileGDB, FileGDB
     208             :  *
     209             :  * Other drivers will return an empty list. GDALDataset::GetLayerCount() and
     210             :  * GDALDataset::GetLayer() should then be used.
     211             :  *
     212             :  * This is the same as the C function GDALGroupGetVectorLayerNames().
     213             :  *
     214             :  * @param papszOptions Driver specific options determining how layers
     215             :  * should be retrieved. Pass nullptr for default behavior.
     216             :  *
     217             :  * @return the vector layer names.
     218             :  * @since GDAL 3.4
     219             :  */
     220             : std::vector<std::string>
     221           1 : GDALGroup::GetVectorLayerNames(CPL_UNUSED CSLConstList papszOptions) const
     222             : {
     223           1 :     return {};
     224             : }
     225             : 
     226             : /************************************************************************/
     227             : /*                          OpenVectorLayer()                           */
     228             : /************************************************************************/
     229             : 
     230             : /** Open and return a vector layer.
     231             :  *
     232             :  * Due to the historical ownership of OGRLayer* by GDALDataset*, the
     233             :  * lifetime of the returned OGRLayer* is linked to the one of the owner
     234             :  * dataset (contrary to the general design of this class where objects can be
     235             :  * used independently of the object that returned them)
     236             :  *
     237             :  * @note Driver implementation: optionally implemented. If implemented,
     238             :  * GetVectorLayerNames() should also be implemented.
     239             :  *
     240             :  * Drivers known to implement it: MEM, netCDF.
     241             :  *
     242             :  * This is the same as the C function GDALGroupOpenVectorLayer().
     243             :  *
     244             :  * @param osName Vector layer name.
     245             :  * @param papszOptions Driver specific options determining how the layer should
     246             :  * be opened.  Pass nullptr for default behavior.
     247             :  *
     248             :  * @return the group, or nullptr.
     249             :  */
     250           2 : OGRLayer *GDALGroup::OpenVectorLayer(CPL_UNUSED const std::string &osName,
     251             :                                      CPL_UNUSED CSLConstList papszOptions) const
     252             : {
     253           2 :     return nullptr;
     254             : }
     255             : 
     256             : /************************************************************************/
     257             : /*                           GetDimensions()                            */
     258             : /************************************************************************/
     259             : 
     260             : /** Return the list of dimensions contained in this group and used by its
     261             :  * arrays.
     262             :  *
     263             :  * This is for dimensions that can potentially be used by several arrays.
     264             :  * Not all drivers might implement this. To retrieve the dimensions used by
     265             :  * a specific array, use GDALMDArray::GetDimensions().
     266             :  *
     267             :  * Drivers known to implement it: MEM, netCDF
     268             :  *
     269             :  * This is the same as the C function GDALGroupGetDimensions().
     270             :  *
     271             :  * @param papszOptions Driver specific options determining how groups
     272             :  * should be retrieved. Pass nullptr for default behavior.
     273             :  *
     274             :  * @return the dimensions.
     275             :  */
     276             : std::vector<std::shared_ptr<GDALDimension>>
     277          16 : GDALGroup::GetDimensions(CPL_UNUSED CSLConstList papszOptions) const
     278             : {
     279          16 :     return {};
     280             : }
     281             : 
     282             : /************************************************************************/
     283             : /*                         GetStructuralInfo()                          */
     284             : /************************************************************************/
     285             : 
     286             : /** Return structural information on the group.
     287             :  *
     288             :  * This may be the compression, etc..
     289             :  *
     290             :  * The return value should not be freed and is valid until GDALGroup is
     291             :  * released or this function called again.
     292             :  *
     293             :  * This is the same as the C function GDALGroupGetStructuralInfo().
     294             :  */
     295          55 : CSLConstList GDALGroup::GetStructuralInfo() const
     296             : {
     297          55 :     return nullptr;
     298             : }
     299             : 
     300             : /************************************************************************/
     301             : /*                            CreateGroup()                             */
     302             : /************************************************************************/
     303             : 
     304             : /** Create a sub-group within a group.
     305             :  *
     306             :  * Optionally implemented by drivers.
     307             :  *
     308             :  * Drivers known to implement it: MEM, netCDF
     309             :  *
     310             :  * This is the same as the C function GDALGroupCreateGroup().
     311             :  *
     312             :  * @param osName Sub-group name.
     313             :  * @param papszOptions Driver specific options determining how the sub-group
     314             :  * should be created.
     315             :  *
     316             :  * @return the new sub-group, or nullptr in case of error.
     317             :  */
     318             : std::shared_ptr<GDALGroup>
     319           0 : GDALGroup::CreateGroup(CPL_UNUSED const std::string &osName,
     320             :                        CPL_UNUSED CSLConstList papszOptions)
     321             : {
     322           0 :     CPLError(CE_Failure, CPLE_NotSupported, "CreateGroup() not implemented");
     323           0 :     return nullptr;
     324             : }
     325             : 
     326             : /************************************************************************/
     327             : /*                            DeleteGroup()                             */
     328             : /************************************************************************/
     329             : 
     330             : /** Delete a sub-group from a group.
     331             :  *
     332             :  * Optionally implemented.
     333             :  *
     334             :  * After this call, if a previously obtained instance of the deleted object
     335             :  * is still alive, no method other than for freeing it should be invoked.
     336             :  *
     337             :  * Drivers known to implement it: MEM, Zarr
     338             :  *
     339             :  * This is the same as the C function GDALGroupDeleteGroup().
     340             :  *
     341             :  * @param osName Sub-group name.
     342             :  * @param papszOptions Driver specific options determining how the group.
     343             :  * should be deleted.
     344             :  *
     345             :  * @return true in case of success
     346             :  * @since GDAL 3.8
     347             :  */
     348           0 : bool GDALGroup::DeleteGroup(CPL_UNUSED const std::string &osName,
     349             :                             CPL_UNUSED CSLConstList papszOptions)
     350             : {
     351           0 :     CPLError(CE_Failure, CPLE_NotSupported, "DeleteGroup() not implemented");
     352           0 :     return false;
     353             : }
     354             : 
     355             : /************************************************************************/
     356             : /*                          CreateDimension()                           */
     357             : /************************************************************************/
     358             : 
     359             : /** Create a dimension within a group.
     360             :  *
     361             :  * @note Driver implementation: drivers supporting CreateDimension() should
     362             :  * implement this method, but do not have necessarily to implement
     363             :  * GDALGroup::GetDimensions().
     364             :  *
     365             :  * Drivers known to implement it: MEM, netCDF
     366             :  *
     367             :  * This is the same as the C function GDALGroupCreateDimension().
     368             :  *
     369             :  * @param osName Dimension name.
     370             :  * @param osType Dimension type (might be empty, and ignored by drivers)
     371             :  * @param osDirection Dimension direction (might be empty, and ignored by
     372             :  * drivers)
     373             :  * @param nSize  Number of values indexed by this dimension. Should be > 0.
     374             :  * @param papszOptions Driver specific options determining how the dimension
     375             :  * should be created.
     376             :  *
     377             :  * @return the new dimension, or nullptr if case of error
     378             :  */
     379           0 : std::shared_ptr<GDALDimension> GDALGroup::CreateDimension(
     380             :     CPL_UNUSED const std::string &osName, CPL_UNUSED const std::string &osType,
     381             :     CPL_UNUSED const std::string &osDirection, CPL_UNUSED GUInt64 nSize,
     382             :     CPL_UNUSED CSLConstList papszOptions)
     383             : {
     384           0 :     CPLError(CE_Failure, CPLE_NotSupported,
     385             :              "CreateDimension() not implemented");
     386           0 :     return nullptr;
     387             : }
     388             : 
     389             : /************************************************************************/
     390             : /*                           CreateMDArray()                            */
     391             : /************************************************************************/
     392             : 
     393             : /** Create a multidimensional array within a group.
     394             :  *
     395             :  * It is recommended that the GDALDimension objects passed in aoDimensions
     396             :  * belong to this group, either by retrieving them with GetDimensions()
     397             :  * or creating a new one with CreateDimension().
     398             :  *
     399             :  * Optionally implemented.
     400             :  *
     401             :  * Drivers known to implement it: MEM, netCDF
     402             :  *
     403             :  * This is the same as the C function GDALGroupCreateMDArray().
     404             :  *
     405             :  * @note Driver implementation: drivers should take into account the possibility
     406             :  * that GDALDimension object passed in aoDimensions might belong to a different
     407             :  * group / dataset / driver and act accordingly.
     408             :  *
     409             :  * @param osName Array name.
     410             :  * @param aoDimensions List of dimensions, ordered from the slowest varying
     411             :  *                     dimension first to the fastest varying dimension last.
     412             :  *                     Might be empty for a scalar array (if supported by
     413             :  * driver)
     414             :  * @param oDataType  Array data type.
     415             :  * @param papszOptions Driver specific options determining how the array
     416             :  * should be created.
     417             :  *
     418             :  * @return the new array, or nullptr in case of error
     419             :  */
     420           0 : std::shared_ptr<GDALMDArray> GDALGroup::CreateMDArray(
     421             :     CPL_UNUSED const std::string &osName,
     422             :     CPL_UNUSED const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
     423             :     CPL_UNUSED const GDALExtendedDataType &oDataType,
     424             :     CPL_UNUSED CSLConstList papszOptions)
     425             : {
     426           0 :     CPLError(CE_Failure, CPLE_NotSupported, "CreateMDArray() not implemented");
     427           0 :     return nullptr;
     428             : }
     429             : 
     430             : /************************************************************************/
     431             : /*                           DeleteMDArray()                            */
     432             : /************************************************************************/
     433             : 
     434             : /** Delete an array from a group.
     435             :  *
     436             :  * Optionally implemented.
     437             :  *
     438             :  * After this call, if a previously obtained instance of the deleted object
     439             :  * is still alive, no method other than for freeing it should be invoked.
     440             :  *
     441             :  * Drivers known to implement it: MEM, Zarr
     442             :  *
     443             :  * This is the same as the C function GDALGroupDeleteMDArray().
     444             :  *
     445             :  * @param osName Arrayname.
     446             :  * @param papszOptions Driver specific options determining how the array.
     447             :  * should be deleted.
     448             :  *
     449             :  * @return true in case of success
     450             :  * @since GDAL 3.8
     451             :  */
     452           0 : bool GDALGroup::DeleteMDArray(CPL_UNUSED const std::string &osName,
     453             :                               CPL_UNUSED CSLConstList papszOptions)
     454             : {
     455           0 :     CPLError(CE_Failure, CPLE_NotSupported, "DeleteMDArray() not implemented");
     456           0 :     return false;
     457             : }
     458             : 
     459             : /************************************************************************/
     460             : /*                          GetTotalCopyCost()                          */
     461             : /************************************************************************/
     462             : 
     463             : /** Return a total "cost" to copy the group.
     464             :  *
     465             :  * Used as a parameter for CopFrom()
     466             :  */
     467          33 : GUInt64 GDALGroup::GetTotalCopyCost() const
     468             : {
     469          33 :     GUInt64 nCost = COPY_COST;
     470          33 :     nCost += GetAttributes().size() * GDALAttribute::COPY_COST;
     471             : 
     472          66 :     auto groupNames = GetGroupNames();
     473          39 :     for (const auto &name : groupNames)
     474             :     {
     475          12 :         auto subGroup = OpenGroup(name);
     476           6 :         if (subGroup)
     477             :         {
     478           6 :             nCost += subGroup->GetTotalCopyCost();
     479             :         }
     480             :     }
     481             : 
     482          33 :     auto arrayNames = GetMDArrayNames();
     483         102 :     for (const auto &name : arrayNames)
     484             :     {
     485         138 :         auto array = OpenMDArray(name);
     486          69 :         if (array)
     487             :         {
     488          69 :             nCost += array->GetTotalCopyCost();
     489             :         }
     490             :     }
     491          66 :     return nCost;
     492             : }
     493             : 
     494             : /************************************************************************/
     495             : /*                              CopyFrom()                              */
     496             : /************************************************************************/
     497             : 
     498             : /** Copy the content of a group into a new (generally empty) group.
     499             :  *
     500             :  * @param poDstRootGroup Destination root group. Must NOT be nullptr.
     501             :  * @param poSrcDS    Source dataset. Might be nullptr (but for correct behavior
     502             :  *                   of some output drivers this is not recommended)
     503             :  * @param poSrcGroup Source group. Must NOT be nullptr.
     504             :  * @param bStrict Whether to enable strict mode. In strict mode, any error will
     505             :  *                stop the copy. In relaxed mode, the copy will be attempted to
     506             :  *                be pursued.
     507             :  * @param nCurCost  Should be provided as a variable initially set to 0.
     508             :  * @param nTotalCost Total cost from GetTotalCopyCost().
     509             :  * @param pfnProgress Progress callback, or nullptr.
     510             :  * @param pProgressData Progress user data, or nullptr.
     511             :  * @param papszOptions Creation options. Currently, only array creation
     512             :  *                     options are supported. They must be prefixed with
     513             :  * "ARRAY:" . The scope may be further restricted to arrays of a certain
     514             :  *                     dimension by adding "IF(DIM={ndims}):" after "ARRAY:".
     515             :  *                     For example, "ARRAY:IF(DIM=2):BLOCKSIZE=256,256" will
     516             :  *                     restrict BLOCKSIZE=256,256 to arrays of dimension 2.
     517             :  *                     Restriction to arrays of a given name is done with adding
     518             :  *                     "IF(NAME={name}):" after "ARRAY:". {name} can also be
     519             :  *                     a full qualified name.
     520             :  *                     A non-driver specific ARRAY option, "AUTOSCALE=YES" can
     521             :  * be used to ask (non indexing) variables of type Float32 or Float64 to be
     522             :  * scaled to UInt16 with scale and offset values being computed from the minimum
     523             :  * and maximum of the source array. The integer data type used can be set with
     524             :  *                     AUTOSCALE_DATA_TYPE=Byte/UInt16/Int16/UInt32/Int32.
     525             :  *
     526             :  * @return true in case of success (or partial success if bStrict == false).
     527             :  */
     528          33 : bool GDALGroup::CopyFrom(const std::shared_ptr<GDALGroup> &poDstRootGroup,
     529             :                          GDALDataset *poSrcDS,
     530             :                          const std::shared_ptr<GDALGroup> &poSrcGroup,
     531             :                          bool bStrict, GUInt64 &nCurCost,
     532             :                          const GUInt64 nTotalCost, GDALProgressFunc pfnProgress,
     533             :                          void *pProgressData, CSLConstList papszOptions)
     534             : {
     535          33 :     if (pfnProgress == nullptr)
     536           0 :         pfnProgress = GDALDummyProgress;
     537             : 
     538             : #define EXIT_OR_CONTINUE_IF_NULL(x)                                            \
     539             :     if (!(x))                                                                  \
     540             :     {                                                                          \
     541             :         if (bStrict)                                                           \
     542             :             return false;                                                      \
     543             :         continue;                                                              \
     544             :     }                                                                          \
     545             :     (void)0
     546             : 
     547             :     try
     548             :     {
     549          33 :         nCurCost += GDALGroup::COPY_COST;
     550             : 
     551          66 :         const auto srcDims = poSrcGroup->GetDimensions();
     552             :         std::map<std::string, std::shared_ptr<GDALDimension>>
     553          66 :             mapExistingDstDims;
     554          66 :         std::map<std::string, std::string> mapSrcVariableNameToIndexedDimName;
     555          87 :         for (const auto &dim : srcDims)
     556             :         {
     557             :             auto dstDim = CreateDimension(dim->GetName(), dim->GetType(),
     558          54 :                                           dim->GetDirection(), dim->GetSize());
     559          54 :             EXIT_OR_CONTINUE_IF_NULL(dstDim);
     560          54 :             mapExistingDstDims[dim->GetName()] = std::move(dstDim);
     561         108 :             auto poIndexingVarSrc(dim->GetIndexingVariable());
     562          54 :             if (poIndexingVarSrc)
     563             :             {
     564             :                 mapSrcVariableNameToIndexedDimName[poIndexingVarSrc
     565          35 :                                                        ->GetName()] =
     566          70 :                     dim->GetName();
     567             :             }
     568             :         }
     569             : 
     570          66 :         auto attrs = poSrcGroup->GetAttributes();
     571          51 :         for (const auto &attr : attrs)
     572             :         {
     573             :             auto dstAttr =
     574          18 :                 CreateAttribute(attr->GetName(), attr->GetDimensionsSize(),
     575          36 :                                 attr->GetDataType());
     576          18 :             EXIT_OR_CONTINUE_IF_NULL(dstAttr);
     577          18 :             auto raw(attr->ReadAsRaw());
     578          18 :             if (!dstAttr->Write(raw.data(), raw.size()) && bStrict)
     579           0 :                 return false;
     580             :         }
     581          33 :         if (!attrs.empty())
     582             :         {
     583           8 :             nCurCost += attrs.size() * GDALAttribute::COPY_COST;
     584           8 :             if (!pfnProgress(double(nCurCost) / nTotalCost, "", pProgressData))
     585           0 :                 return false;
     586             :         }
     587             : 
     588             :         const auto CopyArray =
     589          69 :             [this, &poSrcDS, &poDstRootGroup, &mapExistingDstDims,
     590             :              &mapSrcVariableNameToIndexedDimName, pfnProgress, pProgressData,
     591             :              papszOptions, bStrict, &nCurCost,
     592         616 :              nTotalCost](const std::shared_ptr<GDALMDArray> &srcArray)
     593             :         {
     594             :             // Map source dimensions to target dimensions
     595         138 :             std::vector<std::shared_ptr<GDALDimension>> dstArrayDims;
     596          69 :             const auto &srcArrayDims(srcArray->GetDimensions());
     597         172 :             for (const auto &dim : srcArrayDims)
     598             :             {
     599             :                 auto dstDim = poDstRootGroup->OpenDimensionFromFullname(
     600         103 :                     dim->GetFullName());
     601         103 :                 if (dstDim && dstDim->GetSize() == dim->GetSize())
     602             :                 {
     603          93 :                     dstArrayDims.emplace_back(dstDim);
     604             :                 }
     605             :                 else
     606             :                 {
     607          10 :                     auto oIter = mapExistingDstDims.find(dim->GetName());
     608          19 :                     if (oIter != mapExistingDstDims.end() &&
     609           9 :                         oIter->second->GetSize() == dim->GetSize())
     610             :                     {
     611           8 :                         dstArrayDims.emplace_back(oIter->second);
     612             :                     }
     613             :                     else
     614             :                     {
     615           2 :                         std::string newDimName;
     616           2 :                         if (oIter == mapExistingDstDims.end())
     617             :                         {
     618           1 :                             newDimName = dim->GetName();
     619             :                         }
     620             :                         else
     621             :                         {
     622           1 :                             std::string newDimNamePrefix(srcArray->GetName() +
     623           3 :                                                          '_' + dim->GetName());
     624           1 :                             newDimName = newDimNamePrefix;
     625           1 :                             int nIterCount = 2;
     626           0 :                             while (
     627           1 :                                 cpl::contains(mapExistingDstDims, newDimName))
     628             :                             {
     629           0 :                                 newDimName = newDimNamePrefix +
     630           0 :                                              CPLSPrintf("_%d", nIterCount);
     631           0 :                                 nIterCount++;
     632             :                             }
     633             :                         }
     634           4 :                         dstDim = CreateDimension(newDimName, dim->GetType(),
     635             :                                                  dim->GetDirection(),
     636           4 :                                                  dim->GetSize());
     637           2 :                         if (!dstDim)
     638           0 :                             return false;
     639           2 :                         mapExistingDstDims[newDimName] = dstDim;
     640           2 :                         dstArrayDims.emplace_back(dstDim);
     641             :                     }
     642             :                 }
     643             :             }
     644             : 
     645         138 :             CPLStringList aosArrayCO;
     646          69 :             bool bAutoScale = false;
     647          69 :             GDALDataType eAutoScaleType = GDT_UInt16;
     648          76 :             for (const char *pszItem : cpl::Iterate(papszOptions))
     649             :             {
     650           7 :                 if (STARTS_WITH_CI(pszItem, "ARRAY:"))
     651             :                 {
     652           7 :                     const char *pszOption = pszItem + strlen("ARRAY:");
     653           7 :                     if (STARTS_WITH_CI(pszOption, "IF(DIM="))
     654             :                     {
     655           1 :                         const char *pszNext = strchr(pszOption, ':');
     656           1 :                         if (pszNext != nullptr)
     657             :                         {
     658           1 :                             int nDim = atoi(pszOption + strlen("IF(DIM="));
     659           1 :                             if (static_cast<size_t>(nDim) ==
     660           1 :                                 dstArrayDims.size())
     661             :                             {
     662           1 :                                 pszOption = pszNext + 1;
     663             :                             }
     664             :                             else
     665             :                             {
     666           0 :                                 pszOption = nullptr;
     667             :                             }
     668             :                         }
     669             :                     }
     670           6 :                     else if (STARTS_WITH_CI(pszOption, "IF(NAME="))
     671             :                     {
     672           2 :                         const char *pszName = pszOption + strlen("IF(NAME=");
     673           2 :                         const char *pszNext = strchr(pszName, ':');
     674           2 :                         if (pszNext != nullptr && pszNext > pszName &&
     675           2 :                             pszNext[-1] == ')')
     676             :                         {
     677           4 :                             CPLString osName;
     678           2 :                             osName.assign(pszName, pszNext - pszName - 1);
     679           3 :                             if (osName == srcArray->GetName() ||
     680           1 :                                 osName == srcArray->GetFullName())
     681             :                             {
     682           2 :                                 pszOption = pszNext + 1;
     683             :                             }
     684             :                             else
     685             :                             {
     686           0 :                                 pszOption = nullptr;
     687             :                             }
     688             :                         }
     689             :                     }
     690           7 :                     if (pszOption)
     691             :                     {
     692           7 :                         if (STARTS_WITH_CI(pszOption, "AUTOSCALE="))
     693             :                         {
     694             :                             bAutoScale =
     695           2 :                                 CPLTestBool(pszOption + strlen("AUTOSCALE="));
     696             :                         }
     697           5 :                         else if (STARTS_WITH_CI(pszOption,
     698             :                                                 "AUTOSCALE_DATA_TYPE="))
     699             :                         {
     700           1 :                             const char *pszDataType =
     701             :                                 pszOption + strlen("AUTOSCALE_DATA_TYPE=");
     702           1 :                             eAutoScaleType = GDALGetDataTypeByName(pszDataType);
     703           2 :                             if (GDALDataTypeIsComplex(eAutoScaleType) ||
     704           1 :                                 GDALDataTypeIsFloating(eAutoScaleType))
     705             :                             {
     706           0 :                                 CPLError(CE_Failure, CPLE_NotSupported,
     707             :                                          "Unsupported value for "
     708             :                                          "AUTOSCALE_DATA_TYPE");
     709           0 :                                 return false;
     710             :                             }
     711             :                         }
     712             :                         else
     713             :                         {
     714           4 :                             aosArrayCO.AddString(pszOption);
     715             :                         }
     716             :                     }
     717             :                 }
     718             :             }
     719             : 
     720          69 :             if (aosArrayCO.FetchNameValue("BLOCKSIZE") == nullptr)
     721             :             {
     722         136 :                 const auto anBlockSize = srcArray->GetBlockSize();
     723         136 :                 std::string osBlockSize;
     724          74 :                 for (auto v : anBlockSize)
     725             :                 {
     726          69 :                     if (v == 0)
     727             :                     {
     728          63 :                         osBlockSize.clear();
     729          63 :                         break;
     730             :                     }
     731           6 :                     if (!osBlockSize.empty())
     732           2 :                         osBlockSize += ',';
     733           6 :                     osBlockSize += std::to_string(v);
     734             :                 }
     735          68 :                 if (!osBlockSize.empty())
     736           3 :                     aosArrayCO.SetNameValue("BLOCKSIZE", osBlockSize.c_str());
     737             :             }
     738             : 
     739             :             auto oIterDimName =
     740          69 :                 mapSrcVariableNameToIndexedDimName.find(srcArray->GetName());
     741          69 :             const auto &srcArrayType = srcArray->GetDataType();
     742             : 
     743          69 :             std::shared_ptr<GDALMDArray> dstArray;
     744             : 
     745             :             // Only autoscale non-indexing variables
     746          69 :             bool bHasOffset = false;
     747          69 :             bool bHasScale = false;
     748           4 :             if (bAutoScale && srcArrayType.GetClass() == GEDTC_NUMERIC &&
     749           4 :                 (srcArrayType.GetNumericDataType() == GDT_Float16 ||
     750           2 :                  srcArrayType.GetNumericDataType() == GDT_Float32 ||
     751           0 :                  srcArrayType.GetNumericDataType() == GDT_Float64) &&
     752           2 :                 srcArray->GetOffset(&bHasOffset) == 0.0 && !bHasOffset &&
     753          73 :                 srcArray->GetScale(&bHasScale) == 1.0 && !bHasScale &&
     754          71 :                 oIterDimName == mapSrcVariableNameToIndexedDimName.end())
     755             :             {
     756           2 :                 constexpr bool bApproxOK = false;
     757           2 :                 constexpr bool bForce = true;
     758           2 :                 double dfMin = 0.0;
     759           2 :                 double dfMax = 0.0;
     760           2 :                 if (srcArray->GetStatistics(bApproxOK, bForce, &dfMin, &dfMax,
     761             :                                             nullptr, nullptr, nullptr, nullptr,
     762           2 :                                             nullptr) != CE_None)
     763             :                 {
     764           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     765             :                              "Could not retrieve statistics for array %s",
     766           0 :                              srcArray->GetName().c_str());
     767           0 :                     return false;
     768             :                 }
     769           2 :                 double dfDTMin = 0;
     770           2 :                 double dfDTMax = 0;
     771             : #define setDTMinMax(ctype)                                                     \
     772             :     do                                                                         \
     773             :     {                                                                          \
     774             :         dfDTMin = static_cast<double>(cpl::NumericLimits<ctype>::lowest());    \
     775             :         dfDTMax = static_cast<double>(cpl::NumericLimits<ctype>::max());       \
     776             :     } while (0)
     777             : 
     778           2 :                 switch (eAutoScaleType)
     779             :                 {
     780           0 :                     case GDT_UInt8:
     781           0 :                         setDTMinMax(GByte);
     782           0 :                         break;
     783           0 :                     case GDT_Int8:
     784           0 :                         setDTMinMax(GInt8);
     785           0 :                         break;
     786           1 :                     case GDT_UInt16:
     787           1 :                         setDTMinMax(GUInt16);
     788           1 :                         break;
     789           1 :                     case GDT_Int16:
     790           1 :                         setDTMinMax(GInt16);
     791           1 :                         break;
     792           0 :                     case GDT_UInt32:
     793           0 :                         setDTMinMax(GUInt32);
     794           0 :                         break;
     795           0 :                     case GDT_Int32:
     796           0 :                         setDTMinMax(GInt32);
     797           0 :                         break;
     798           0 :                     case GDT_UInt64:
     799           0 :                         setDTMinMax(std::uint64_t);
     800           0 :                         break;
     801           0 :                     case GDT_Int64:
     802           0 :                         setDTMinMax(std::int64_t);
     803           0 :                         break;
     804           0 :                     case GDT_Float16:
     805             :                     case GDT_Float32:
     806             :                     case GDT_Float64:
     807             :                     case GDT_Unknown:
     808             :                     case GDT_CInt16:
     809             :                     case GDT_CInt32:
     810             :                     case GDT_CFloat16:
     811             :                     case GDT_CFloat32:
     812             :                     case GDT_CFloat64:
     813             :                     case GDT_TypeCount:
     814           0 :                         CPLAssert(false);
     815             :                 }
     816             : 
     817             :                 dstArray =
     818           4 :                     CreateMDArray(srcArray->GetName(), dstArrayDims,
     819           4 :                                   GDALExtendedDataType::Create(eAutoScaleType),
     820           4 :                                   aosArrayCO.List());
     821           2 :                 if (!dstArray)
     822           0 :                     return !bStrict;
     823             : 
     824           2 :                 if (srcArray->GetRawNoDataValue() != nullptr)
     825             :                 {
     826             :                     // If there's a nodata value in the source array, reserve
     827             :                     // DTMax for that purpose in the target scaled array
     828           1 :                     if (!dstArray->SetNoDataValue(dfDTMax))
     829             :                     {
     830           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     831             :                                  "Cannot set nodata value");
     832           0 :                         return false;
     833             :                     }
     834           1 :                     dfDTMax--;
     835             :                 }
     836           2 :                 const double dfScale =
     837           2 :                     dfMax > dfMin ? (dfMax - dfMin) / (dfDTMax - dfDTMin) : 1.0;
     838           2 :                 const double dfOffset = dfMin - dfDTMin * dfScale;
     839             : 
     840           4 :                 if (!dstArray->SetOffset(dfOffset) ||
     841           2 :                     !dstArray->SetScale(dfScale))
     842             :                 {
     843           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     844             :                              "Cannot set scale/offset");
     845           0 :                     return false;
     846             :                 }
     847             : 
     848           2 :                 auto poUnscaled = dstArray->GetUnscaled();
     849           2 :                 if (srcArray->GetRawNoDataValue() != nullptr)
     850             :                 {
     851           1 :                     poUnscaled->SetNoDataValue(
     852             :                         srcArray->GetNoDataValueAsDouble());
     853             :                 }
     854             : 
     855             :                 // Copy source array into unscaled array
     856           4 :                 if (!poUnscaled->CopyFrom(poSrcDS, srcArray.get(), bStrict,
     857             :                                           nCurCost, nTotalCost, pfnProgress,
     858           2 :                                           pProgressData))
     859             :                 {
     860           0 :                     return false;
     861             :                 }
     862             :             }
     863             :             else
     864             :             {
     865         134 :                 dstArray = CreateMDArray(srcArray->GetName(), dstArrayDims,
     866         134 :                                          srcArrayType, aosArrayCO.List());
     867          67 :                 if (!dstArray)
     868           0 :                     return !bStrict;
     869             : 
     870         134 :                 if (!dstArray->CopyFrom(poSrcDS, srcArray.get(), bStrict,
     871             :                                         nCurCost, nTotalCost, pfnProgress,
     872          67 :                                         pProgressData))
     873             :                 {
     874           0 :                     return false;
     875             :                 }
     876             :             }
     877             : 
     878             :             // If this array is the indexing variable of a dimension, link them
     879             :             // together.
     880          69 :             if (oIterDimName != mapSrcVariableNameToIndexedDimName.end())
     881             :             {
     882             :                 auto oCorrespondingDimIter =
     883          35 :                     mapExistingDstDims.find(oIterDimName->second);
     884          35 :                 if (oCorrespondingDimIter != mapExistingDstDims.end())
     885             :                 {
     886             :                     CPLErrorStateBackuper oErrorStateBackuper(
     887          35 :                         CPLQuietErrorHandler);
     888          70 :                     oCorrespondingDimIter->second->SetIndexingVariable(
     889          35 :                         std::move(dstArray));
     890             :                 }
     891             :             }
     892             : 
     893          69 :             return true;
     894          33 :         };
     895             : 
     896          66 :         const auto arrayNames = poSrcGroup->GetMDArrayNames();
     897             : 
     898             :         // Start by copying arrays that are indexing variables of dimensions
     899         102 :         for (const auto &name : arrayNames)
     900             :         {
     901          69 :             auto srcArray = poSrcGroup->OpenMDArray(name);
     902          69 :             EXIT_OR_CONTINUE_IF_NULL(srcArray);
     903             : 
     904          69 :             if (cpl::contains(mapSrcVariableNameToIndexedDimName,
     905         173 :                               srcArray->GetName()) &&
     906         104 :                 !OpenMDArray(name))
     907             :             {
     908          35 :                 if (!CopyArray(srcArray))
     909           0 :                     return false;
     910             :             }
     911             :         }
     912             : 
     913             :         // Then copy regular arrays
     914         102 :         for (const auto &name : arrayNames)
     915             :         {
     916          69 :             auto srcArray = poSrcGroup->OpenMDArray(name);
     917          69 :             EXIT_OR_CONTINUE_IF_NULL(srcArray);
     918             : 
     919          69 :             if (!cpl::contains(mapSrcVariableNameToIndexedDimName,
     920         172 :                                srcArray->GetName()) &&
     921         103 :                 !OpenMDArray(name))
     922             :             {
     923          34 :                 if (!CopyArray(srcArray))
     924           0 :                     return false;
     925             :             }
     926             :         }
     927             : 
     928          66 :         const auto groupNames = poSrcGroup->GetGroupNames();
     929          39 :         for (const auto &name : groupNames)
     930             :         {
     931           6 :             auto srcSubGroup = poSrcGroup->OpenGroup(name);
     932           6 :             EXIT_OR_CONTINUE_IF_NULL(srcSubGroup);
     933           6 :             auto dstSubGroup = CreateGroup(name);
     934           6 :             EXIT_OR_CONTINUE_IF_NULL(dstSubGroup);
     935          12 :             if (!dstSubGroup->CopyFrom(
     936             :                     poDstRootGroup, poSrcDS, srcSubGroup, bStrict, nCurCost,
     937           6 :                     nTotalCost, pfnProgress, pProgressData, papszOptions))
     938           0 :                 return false;
     939             :         }
     940             : 
     941          33 :         if (!pfnProgress(double(nCurCost) / nTotalCost, "", pProgressData))
     942           0 :             return false;
     943             : 
     944          33 :         return true;
     945             :     }
     946           0 :     catch (const std::exception &e)
     947             :     {
     948           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
     949           0 :         return false;
     950             :     }
     951             : }
     952             : 
     953             : /************************************************************************/
     954             : /*                         GetInnerMostGroup()                          */
     955             : /************************************************************************/
     956             : 
     957             : //! @cond Doxygen_Suppress
     958             : const GDALGroup *
     959        2308 : GDALGroup::GetInnerMostGroup(const std::string &osPathOrArrayOrDim,
     960             :                              std::shared_ptr<GDALGroup> &curGroupHolder,
     961             :                              std::string &osLastPart) const
     962             : {
     963        2308 :     if (osPathOrArrayOrDim.empty() || osPathOrArrayOrDim[0] != '/')
     964           8 :         return nullptr;
     965        2300 :     const GDALGroup *poCurGroup = this;
     966             :     CPLStringList aosTokens(
     967        4600 :         CSLTokenizeString2(osPathOrArrayOrDim.c_str(), "/", 0));
     968        2300 :     if (aosTokens.size() == 0)
     969             :     {
     970             :         // "/" case: the root group itself is the innermost group
     971           2 :         osLastPart.clear();
     972           2 :         return poCurGroup;
     973             :     }
     974             : 
     975        2831 :     for (int i = 0; i < aosTokens.size() - 1; i++)
     976             :     {
     977         545 :         curGroupHolder = poCurGroup->OpenGroup(aosTokens[i], nullptr);
     978         545 :         if (!curGroupHolder)
     979             :         {
     980          12 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
     981             :                      aosTokens[i]);
     982          12 :             return nullptr;
     983             :         }
     984         533 :         poCurGroup = curGroupHolder.get();
     985             :     }
     986        2286 :     osLastPart = aosTokens[aosTokens.size() - 1];
     987        2286 :     return poCurGroup;
     988             : }
     989             : 
     990             : //! @endcond
     991             : 
     992             : /************************************************************************/
     993             : /*                      OpenMDArrayFromFullname()                       */
     994             : /************************************************************************/
     995             : 
     996             : /** Get an array from its fully qualified name */
     997             : std::shared_ptr<GDALMDArray>
     998         813 : GDALGroup::OpenMDArrayFromFullname(const std::string &osFullName,
     999             :                                    CSLConstList papszOptions) const
    1000             : {
    1001        1626 :     std::string osName;
    1002         813 :     std::shared_ptr<GDALGroup> curGroupHolder;
    1003         813 :     auto poGroup(GetInnerMostGroup(osFullName, curGroupHolder, osName));
    1004         813 :     if (poGroup == nullptr)
    1005          12 :         return nullptr;
    1006         801 :     return poGroup->OpenMDArray(osName, papszOptions);
    1007             : }
    1008             : 
    1009             : /************************************************************************/
    1010             : /*                     OpenAttributeFromFullname()                      */
    1011             : /************************************************************************/
    1012             : 
    1013             : /** Get an attribute from its fully qualified name */
    1014             : std::shared_ptr<GDALAttribute>
    1015           9 : GDALGroup::OpenAttributeFromFullname(const std::string &osFullName,
    1016             :                                      CSLConstList papszOptions) const
    1017             : {
    1018           9 :     const auto pos = osFullName.rfind('/');
    1019           9 :     if (pos == std::string::npos)
    1020           0 :         return nullptr;
    1021          18 :     const std::string attrName = osFullName.substr(pos + 1);
    1022           9 :     if (pos == 0)
    1023           2 :         return GetAttribute(attrName);
    1024          14 :     const std::string container = osFullName.substr(0, pos);
    1025          14 :     auto poArray = OpenMDArrayFromFullname(container, papszOptions);
    1026           7 :     if (poArray)
    1027           4 :         return poArray->GetAttribute(attrName);
    1028           6 :     auto poGroup = OpenGroupFromFullname(container, papszOptions);
    1029           3 :     if (poGroup)
    1030           1 :         return poGroup->GetAttribute(attrName);
    1031           2 :     return nullptr;
    1032             : }
    1033             : 
    1034             : /************************************************************************/
    1035             : /*                           ResolveMDArray()                           */
    1036             : /************************************************************************/
    1037             : 
    1038             : /** Locate an array in a group and its subgroups by name.
    1039             :  *
    1040             :  * If osName is a fully qualified name, then OpenMDArrayFromFullname() is first
    1041             :  * used
    1042             :  * Otherwise the search will start from the group identified by osStartingPath,
    1043             :  * and an array whose name is osName will be looked for in this group (if
    1044             :  * osStartingPath is empty or "/", then the current group is used). If there
    1045             :  * is no match, then a recursive descendant search will be made in its
    1046             :  * subgroups. If there is no match in the subgroups, then the parent (if
    1047             :  * existing) of the group pointed by osStartingPath will be used as the new
    1048             :  * starting point for the search.
    1049             :  *
    1050             :  * @param osName name, qualified or not
    1051             :  * @param osStartingPath fully qualified name of the (sub-)group from which
    1052             :  *                       the search should be started. If this is a non-empty
    1053             :  *                       string, the group on which this method is called should
    1054             :  *                       nominally be the root group (otherwise the path will
    1055             :  *                       be interpreted as from the current group)
    1056             :  * @param papszOptions options to pass to OpenMDArray()
    1057             :  * @since GDAL 3.2
    1058             :  */
    1059             : std::shared_ptr<GDALMDArray>
    1060          19 : GDALGroup::ResolveMDArray(const std::string &osName,
    1061             :                           const std::string &osStartingPath,
    1062             :                           CSLConstList papszOptions) const
    1063             : {
    1064          19 :     if (!osName.empty() && osName[0] == '/')
    1065             :     {
    1066           1 :         auto poArray = OpenMDArrayFromFullname(osName, papszOptions);
    1067           1 :         if (poArray)
    1068           1 :             return poArray;
    1069             :     }
    1070          36 :     std::string osPath(osStartingPath);
    1071          36 :     std::set<std::string> oSetAlreadyVisited;
    1072             : 
    1073             :     while (true)
    1074             :     {
    1075           0 :         std::shared_ptr<GDALGroup> curGroupHolder;
    1076           0 :         std::shared_ptr<GDALGroup> poGroup;
    1077             : 
    1078          22 :         std::queue<std::shared_ptr<GDALGroup>> oQueue;
    1079          22 :         bool goOn = false;
    1080          22 :         if (osPath.empty() || osPath == "/")
    1081             :         {
    1082          11 :             goOn = true;
    1083             :         }
    1084             :         else
    1085             :         {
    1086          22 :             std::string osLastPart;
    1087             :             const GDALGroup *poGroupPtr =
    1088          11 :                 GetInnerMostGroup(osPath, curGroupHolder, osLastPart);
    1089          11 :             if (poGroupPtr)
    1090          11 :                 poGroup = poGroupPtr->OpenGroup(osLastPart);
    1091          22 :             if (poGroup &&
    1092          22 :                 !cpl::contains(oSetAlreadyVisited, poGroup->GetFullName()))
    1093             :             {
    1094          11 :                 oQueue.push(poGroup);
    1095          11 :                 goOn = true;
    1096             :             }
    1097             :         }
    1098             : 
    1099          22 :         if (goOn)
    1100             :         {
    1101          17 :             do
    1102             :             {
    1103             :                 const GDALGroup *groupPtr;
    1104          39 :                 if (!oQueue.empty())
    1105             :                 {
    1106          28 :                     poGroup = oQueue.front();
    1107          28 :                     oQueue.pop();
    1108          28 :                     groupPtr = poGroup.get();
    1109             :                 }
    1110             :                 else
    1111             :                 {
    1112          11 :                     groupPtr = this;
    1113             :                 }
    1114             : 
    1115          39 :                 auto poArray = groupPtr->OpenMDArray(osName, papszOptions);
    1116          39 :                 if (poArray)
    1117          16 :                     return poArray;
    1118             : 
    1119          46 :                 const auto aosGroupNames = groupPtr->GetGroupNames();
    1120          47 :                 for (const auto &osGroupName : aosGroupNames)
    1121             :                 {
    1122          48 :                     auto poSubGroup = groupPtr->OpenGroup(osGroupName);
    1123          48 :                     if (poSubGroup && !cpl::contains(oSetAlreadyVisited,
    1124          48 :                                                      poSubGroup->GetFullName()))
    1125             :                     {
    1126          24 :                         oQueue.push(poSubGroup);
    1127          24 :                         oSetAlreadyVisited.insert(poSubGroup->GetFullName());
    1128             :                     }
    1129             :                 }
    1130          23 :             } while (!oQueue.empty());
    1131             :         }
    1132             : 
    1133           6 :         if (osPath.empty() || osPath == "/")
    1134           2 :             break;
    1135             : 
    1136           4 :         const auto nPos = osPath.rfind('/');
    1137           4 :         if (nPos == 0)
    1138           1 :             osPath = "/";
    1139             :         else
    1140             :         {
    1141           3 :             if (nPos == std::string::npos)
    1142           0 :                 break;
    1143           3 :             osPath.resize(nPos);
    1144             :         }
    1145           4 :     }
    1146           2 :     return nullptr;
    1147             : }
    1148             : 
    1149             : /************************************************************************/
    1150             : /*                       OpenGroupFromFullname()                        */
    1151             : /************************************************************************/
    1152             : 
    1153             : /** Get a group from its fully qualified name.
    1154             :  * @since GDAL 3.2
    1155             :  */
    1156             : std::shared_ptr<GDALGroup>
    1157        1272 : GDALGroup::OpenGroupFromFullname(const std::string &osFullName,
    1158             :                                  CSLConstList papszOptions) const
    1159             : {
    1160        2544 :     std::string osName;
    1161        1272 :     std::shared_ptr<GDALGroup> curGroupHolder;
    1162        1272 :     auto poGroup(GetInnerMostGroup(osFullName, curGroupHolder, osName));
    1163        1272 :     if (poGroup == nullptr)
    1164           4 :         return nullptr;
    1165        1268 :     if (osName.empty())
    1166           2 :         return m_pSelf.lock();
    1167        1266 :     return poGroup->OpenGroup(osName, papszOptions);
    1168             : }
    1169             : 
    1170             : /************************************************************************/
    1171             : /*                     OpenDimensionFromFullname()                      */
    1172             : /************************************************************************/
    1173             : 
    1174             : /** Get a dimension from its fully qualified name */
    1175             : std::shared_ptr<GDALDimension>
    1176         212 : GDALGroup::OpenDimensionFromFullname(const std::string &osFullName) const
    1177             : {
    1178         424 :     std::string osName;
    1179         212 :     std::shared_ptr<GDALGroup> curGroupHolder;
    1180         212 :     auto poGroup(GetInnerMostGroup(osFullName, curGroupHolder, osName));
    1181         212 :     if (poGroup == nullptr)
    1182           4 :         return nullptr;
    1183         416 :     auto dims(poGroup->GetDimensions());
    1184         360 :     for (auto &dim : dims)
    1185             :     {
    1186         308 :         if (dim->GetName() == osName)
    1187         156 :             return dim;
    1188             :     }
    1189          52 :     return nullptr;
    1190             : }
    1191             : 
    1192             : /************************************************************************/
    1193             : /*                          ClearStatistics()                           */
    1194             : /************************************************************************/
    1195             : 
    1196             : /**
    1197             :  * \brief Clear statistics.
    1198             :  *
    1199             :  * @since GDAL 3.4
    1200             :  */
    1201           0 : void GDALGroup::ClearStatistics()
    1202             : {
    1203           0 :     auto groupNames = GetGroupNames();
    1204           0 :     for (const auto &name : groupNames)
    1205             :     {
    1206           0 :         auto subGroup = OpenGroup(name);
    1207           0 :         if (subGroup)
    1208             :         {
    1209           0 :             subGroup->ClearStatistics();
    1210             :         }
    1211             :     }
    1212             : 
    1213           0 :     auto arrayNames = GetMDArrayNames();
    1214           0 :     for (const auto &name : arrayNames)
    1215             :     {
    1216           0 :         auto array = OpenMDArray(name);
    1217           0 :         if (array)
    1218             :         {
    1219           0 :             array->ClearStatistics();
    1220             :         }
    1221             :     }
    1222           0 : }
    1223             : 
    1224             : /************************************************************************/
    1225             : /*                               Rename()                               */
    1226             : /************************************************************************/
    1227             : 
    1228             : /** Rename the group.
    1229             :  *
    1230             :  * This is not implemented by all drivers.
    1231             :  *
    1232             :  * Drivers known to implement it: MEM, netCDF, ZARR.
    1233             :  *
    1234             :  * This is the same as the C function GDALGroupRename().
    1235             :  *
    1236             :  * @param osNewName New name.
    1237             :  *
    1238             :  * @return true in case of success
    1239             :  * @since GDAL 3.8
    1240             :  */
    1241           0 : bool GDALGroup::Rename(CPL_UNUSED const std::string &osNewName)
    1242             : {
    1243           0 :     CPLError(CE_Failure, CPLE_NotSupported, "Rename() not implemented");
    1244           0 :     return false;
    1245             : }
    1246             : 
    1247             : /************************************************************************/
    1248             : /*                             BaseRename()                             */
    1249             : /************************************************************************/
    1250             : 
    1251             : //! @cond Doxygen_Suppress
    1252           8 : void GDALGroup::BaseRename(const std::string &osNewName)
    1253             : {
    1254           8 :     m_osFullName.resize(m_osFullName.size() - m_osName.size());
    1255           8 :     m_osFullName += osNewName;
    1256           8 :     m_osName = osNewName;
    1257             : 
    1258           8 :     NotifyChildrenOfRenaming();
    1259           8 : }
    1260             : 
    1261             : //! @endcond
    1262             : 
    1263             : /************************************************************************/
    1264             : /*                           ParentRenamed()                            */
    1265             : /************************************************************************/
    1266             : 
    1267             : //! @cond Doxygen_Suppress
    1268           7 : void GDALGroup::ParentRenamed(const std::string &osNewParentFullName)
    1269             : {
    1270           7 :     m_osFullName = osNewParentFullName;
    1271           7 :     m_osFullName += "/";
    1272           7 :     m_osFullName += m_osName;
    1273             : 
    1274           7 :     NotifyChildrenOfRenaming();
    1275           7 : }
    1276             : 
    1277             : //! @endcond
    1278             : 
    1279             : /************************************************************************/
    1280             : /*                              Deleted()                               */
    1281             : /************************************************************************/
    1282             : 
    1283             : //! @cond Doxygen_Suppress
    1284          28 : void GDALGroup::Deleted()
    1285             : {
    1286          28 :     m_bValid = false;
    1287             : 
    1288          28 :     NotifyChildrenOfDeletion();
    1289          28 : }
    1290             : 
    1291             : //! @endcond
    1292             : 
    1293             : /************************************************************************/
    1294             : /*                           ParentDeleted()                            */
    1295             : /************************************************************************/
    1296             : 
    1297             : //! @cond Doxygen_Suppress
    1298           3 : void GDALGroup::ParentDeleted()
    1299             : {
    1300           3 :     Deleted();
    1301           3 : }
    1302             : 
    1303             : //! @endcond
    1304             : 
    1305             : /************************************************************************/
    1306             : /*                     CheckValidAndErrorOutIfNot()                     */
    1307             : /************************************************************************/
    1308             : 
    1309             : //! @cond Doxygen_Suppress
    1310       40490 : bool GDALGroup::CheckValidAndErrorOutIfNot() const
    1311             : {
    1312       40490 :     if (!m_bValid)
    1313             :     {
    1314          14 :         CPLError(CE_Failure, CPLE_AppDefined,
    1315             :                  "This object has been deleted. No action on it is possible");
    1316             :     }
    1317       40490 :     return m_bValid;
    1318             : }
    1319             : 
    1320             : //! @endcond

Generated by: LCOV version 1.14