LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfmultidim.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2243 2451 91.5 %
Date: 2025-06-19 12:30:01 Functions: 135 141 95.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  netCDF read/write Driver
       4             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       5             :  *
       6             :  ******************************************************************************
       7             :  * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
       8             :  *
       9             :  * SPDX-License-Identifier: MIT
      10             :  ****************************************************************************/
      11             : 
      12             : #include <algorithm>
      13             : #include <cinttypes>
      14             : #include <limits>
      15             : #include <map>
      16             : 
      17             : #include "gdal_rat.h"
      18             : #include "memdataset.h"
      19             : 
      20             : #include "netcdfdataset.h"
      21             : #include "netcdfdrivercore.h"
      22             : 
      23             : #include "netcdf_mem.h"
      24             : 
      25             : static bool BuildDataType(int gid, int varid, int nVarType,
      26             :                           std::unique_ptr<GDALExtendedDataType> &dt,
      27             :                           bool &bPerfectDataTypeMatch);
      28             : 
      29             : /************************************************************************/
      30             : /*                         netCDFSharedResources                        */
      31             : /************************************************************************/
      32             : 
      33             : class netCDFSharedResources
      34             : {
      35             :     friend class netCDFDataset;
      36             : 
      37             :     bool m_bImappIsInElements = true;
      38             :     bool m_bReadOnly = true;
      39             :     bool m_bIsNC4 = false;
      40             :     int m_cdfid = 0;
      41             : #ifdef ENABLE_NCDUMP
      42             :     bool m_bFileToDestroyAtClosing = false;
      43             : #endif
      44             :     CPLString m_osFilename{};
      45             : #ifdef ENABLE_UFFD
      46             :     cpl_uffd_context *m_pUffdCtx = nullptr;
      47             : #endif
      48             :     VSILFILE *m_fpVSIMEM = nullptr;
      49             :     bool m_bDefineMode = false;
      50             :     std::map<int, int> m_oMapDimIdToGroupId{};
      51             :     bool m_bIsInIndexingVariable = false;
      52             :     std::shared_ptr<GDALPamMultiDim> m_poPAM{};
      53             :     std::map<int, std::weak_ptr<GDALDimension>> m_oCachedDimensions{};
      54             : 
      55             :   public:
      56             :     explicit netCDFSharedResources(const std::string &osFilename);
      57             :     ~netCDFSharedResources();
      58             : 
      59         593 :     inline int GetCDFId() const
      60             :     {
      61         593 :         return m_cdfid;
      62             :     }
      63             : 
      64        1892 :     inline bool IsReadOnly() const
      65             :     {
      66        1892 :         return m_bReadOnly;
      67             :     }
      68             : 
      69         161 :     inline bool IsNC4() const
      70             :     {
      71         161 :         return m_bIsNC4;
      72             :     }
      73             : 
      74             :     bool SetDefineMode(bool bNewDefineMode);
      75             :     int GetBelongingGroupOfDim(int startgid, int dimid);
      76             : 
      77           7 :     inline bool GetImappIsInElements() const
      78             :     {
      79           7 :         return m_bImappIsInElements;
      80             :     }
      81             : 
      82        1136 :     void SetIsInGetIndexingVariable(bool b)
      83             :     {
      84        1136 :         m_bIsInIndexingVariable = b;
      85        1136 :     }
      86             : 
      87        1743 :     bool GetIsInIndexingVariable() const
      88             :     {
      89        1743 :         return m_bIsInIndexingVariable;
      90             :     }
      91             : 
      92         607 :     const std::string &GetFilename() const
      93             :     {
      94         607 :         return m_osFilename;
      95             :     }
      96             : 
      97        1669 :     const std::shared_ptr<GDALPamMultiDim> &GetPAM()
      98             :     {
      99        1669 :         return m_poPAM;
     100             :     }
     101             : 
     102        1584 :     void CacheDimension(int dimid, const std::shared_ptr<GDALDimension> &poDim)
     103             :     {
     104        1584 :         m_oCachedDimensions[dimid] = poDim;
     105        1584 :     }
     106             : 
     107        2363 :     std::shared_ptr<GDALDimension> GetCachedDimension(int dimid) const
     108             :     {
     109        2363 :         auto oIter = m_oCachedDimensions.find(dimid);
     110        2363 :         if (oIter == m_oCachedDimensions.end())
     111         408 :             return nullptr;
     112        1955 :         return oIter->second.lock();
     113             :     }
     114             : };
     115             : 
     116             : /************************************************************************/
     117             : /*                       netCDFSharedResources()                        */
     118             : /************************************************************************/
     119             : 
     120         238 : netCDFSharedResources::netCDFSharedResources(const std::string &osFilename)
     121             :     : m_bImappIsInElements(false), m_osFilename(osFilename),
     122         238 :       m_poPAM(std::make_shared<GDALPamMultiDim>(osFilename))
     123             : {
     124             :     // netcdf >= 4.4 uses imapp argument of nc_get/put_varm as a stride in
     125             :     // elements, whereas earlier versions use bytes.
     126             :     CPLStringList aosVersionNumbers(
     127         476 :         CSLTokenizeString2(nc_inq_libvers(), ".", 0));
     128         238 :     m_bImappIsInElements = false;
     129         238 :     if (aosVersionNumbers.size() >= 3)
     130             :     {
     131         238 :         m_bImappIsInElements =
     132         238 :             (atoi(aosVersionNumbers[0]) > 4 || atoi(aosVersionNumbers[1]) >= 4);
     133             :     }
     134         238 : }
     135             : 
     136             : /************************************************************************/
     137             : /*                       GetBelongingGroupOfDim()                       */
     138             : /************************************************************************/
     139             : 
     140        1522 : int netCDFSharedResources::GetBelongingGroupOfDim(int startgid, int dimid)
     141             : {
     142             :     // Am I missing a netCDF API to do this directly ?
     143        1522 :     auto oIter = m_oMapDimIdToGroupId.find(dimid);
     144        1522 :     if (oIter != m_oMapDimIdToGroupId.end())
     145        1219 :         return oIter->second;
     146             : 
     147         303 :     int gid = startgid;
     148             :     while (true)
     149             :     {
     150         318 :         int nbDims = 0;
     151         318 :         NCDF_ERR(nc_inq_ndims(gid, &nbDims));
     152         318 :         if (nbDims > 0)
     153             :         {
     154         303 :             std::vector<int> dimids(nbDims);
     155         303 :             NCDF_ERR(nc_inq_dimids(gid, &nbDims, &dimids[0], FALSE));
     156         653 :             for (int i = 0; i < nbDims; i++)
     157             :             {
     158         653 :                 m_oMapDimIdToGroupId[dimid] = gid;
     159         653 :                 if (dimids[i] == dimid)
     160         303 :                     return gid;
     161             :             }
     162             :         }
     163          15 :         int nParentGID = 0;
     164          15 :         if (nc_inq_grp_parent(gid, &nParentGID) != NC_NOERR)
     165           0 :             return startgid;
     166          15 :         gid = nParentGID;
     167          15 :     }
     168             : }
     169             : 
     170             : /************************************************************************/
     171             : /*                            SetDefineMode()                           */
     172             : /************************************************************************/
     173             : 
     174         984 : bool netCDFSharedResources::SetDefineMode(bool bNewDefineMode)
     175             : {
     176             :     // Do nothing if already in new define mode
     177             :     // or if dataset is in read-only mode or if dataset is NC4 format.
     178         984 :     if (m_bDefineMode == bNewDefineMode || m_bReadOnly || m_bIsNC4)
     179         970 :         return true;
     180             : 
     181          14 :     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) new=%d, old=%d", m_cdfid,
     182          14 :              static_cast<int>(bNewDefineMode), static_cast<int>(m_bDefineMode));
     183             : 
     184          14 :     m_bDefineMode = bNewDefineMode;
     185             : 
     186             :     int status;
     187          14 :     if (m_bDefineMode)
     188           8 :         status = nc_redef(m_cdfid);
     189             :     else
     190           6 :         status = nc_enddef(m_cdfid);
     191             : 
     192          14 :     NCDF_ERR(status);
     193          14 :     return status == NC_NOERR;
     194             : }
     195             : 
     196             : /************************************************************************/
     197             : /*                        netCDFAttributeHolder                         */
     198             : /************************************************************************/
     199             : 
     200             : class netCDFAttributeHolder CPL_NON_FINAL
     201             : {
     202             :   protected:
     203             :     std::map<std::string, GDALAttribute *> m_oMapAttributes{};
     204             : 
     205             :   public:
     206         705 :     void RegisterAttribute(GDALAttribute *poAttr)
     207             :     {
     208         705 :         m_oMapAttributes[poAttr->GetName()] = poAttr;
     209         705 :     }
     210             : 
     211         641 :     void UnRegisterAttribute(GDALAttribute *poAttr)
     212             :     {
     213         641 :         m_oMapAttributes.erase(poAttr->GetName());
     214         641 :     }
     215             : };
     216             : 
     217             : /************************************************************************/
     218             : /*                           netCDFGroup                                */
     219             : /************************************************************************/
     220             : 
     221             : class netCDFGroup final : public GDALGroup, public netCDFAttributeHolder
     222             : {
     223             :     std::shared_ptr<netCDFSharedResources> m_poShared;
     224             :     int m_gid = 0;
     225             :     CPLStringList m_aosStructuralInfo{};
     226             :     std::weak_ptr<netCDFGroup> m_poParent{};
     227             :     std::set<GDALGroup *> m_oSetGroups{};
     228             :     std::set<GDALDimension *> m_oSetDimensions{};
     229             :     std::set<GDALMDArray *> m_oSetArrays{};
     230             : 
     231         593 :     static std::string retrieveName(int gid)
     232             :     {
     233         593 :         CPLMutexHolderD(&hNCMutex);
     234         593 :         char szName[NC_MAX_NAME + 1] = {};
     235         593 :         NCDF_ERR(nc_inq_grpname(gid, szName));
     236        1186 :         return szName;
     237             :     }
     238             : 
     239          48 :     void RegisterSubGroup(GDALGroup *poSubGroup)
     240             :     {
     241          48 :         m_oSetGroups.insert(poSubGroup);
     242          48 :     }
     243             : 
     244          32 :     void UnRegisterSubGroup(GDALGroup *poSubGroup)
     245             :     {
     246          32 :         m_oSetGroups.erase(poSubGroup);
     247          32 :     }
     248             : 
     249             :   protected:
     250             :     friend class netCDFDimension;
     251             : 
     252         515 :     void RegisterDimension(GDALDimension *poDim)
     253             :     {
     254         515 :         m_oSetDimensions.insert(poDim);
     255         515 :     }
     256             : 
     257         235 :     void UnRegisterDimension(GDALDimension *poDim)
     258             :     {
     259         235 :         m_oSetDimensions.erase(poDim);
     260         235 :     }
     261             : 
     262             :     friend class netCDFVariable;
     263             : 
     264         546 :     void RegisterArray(GDALMDArray *poArray)
     265             :     {
     266         546 :         m_oSetArrays.insert(poArray);
     267         546 :     }
     268             : 
     269         335 :     void UnRegisterArray(GDALMDArray *poArray)
     270             :     {
     271         335 :         m_oSetArrays.erase(poArray);
     272         335 :     }
     273             : 
     274             :     void NotifyChildrenOfRenaming() override;
     275             : 
     276             :     netCDFGroup(const std::shared_ptr<netCDFSharedResources> &poShared,
     277             :                 int gid);
     278             : 
     279             :   public:
     280             :     ~netCDFGroup();
     281             : 
     282             :     static std::shared_ptr<netCDFGroup>
     283             :     Create(const std::shared_ptr<netCDFSharedResources> &poShared, int cdfid);
     284             : 
     285             :     static std::shared_ptr<netCDFGroup>
     286             :     Create(const std::shared_ptr<netCDFSharedResources> &poShared,
     287             :            const std::shared_ptr<netCDFGroup> &poParent, int nSubGroupId);
     288             : 
     289             :     std::vector<std::string>
     290             :     GetGroupNames(CSLConstList papszOptions) const override;
     291             :     std::shared_ptr<GDALGroup>
     292             :     OpenGroup(const std::string &osName,
     293             :               CSLConstList papszOptions = nullptr) const override;
     294             : 
     295             :     std::vector<std::string>
     296             :     GetMDArrayNames(CSLConstList papszOptions) const override;
     297             :     std::shared_ptr<GDALMDArray>
     298             :     OpenMDArray(const std::string &osName,
     299             :                 CSLConstList papszOptions) const override;
     300             : 
     301             :     std::vector<std::shared_ptr<GDALDimension>>
     302             :     GetDimensions(CSLConstList papszOptions) const override;
     303             : 
     304             :     std::shared_ptr<GDALAttribute>
     305             :     GetAttribute(const std::string &osName) const override;
     306             : 
     307             :     std::vector<std::shared_ptr<GDALAttribute>>
     308             :     GetAttributes(CSLConstList papszOptions) const override;
     309             : 
     310             :     std::shared_ptr<GDALGroup> CreateGroup(const std::string &osName,
     311             :                                            CSLConstList papszOptions) override;
     312             : 
     313             :     std::shared_ptr<GDALDimension>
     314             :     CreateDimension(const std::string &osName, const std::string &osType,
     315             :                     const std::string &osDirection, GUInt64 nSize,
     316             :                     CSLConstList papszOptions) override;
     317             : 
     318             :     std::shared_ptr<GDALMDArray> CreateMDArray(
     319             :         const std::string &osName,
     320             :         const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
     321             :         const GDALExtendedDataType &oDataType,
     322             :         CSLConstList papszOptions) override;
     323             : 
     324             :     std::shared_ptr<GDALAttribute>
     325             :     CreateAttribute(const std::string &osName,
     326             :                     const std::vector<GUInt64> &anDimensions,
     327             :                     const GDALExtendedDataType &oDataType,
     328             :                     CSLConstList papszOptions) override;
     329             : 
     330             :     bool DeleteAttribute(const std::string &osName,
     331             :                          CSLConstList papszOptions) override;
     332             : 
     333             :     CSLConstList GetStructuralInfo() const override;
     334             : 
     335             :     void ClearStatistics() override;
     336             : 
     337             :     bool Rename(const std::string &osNewName) override;
     338             : };
     339             : 
     340             : /************************************************************************/
     341             : /*                   netCDFVirtualGroupBySameDimension                  */
     342             : /************************************************************************/
     343             : 
     344             : class netCDFVirtualGroupBySameDimension final : public GDALGroup
     345             : {
     346             :     // the real group to which we derived this virtual group from
     347             :     std::shared_ptr<netCDFGroup> m_poGroup;
     348             :     std::string m_osDimName{};
     349             : 
     350             :   protected:
     351             :     netCDFVirtualGroupBySameDimension(
     352             :         const std::shared_ptr<netCDFGroup> &poGroup,
     353             :         const std::string &osDimName);
     354             : 
     355             :   public:
     356             :     static std::shared_ptr<netCDFVirtualGroupBySameDimension>
     357             :     Create(const std::shared_ptr<netCDFGroup> &poGroup,
     358             :            const std::string &osDimName);
     359             : 
     360             :     std::vector<std::string>
     361             :     GetMDArrayNames(CSLConstList papszOptions) const override;
     362             :     std::shared_ptr<GDALMDArray>
     363             :     OpenMDArray(const std::string &osName,
     364             :                 CSLConstList papszOptions) const override;
     365             : };
     366             : 
     367             : /************************************************************************/
     368             : /*                         netCDFDimension                              */
     369             : /************************************************************************/
     370             : 
     371             : class netCDFDimension final : public GDALDimension
     372             : {
     373             :     std::shared_ptr<netCDFSharedResources> m_poShared;
     374             :     int m_gid = 0;
     375             :     int m_dimid = 0;
     376             :     std::weak_ptr<netCDFGroup> m_poParent{};
     377             : 
     378        1721 :     static std::string retrieveName(int cfid, int dimid)
     379             :     {
     380        1721 :         CPLMutexHolderD(&hNCMutex);
     381        1721 :         char szName[NC_MAX_NAME + 1] = {};
     382        1721 :         NCDF_ERR(nc_inq_dimname(cfid, dimid, szName));
     383        3442 :         return szName;
     384             :     }
     385             : 
     386        2042 :     static GUInt64 retrieveSize(int cfid, int dimid)
     387             :     {
     388        2042 :         CPLMutexHolderD(&hNCMutex);
     389        2042 :         size_t nDimLen = 0;
     390        2042 :         NCDF_ERR(nc_inq_dimlen(cfid, dimid, &nDimLen));
     391        4084 :         return nDimLen;
     392             :     }
     393             : 
     394             :   public:
     395             :     netCDFDimension(const std::shared_ptr<netCDFSharedResources> &poShared,
     396             :                     int cfid, int dimid, size_t nForcedSize,
     397             :                     const std::string &osType);
     398             : 
     399             :     ~netCDFDimension();
     400             : 
     401             :     static std::shared_ptr<netCDFDimension>
     402             :     Create(const std::shared_ptr<netCDFSharedResources> &poShared,
     403             :            const std::shared_ptr<netCDFGroup> &poParent, int cfid, int dimid,
     404             :            size_t nForcedSize, const std::string &osType);
     405             : 
     406             :     std::shared_ptr<GDALMDArray> GetIndexingVariable() const override;
     407             : 
     408         199 :     int GetId() const
     409             :     {
     410         199 :         return m_dimid;
     411             :     }
     412             : 
     413         454 :     GUInt64 GetActualSize() const
     414             :     {
     415         454 :         return retrieveSize(m_gid, m_dimid);
     416             :     }
     417             : 
     418           3 :     void SetSize(GUInt64 nNewSize)
     419             :     {
     420           3 :         m_nSize = nNewSize;
     421           3 :     }
     422             : 
     423             :     bool Rename(const std::string &osNewName) override;
     424             : };
     425             : 
     426             : /************************************************************************/
     427             : /*                         netCDFAttribute                              */
     428             : /************************************************************************/
     429             : 
     430             : class netCDFVariable;
     431             : 
     432             : class netCDFAttribute final : public GDALAttribute
     433             : {
     434             :     std::shared_ptr<netCDFSharedResources> m_poShared;
     435             :     std::weak_ptr<netCDFAttributeHolder> m_poParent;
     436             :     int m_gid = 0;
     437             :     int m_varid = 0;
     438             :     size_t m_nTextLength = 0;
     439             :     std::vector<std::shared_ptr<GDALDimension>> m_dims{};
     440             :     nc_type m_nAttType = NC_NAT;
     441             :     mutable std::unique_ptr<GDALExtendedDataType> m_dt;
     442             :     mutable bool m_bPerfectDataTypeMatch = false;
     443             : 
     444             :   protected:
     445             :     netCDFAttribute(const std::shared_ptr<netCDFSharedResources> &poShared,
     446             :                     int gid, int varid, const std::string &name);
     447             : 
     448             :     netCDFAttribute(const std::shared_ptr<netCDFSharedResources> &poShared,
     449             :                     int gid, int varid, const std::string &osName,
     450             :                     const std::vector<GUInt64> &anDimensions,
     451             :                     const GDALExtendedDataType &oDataType,
     452             :                     CSLConstList papszOptions);
     453             : 
     454             :     bool
     455             :     IRead(const GUInt64 *arrayStartIdx,    // array of size GetDimensionCount()
     456             :           const size_t *count,             // array of size GetDimensionCount()
     457             :           const GInt64 *arrayStep,         // step in elements
     458             :           const GPtrDiff_t *bufferStride,  // stride in elements
     459             :           const GDALExtendedDataType &bufferDataType,
     460             :           void *pDstBuffer) const override;
     461             : 
     462             :     bool
     463             :     IWrite(const GUInt64 *arrayStartIdx,    // array of size GetDimensionCount()
     464             :            const size_t *count,             // array of size GetDimensionCount()
     465             :            const GInt64 *arrayStep,         // step in elements
     466             :            const GPtrDiff_t *bufferStride,  // stride in elements
     467             :            const GDALExtendedDataType &bufferDataType,
     468             :            const void *pSrcBuffer) override;
     469             : 
     470             :   public:
     471             :     ~netCDFAttribute() override;
     472             : 
     473             :     static std::shared_ptr<netCDFAttribute>
     474             :     Create(const std::shared_ptr<netCDFSharedResources> &poShared,
     475             :            const std::shared_ptr<netCDFAttributeHolder> &poParent, int gid,
     476             :            int varid, const std::string &name);
     477             : 
     478             :     static std::shared_ptr<netCDFAttribute>
     479             :     Create(const std::shared_ptr<netCDFSharedResources> &poShared,
     480             :            const std::shared_ptr<netCDFAttributeHolder> &poParent, int gid,
     481             :            int varid, const std::string &osName,
     482             :            const std::vector<GUInt64> &anDimensions,
     483             :            const GDALExtendedDataType &oDataType, CSLConstList papszOptions);
     484             : 
     485             :     const std::vector<std::shared_ptr<GDALDimension>> &
     486        2641 :     GetDimensions() const override
     487             :     {
     488        2641 :         return m_dims;
     489             :     }
     490             : 
     491             :     const GDALExtendedDataType &GetDataType() const override;
     492             : 
     493             :     bool Rename(const std::string &osNewName) override;
     494             : };
     495             : 
     496             : /************************************************************************/
     497             : /*                         netCDFVariable                               */
     498             : /************************************************************************/
     499             : 
     500             : class netCDFVariable final : public GDALPamMDArray, public netCDFAttributeHolder
     501             : {
     502             :     std::shared_ptr<netCDFSharedResources> m_poShared;
     503             :     std::weak_ptr<netCDFGroup> m_poParent{};
     504             :     int m_gid = 0;
     505             :     int m_varid = 0;
     506             :     int m_nDims = 0;
     507             :     mutable std::vector<std::shared_ptr<GDALDimension>> m_dims{};
     508             :     mutable nc_type m_nVarType = NC_NAT;
     509             :     mutable std::unique_ptr<GDALExtendedDataType> m_dt;
     510             :     mutable bool m_bPerfectDataTypeMatch = false;
     511             :     mutable std::vector<GByte> m_abyNoData{};
     512             :     mutable bool m_bGetRawNoDataValueHasRun = false;
     513             :     bool m_bHasWrittenData = true;
     514             :     bool m_bUseDefaultFillAsNoData = false;
     515             :     std::string m_osUnit{};
     516             :     CPLStringList m_aosStructuralInfo{};
     517             :     mutable bool m_bSRSRead = false;
     518             :     mutable std::shared_ptr<OGRSpatialReference> m_poSRS{};
     519             :     bool m_bWriteGDALTags = true;
     520             :     size_t m_nTextLength = 0;
     521             :     mutable std::vector<GUInt64> m_cachedArrayStartIdx{};
     522             :     mutable std::vector<size_t> m_cachedCount{};
     523             :     mutable std::shared_ptr<GDALMDArray> m_poCachedArray{};
     524             : 
     525             :     void ConvertNCToGDAL(GByte *) const;
     526             :     void ConvertGDALToNC(GByte *) const;
     527             : 
     528             :     bool ReadOneElement(const GDALExtendedDataType &src_datatype,
     529             :                         const GDALExtendedDataType &bufferDataType,
     530             :                         const size_t *array_idx, void *pDstBuffer) const;
     531             : 
     532             :     bool WriteOneElement(const GDALExtendedDataType &dst_datatype,
     533             :                          const GDALExtendedDataType &bufferDataType,
     534             :                          const size_t *array_idx, const void *pSrcBuffer) const;
     535             : 
     536             :     template <typename BufferType, typename NCGetPutVar1FuncType,
     537             :               typename ReadOrWriteOneElementType>
     538             :     bool
     539             :     IReadWriteGeneric(const size_t *arrayStartIdx, const size_t *count,
     540             :                       const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
     541             :                       const GDALExtendedDataType &bufferDataType,
     542             :                       BufferType buffer, NCGetPutVar1FuncType NCGetPutVar1Func,
     543             :                       ReadOrWriteOneElementType ReadOrWriteOneElement) const;
     544             : 
     545             :     template <typename BufferType, typename NCGetPutVar1FuncType,
     546             :               typename NCGetPutVaraFuncType, typename NCGetPutVarmFuncType,
     547             :               typename ReadOrWriteOneElementType>
     548             :     bool IReadWrite(const bool bIsRead, const GUInt64 *arrayStartIdx,
     549             :                     const size_t *count, const GInt64 *arrayStep,
     550             :                     const GPtrDiff_t *bufferStride,
     551             :                     const GDALExtendedDataType &bufferDataType,
     552             :                     BufferType buffer, NCGetPutVar1FuncType NCGetPutVar1Func,
     553             :                     NCGetPutVaraFuncType NCGetPutVaraFunc,
     554             :                     NCGetPutVarmFuncType NCGetPutVarmFunc,
     555             :                     ReadOrWriteOneElementType ReadOrWriteOneElement) const;
     556             : 
     557             :   protected:
     558             :     netCDFVariable(const std::shared_ptr<netCDFSharedResources> &poShared,
     559             :                    int gid, int varid,
     560             :                    const std::vector<std::shared_ptr<GDALDimension>> &dims,
     561             :                    CSLConstList papszOptions);
     562             : 
     563             :     bool
     564             :     IRead(const GUInt64 *arrayStartIdx,    // array of size GetDimensionCount()
     565             :           const size_t *count,             // array of size GetDimensionCount()
     566             :           const GInt64 *arrayStep,         // step in elements
     567             :           const GPtrDiff_t *bufferStride,  // stride in elements
     568             :           const GDALExtendedDataType &bufferDataType,
     569             :           void *pDstBuffer) const override;
     570             : 
     571             :     bool
     572             :     IWrite(const GUInt64 *arrayStartIdx,    // array of size GetDimensionCount()
     573             :            const size_t *count,             // array of size GetDimensionCount()
     574             :            const GInt64 *arrayStep,         // step in elements
     575             :            const GPtrDiff_t *bufferStride,  // stride in elements
     576             :            const GDALExtendedDataType &bufferDataType,
     577             :            const void *pSrcBuffer) override;
     578             : 
     579             :     bool IAdviseRead(const GUInt64 *arrayStartIdx, const size_t *count,
     580             :                      CSLConstList papszOptions) const override;
     581             : 
     582             :     void NotifyChildrenOfRenaming() override;
     583             : 
     584             :     bool SetStatistics(bool bApproxStats, double dfMin, double dfMax,
     585             :                        double dfMean, double dfStdDev, GUInt64 nValidCount,
     586             :                        CSLConstList papszOptions) override;
     587             : 
     588             :   public:
     589             :     static std::shared_ptr<netCDFVariable>
     590        1668 :     Create(const std::shared_ptr<netCDFSharedResources> &poShared,
     591             :            const std::shared_ptr<netCDFGroup> &poParent, int gid, int varid,
     592             :            const std::vector<std::shared_ptr<GDALDimension>> &dims,
     593             :            CSLConstList papszOptions, bool bCreate)
     594             :     {
     595             :         auto var(std::shared_ptr<netCDFVariable>(
     596        1668 :             new netCDFVariable(poShared, gid, varid, dims, papszOptions)));
     597        1668 :         var->SetSelf(var);
     598        1668 :         var->m_poParent = poParent;
     599        1668 :         if (poParent)
     600         546 :             poParent->RegisterArray(var.get());
     601        1668 :         var->m_bHasWrittenData = !bCreate;
     602        1668 :         return var;
     603             :     }
     604             : 
     605             :     ~netCDFVariable() override;
     606             : 
     607        1253 :     void SetUseDefaultFillAsNoData(bool b)
     608             :     {
     609        1253 :         m_bUseDefaultFillAsNoData = b;
     610        1253 :     }
     611             : 
     612          36 :     bool IsWritable() const override
     613             :     {
     614          36 :         return !m_poShared->IsReadOnly();
     615             :     }
     616             : 
     617         607 :     const std::string &GetFilename() const override
     618             :     {
     619         607 :         return m_poShared->GetFilename();
     620             :     }
     621             : 
     622             :     const std::vector<std::shared_ptr<GDALDimension>> &
     623             :     GetDimensions() const override;
     624             : 
     625             :     const GDALExtendedDataType &GetDataType() const override;
     626             : 
     627             :     std::shared_ptr<GDALAttribute>
     628             :     GetAttribute(const std::string &osName) const override;
     629             : 
     630             :     std::vector<std::shared_ptr<GDALAttribute>>
     631             :     GetAttributes(CSLConstList papszOptions) const override;
     632             : 
     633             :     std::shared_ptr<GDALAttribute>
     634             :     CreateAttribute(const std::string &osName,
     635             :                     const std::vector<GUInt64> &anDimensions,
     636             :                     const GDALExtendedDataType &oDataType,
     637             :                     CSLConstList papszOptions) override;
     638             : 
     639             :     bool DeleteAttribute(const std::string &osName,
     640             :                          CSLConstList papszOptions) override;
     641             : 
     642             :     const void *GetRawNoDataValue() const override;
     643             : 
     644             :     bool SetRawNoDataValue(const void *) override;
     645             : 
     646             :     std::vector<GUInt64> GetBlockSize() const override;
     647             : 
     648             :     CSLConstList GetStructuralInfo() const override;
     649             : 
     650          90 :     const std::string &GetUnit() const override
     651             :     {
     652          90 :         return m_osUnit;
     653             :     }
     654             : 
     655             :     bool SetUnit(const std::string &osUnit) override;
     656             : 
     657             :     std::shared_ptr<OGRSpatialReference> GetSpatialRef() const override;
     658             : 
     659             :     bool SetSpatialRef(const OGRSpatialReference *poSRS) override;
     660             : 
     661             :     double GetOffset(bool *pbHasOffset,
     662             :                      GDALDataType *peStorageType) const override;
     663             : 
     664             :     double GetScale(bool *pbHasScale,
     665             :                     GDALDataType *peStorageType) const override;
     666             : 
     667             :     bool SetOffset(double dfOffset, GDALDataType eStorageType) override;
     668             : 
     669             :     bool SetScale(double dfScale, GDALDataType eStorageType) override;
     670             : 
     671             :     std::vector<std::shared_ptr<GDALMDArray>>
     672             :     GetCoordinateVariables() const override;
     673             : 
     674             :     bool Resize(const std::vector<GUInt64> &anNewDimSizes,
     675             :                 CSLConstList) override;
     676             : 
     677         257 :     int GetGroupId() const
     678             :     {
     679         257 :         return m_gid;
     680             :     }
     681             : 
     682         233 :     int GetVarId() const
     683             :     {
     684         233 :         return m_varid;
     685             :     }
     686             : 
     687        5146 :     static std::string retrieveName(int gid, int varid)
     688             :     {
     689        5146 :         CPLMutexHolderD(&hNCMutex);
     690        5146 :         char szName[NC_MAX_NAME + 1] = {};
     691        5146 :         NCDF_ERR(nc_inq_varname(gid, varid, szName));
     692       10292 :         return szName;
     693             :     }
     694             : 
     695             :     bool Rename(const std::string &osNewName) override;
     696             : 
     697           8 :     std::shared_ptr<GDALGroup> GetRootGroup() const override
     698             :     {
     699           8 :         return netCDFGroup::Create(m_poShared, nullptr, m_gid);
     700             :     }
     701             : };
     702             : 
     703             : /************************************************************************/
     704             : /*                       ~netCDFSharedResources()                       */
     705             : /************************************************************************/
     706             : 
     707         238 : netCDFSharedResources::~netCDFSharedResources()
     708             : {
     709         476 :     CPLMutexHolderD(&hNCMutex);
     710             : 
     711         238 :     if (m_cdfid > 0)
     712             :     {
     713             : #ifdef NCDF_DEBUG
     714             :         CPLDebug("GDAL_netCDF", "calling nc_close( %d)", m_cdfid);
     715             : #endif
     716         235 :         int status = GDAL_nc_close(m_cdfid);
     717         235 :         NCDF_ERR(status);
     718             :     }
     719             : 
     720             : #ifdef ENABLE_UFFD
     721         238 :     if (m_pUffdCtx)
     722             :     {
     723           1 :         NETCDF_UFFD_UNMAP(m_pUffdCtx);
     724             :     }
     725             : #endif
     726             : 
     727         238 :     if (m_fpVSIMEM)
     728           4 :         VSIFCloseL(m_fpVSIMEM);
     729             : 
     730             : #ifdef ENABLE_NCDUMP
     731         238 :     if (m_bFileToDestroyAtClosing)
     732           0 :         VSIUnlink(m_osFilename);
     733             : #endif
     734         238 : }
     735             : 
     736             : /************************************************************************/
     737             : /*                     NCDFGetParentGroupName()                         */
     738             : /************************************************************************/
     739             : 
     740         593 : static CPLString NCDFGetParentGroupName(int gid)
     741             : {
     742         593 :     int nParentGID = 0;
     743         593 :     if (nc_inq_grp_parent(gid, &nParentGID) != NC_NOERR)
     744        1086 :         return std::string();
     745          50 :     return NCDFGetGroupFullName(nParentGID);
     746             : }
     747             : 
     748             : /************************************************************************/
     749             : /*                             netCDFGroup()                            */
     750             : /************************************************************************/
     751             : 
     752         593 : netCDFGroup::netCDFGroup(const std::shared_ptr<netCDFSharedResources> &poShared,
     753         593 :                          int gid)
     754        1186 :     : GDALGroup(NCDFGetParentGroupName(gid), retrieveName(gid)),
     755        1779 :       m_poShared(poShared), m_gid(gid)
     756             : {
     757        1186 :     CPLMutexHolderD(&hNCMutex);
     758             : 
     759         593 :     if (m_gid == m_poShared->GetCDFId())
     760             :     {
     761         543 :         int nFormat = 0;
     762         543 :         NCDF_ERR(nc_inq_format(m_gid, &nFormat));
     763         543 :         if (nFormat == NC_FORMAT_CLASSIC)
     764             :         {
     765         152 :             m_aosStructuralInfo.SetNameValue("NC_FORMAT", "CLASSIC");
     766             :         }
     767             : #ifdef NC_FORMAT_64BIT_OFFSET
     768         391 :         else if (nFormat == NC_FORMAT_64BIT_OFFSET)
     769             :         {
     770           0 :             m_aosStructuralInfo.SetNameValue("NC_FORMAT", "64BIT_OFFSET");
     771             :         }
     772             : #endif
     773             : #ifdef NC_FORMAT_CDF5
     774         391 :         else if (nFormat == NC_FORMAT_CDF5)
     775             :         {
     776           0 :             m_aosStructuralInfo.SetNameValue("NC_FORMAT", "CDF5");
     777             :         }
     778             : #endif
     779         391 :         else if (nFormat == NC_FORMAT_NETCDF4)
     780             :         {
     781         389 :             m_aosStructuralInfo.SetNameValue("NC_FORMAT", "NETCDF4");
     782             :         }
     783           2 :         else if (nFormat == NC_FORMAT_NETCDF4_CLASSIC)
     784             :         {
     785           2 :             m_aosStructuralInfo.SetNameValue("NC_FORMAT", "NETCDF4_CLASSIC");
     786             :         }
     787             :     }
     788             : 
     789             :     // Get enuerations associated with the group
     790         593 :     int nCustomTypeCount = 0;
     791         593 :     NCDF_ERR(nc_inq_typeids(m_gid, &nCustomTypeCount, nullptr));
     792         593 :     if (nCustomTypeCount > 0)
     793             :     {
     794          80 :         std::vector<int> anCustomTypeIDs(nCustomTypeCount);
     795          40 :         NCDF_ERR(
     796             :             nc_inq_typeids(m_gid, &nCustomTypeCount, anCustomTypeIDs.data()));
     797             : 
     798          80 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     799             : 
     800         353 :         for (int i = 0; i < nCustomTypeCount; ++i)
     801             :         {
     802         313 :             std::unique_ptr<GDALExtendedDataType> dt;
     803         313 :             bool bPerfectDataTypeMatch = false;
     804         313 :             if (BuildDataType(m_gid, /* varId = */ -1, anCustomTypeIDs[i], dt,
     805         313 :                               bPerfectDataTypeMatch) &&
     806         626 :                 dt && dt->GetRAT())
     807             :             {
     808          61 :                 m_apoTypes.push_back(
     809         122 :                     std::shared_ptr<GDALExtendedDataType>(dt.release()));
     810             :             }
     811             :         }
     812             :     }
     813         593 : }
     814             : 
     815             : /************************************************************************/
     816             : /*                            ~netCDFGroup()                            */
     817             : /************************************************************************/
     818             : 
     819         891 : netCDFGroup::~netCDFGroup()
     820             : {
     821        1186 :     auto poParent = m_poParent.lock();
     822         593 :     if (poParent)
     823          32 :         poParent->UnRegisterSubGroup(this);
     824         891 : }
     825             : 
     826             : /************************************************************************/
     827             : /*                              Create()                                */
     828             : /************************************************************************/
     829             : 
     830             : /* static */
     831             : std::shared_ptr<netCDFGroup>
     832         298 : netCDFGroup::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
     833             :                     int cdfid)
     834             : {
     835             :     auto poGroup =
     836         298 :         std::shared_ptr<netCDFGroup>(new netCDFGroup(poShared, cdfid));
     837         298 :     poGroup->SetSelf(poGroup);
     838         298 :     return poGroup;
     839             : }
     840             : 
     841             : /************************************************************************/
     842             : /*                              Create()                                */
     843             : /************************************************************************/
     844             : 
     845             : /* static */
     846             : std::shared_ptr<netCDFGroup>
     847         117 : netCDFGroup::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
     848             :                     const std::shared_ptr<netCDFGroup> &poParent,
     849             :                     int nSubGroupId)
     850             : {
     851         117 :     auto poSubGroup = netCDFGroup::Create(poShared, nSubGroupId);
     852         117 :     poSubGroup->m_poParent = poParent;
     853         117 :     if (poParent)
     854          48 :         poParent->RegisterSubGroup(poSubGroup.get());
     855         117 :     return poSubGroup;
     856             : }
     857             : 
     858             : /************************************************************************/
     859             : /*                             CreateGroup()                            */
     860             : /************************************************************************/
     861             : 
     862             : std::shared_ptr<GDALGroup>
     863          16 : netCDFGroup::CreateGroup(const std::string &osName,
     864             :                          CSLConstList /*papszOptions*/)
     865             : {
     866          16 :     if (osName.empty())
     867             :     {
     868           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     869             :                  "Empty group name not supported");
     870           1 :         return nullptr;
     871             :     }
     872          30 :     CPLMutexHolderD(&hNCMutex);
     873          15 :     m_poShared->SetDefineMode(true);
     874          15 :     int nSubGroupId = -1;
     875          15 :     int ret = nc_def_grp(m_gid, osName.c_str(), &nSubGroupId);
     876          15 :     NCDF_ERR(ret);
     877          15 :     if (ret != NC_NOERR)
     878           3 :         return nullptr;
     879          12 :     return netCDFGroup::Create(
     880          24 :         m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
     881          12 :         nSubGroupId);
     882             : }
     883             : 
     884             : /************************************************************************/
     885             : /*                             CreateDimension()                        */
     886             : /************************************************************************/
     887             : 
     888             : std::shared_ptr<GDALDimension>
     889         138 : netCDFGroup::CreateDimension(const std::string &osName,
     890             :                              const std::string &osType, const std::string &,
     891             :                              GUInt64 nSize, CSLConstList papszOptions)
     892             : {
     893             :     const bool bUnlimited =
     894         138 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "UNLIMITED", "FALSE"));
     895             :     if (static_cast<size_t>(nSize) != nSize)
     896             :     {
     897             :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid size");
     898             :         return nullptr;
     899             :     }
     900         276 :     CPLMutexHolderD(&hNCMutex);
     901         138 :     m_poShared->SetDefineMode(true);
     902         138 :     int nDimId = -1;
     903         138 :     NCDF_ERR(nc_def_dim(m_gid, osName.c_str(),
     904             :                         static_cast<size_t>(bUnlimited ? 0 : nSize), &nDimId));
     905         138 :     if (nDimId < 0)
     906           3 :         return nullptr;
     907         135 :     return netCDFDimension::Create(
     908         270 :         m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
     909         135 :         m_gid, nDimId, static_cast<size_t>(nSize), osType);
     910             : }
     911             : 
     912             : /************************************************************************/
     913             : /*                     CreateOrGetComplexDataType()                     */
     914             : /************************************************************************/
     915             : 
     916           7 : static int CreateOrGetComplexDataType(int gid, GDALDataType eDT)
     917             : {
     918           7 :     const char *pszName = "";
     919           7 :     CPL_IGNORE_RET_VAL(pszName);  // Make CSA happy
     920           7 :     int nSubTypeId = NC_NAT;
     921           7 :     switch (eDT)
     922             :     {
     923           1 :         case GDT_CInt16:
     924           1 :             pszName = "ComplexInt16";
     925           1 :             nSubTypeId = NC_SHORT;
     926           1 :             break;
     927           1 :         case GDT_CInt32:
     928           1 :             pszName = "ComplexInt32";
     929           1 :             nSubTypeId = NC_INT;
     930           1 :             break;
     931           2 :         case GDT_CFloat32:
     932           2 :             pszName = "ComplexFloat32";
     933           2 :             nSubTypeId = NC_FLOAT;
     934           2 :             break;
     935           3 :         case GDT_CFloat64:
     936           3 :             pszName = "ComplexFloat64";
     937           3 :             nSubTypeId = NC_DOUBLE;
     938           3 :             break;
     939           0 :         default:
     940           0 :             CPLAssert(false);
     941             :             break;
     942             :     }
     943           7 :     int nTypeId = NC_NAT;
     944           7 :     if (nc_inq_typeid(gid, pszName, &nTypeId) == NC_NOERR)
     945             :     {
     946             :         // We could check that the type definition is really the one we want
     947           1 :         return nTypeId;
     948             :     }
     949           6 :     const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
     950           6 :     NCDF_ERR(nc_def_compound(gid, nDTSize, pszName, &nTypeId));
     951           6 :     if (nTypeId != NC_NAT)
     952             :     {
     953           6 :         NCDF_ERR(nc_insert_compound(gid, nTypeId, "real", 0, nSubTypeId));
     954           6 :         NCDF_ERR(
     955             :             nc_insert_compound(gid, nTypeId, "imag", nDTSize / 2, nSubTypeId));
     956             :     }
     957           6 :     return nTypeId;
     958             : }
     959             : 
     960             : /************************************************************************/
     961             : /*                    CreateOrGetCompoundDataType()                     */
     962             : /************************************************************************/
     963             : 
     964             : static int CreateOrGetType(int gid, const GDALExtendedDataType &oType);
     965             : 
     966           3 : static int CreateOrGetCompoundDataType(int gid,
     967             :                                        const GDALExtendedDataType &oType)
     968             : {
     969           3 :     int nTypeId = NC_NAT;
     970           3 :     if (nc_inq_typeid(gid, oType.GetName().c_str(), &nTypeId) == NC_NOERR)
     971             :     {
     972             :         // We could check that the type definition is really the one we want
     973           2 :         return nTypeId;
     974             :     }
     975           1 :     NCDF_ERR(nc_def_compound(gid, oType.GetSize(), oType.GetName().c_str(),
     976             :                              &nTypeId));
     977           1 :     if (nTypeId != NC_NAT)
     978             :     {
     979           3 :         for (const auto &comp : oType.GetComponents())
     980             :         {
     981           2 :             int nSubTypeId = CreateOrGetType(gid, comp->GetType());
     982           2 :             if (nSubTypeId == NC_NAT)
     983           0 :                 return NC_NAT;
     984           2 :             NCDF_ERR(nc_insert_compound(gid, nTypeId, comp->GetName().c_str(),
     985             :                                         comp->GetOffset(), nSubTypeId));
     986             :         }
     987             :     }
     988           1 :     return nTypeId;
     989             : }
     990             : 
     991             : /************************************************************************/
     992             : /*                         CreateOrGetType()                            */
     993             : /************************************************************************/
     994             : 
     995         290 : static int CreateOrGetType(int gid, const GDALExtendedDataType &oType)
     996             : {
     997         290 :     int nTypeId = NC_NAT;
     998         290 :     const auto typeClass = oType.GetClass();
     999         290 :     if (typeClass == GEDTC_NUMERIC)
    1000             :     {
    1001         149 :         switch (oType.GetNumericDataType())
    1002             :         {
    1003          36 :             case GDT_Byte:
    1004          36 :                 nTypeId = NC_UBYTE;
    1005          36 :                 break;
    1006           1 :             case GDT_Int8:
    1007           1 :                 nTypeId = NC_BYTE;
    1008           1 :                 break;
    1009          11 :             case GDT_UInt16:
    1010          11 :                 nTypeId = NC_USHORT;
    1011          11 :                 break;
    1012          10 :             case GDT_Int16:
    1013          10 :                 nTypeId = NC_SHORT;
    1014          10 :                 break;
    1015           5 :             case GDT_UInt32:
    1016           5 :                 nTypeId = NC_UINT;
    1017           5 :                 break;
    1018           2 :             case GDT_Int32:
    1019           2 :                 nTypeId = NC_INT;
    1020           2 :                 break;
    1021           1 :             case GDT_UInt64:
    1022           1 :                 nTypeId = NC_UINT64;
    1023           1 :                 break;
    1024           1 :             case GDT_Int64:
    1025           1 :                 nTypeId = NC_INT64;
    1026           1 :                 break;
    1027           4 :             case GDT_Float32:
    1028           4 :                 nTypeId = NC_FLOAT;
    1029           4 :                 break;
    1030          71 :             case GDT_Float64:
    1031          71 :                 nTypeId = NC_DOUBLE;
    1032          71 :                 break;
    1033           7 :             case GDT_CInt16:
    1034             :             case GDT_CInt32:
    1035             :             case GDT_CFloat32:
    1036             :             case GDT_CFloat64:
    1037             :                 nTypeId =
    1038           7 :                     CreateOrGetComplexDataType(gid, oType.GetNumericDataType());
    1039           7 :                 break;
    1040           0 :             default:
    1041           0 :                 break;
    1042             :         }
    1043             :     }
    1044         141 :     else if (typeClass == GEDTC_STRING)
    1045             :     {
    1046         138 :         nTypeId = NC_STRING;
    1047             :     }
    1048           3 :     else if (typeClass == GEDTC_COMPOUND)
    1049             :     {
    1050           3 :         nTypeId = CreateOrGetCompoundDataType(gid, oType);
    1051             :     }
    1052         290 :     return nTypeId;
    1053             : }
    1054             : 
    1055             : /************************************************************************/
    1056             : /*                            CreateMDArray()                           */
    1057             : /************************************************************************/
    1058             : 
    1059         127 : std::shared_ptr<GDALMDArray> netCDFGroup::CreateMDArray(
    1060             :     const std::string &osName,
    1061             :     const std::vector<std::shared_ptr<GDALDimension>> &aoDimensions,
    1062             :     const GDALExtendedDataType &oType, CSLConstList papszOptions)
    1063             : {
    1064         127 :     if (osName.empty())
    1065             :     {
    1066           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1067             :                  "Empty array name not supported");
    1068           0 :         return nullptr;
    1069             :     }
    1070         254 :     CPLMutexHolderD(&hNCMutex);
    1071         127 :     m_poShared->SetDefineMode(true);
    1072         127 :     int nVarId = -1;
    1073         254 :     std::vector<int> anDimIds;
    1074         254 :     std::vector<std::shared_ptr<GDALDimension>> dims;
    1075         321 :     for (const auto &dim : aoDimensions)
    1076             :     {
    1077         194 :         int nDimId = -1;
    1078         194 :         auto netCDFDim = std::dynamic_pointer_cast<netCDFDimension>(dim);
    1079         194 :         if (netCDFDim)
    1080             :         {
    1081         191 :             nDimId = netCDFDim->GetId();
    1082             :         }
    1083             :         else
    1084             :         {
    1085           3 :             if (nc_inq_dimid(m_gid, dim->GetName().c_str(), &nDimId) ==
    1086             :                 NC_NOERR)
    1087             :             {
    1088           4 :                 netCDFDim = netCDFDimension::Create(
    1089           2 :                     m_poShared,
    1090           4 :                     std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
    1091           2 :                     m_gid, nDimId, 0, dim->GetType());
    1092           2 :                 if (netCDFDim->GetSize() != dim->GetSize())
    1093             :                 {
    1094           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1095             :                              "Dimension %s already exists, "
    1096             :                              "but with a size of " CPL_FRMT_GUIB,
    1097           1 :                              dim->GetName().c_str(),
    1098           1 :                              static_cast<GUIntBig>(netCDFDim->GetSize()));
    1099             :                 }
    1100             :             }
    1101             :             else
    1102             :             {
    1103             :                 netCDFDim =
    1104           2 :                     std::dynamic_pointer_cast<netCDFDimension>(CreateDimension(
    1105             :                         dim->GetName(), dim->GetType(), dim->GetDirection(),
    1106           1 :                         dim->GetSize(), nullptr));
    1107           1 :                 if (!netCDFDim)
    1108           0 :                     return nullptr;
    1109           1 :                 nDimId = netCDFDim->GetId();
    1110             :             }
    1111             :         }
    1112         194 :         anDimIds.push_back(nDimId);
    1113         194 :         dims.emplace_back(netCDFDim);
    1114             :     }
    1115         127 :     int nTypeId = CreateOrGetType(m_gid, oType);
    1116         127 :     if (nTypeId == NC_NAT)
    1117             :     {
    1118           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Unhandled data type");
    1119           0 :         return nullptr;
    1120             :     }
    1121         127 :     const char *pszType = CSLFetchNameValueDef(papszOptions, "NC_TYPE", "");
    1122         184 :     if ((EQUAL(pszType, "") || EQUAL(pszType, "NC_CHAR")) && dims.size() == 1 &&
    1123         311 :         oType.GetClass() == GEDTC_STRING && oType.GetMaxStringLength() > 0)
    1124             :     {
    1125           2 :         nTypeId = NC_CHAR;
    1126             :         auto dimLength =
    1127           4 :             std::dynamic_pointer_cast<netCDFDimension>(CreateDimension(
    1128           4 :                 aoDimensions[0]->GetName() + "_length", std::string(),
    1129           4 :                 std::string(), oType.GetMaxStringLength(), nullptr));
    1130           2 :         if (!dimLength)
    1131           0 :             return nullptr;
    1132           2 :         anDimIds.push_back(dimLength->GetId());
    1133             :     }
    1134         125 :     else if (EQUAL(pszType, "NC_BYTE"))
    1135           1 :         nTypeId = NC_BYTE;
    1136         124 :     else if (EQUAL(pszType, "NC_INT64"))
    1137           1 :         nTypeId = NC_INT64;
    1138         123 :     else if (EQUAL(pszType, "NC_UINT64"))
    1139           1 :         nTypeId = NC_UINT64;
    1140         127 :     NCDF_ERR(nc_def_var(m_gid, osName.c_str(), nTypeId,
    1141             :                         static_cast<int>(anDimIds.size()),
    1142             :                         anDimIds.empty() ? nullptr : anDimIds.data(), &nVarId));
    1143         127 :     if (nVarId < 0)
    1144           3 :         return nullptr;
    1145             : 
    1146         124 :     const char *pszBlockSize = CSLFetchNameValue(papszOptions, "BLOCKSIZE");
    1147         134 :     if (pszBlockSize &&
    1148             :         /* ignore for now BLOCKSIZE for 1-dim string variables created as 2-dim
    1149             :          */
    1150          10 :         anDimIds.size() == aoDimensions.size())
    1151             :     {
    1152          10 :         auto aszTokens(CPLStringList(CSLTokenizeString2(pszBlockSize, ",", 0)));
    1153          10 :         if (static_cast<size_t>(aszTokens.size()) != aoDimensions.size())
    1154             :         {
    1155           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1156             :                      "Invalid number of values in BLOCKSIZE");
    1157           0 :             return nullptr;
    1158             :         }
    1159          10 :         if (!aoDimensions.empty())
    1160             :         {
    1161           9 :             std::vector<size_t> anChunkSize(aoDimensions.size());
    1162          27 :             for (size_t i = 0; i < anChunkSize.size(); ++i)
    1163             :             {
    1164          18 :                 anChunkSize[i] =
    1165          18 :                     static_cast<size_t>(CPLAtoGIntBig(aszTokens[i]));
    1166             :             }
    1167             :             int ret =
    1168           9 :                 nc_def_var_chunking(m_gid, nVarId, NC_CHUNKED, &anChunkSize[0]);
    1169           9 :             NCDF_ERR(ret);
    1170           9 :             if (ret != NC_NOERR)
    1171           0 :                 return nullptr;
    1172             :         }
    1173             :     }
    1174             : 
    1175         124 :     const char *pszCompress = CSLFetchNameValue(papszOptions, "COMPRESS");
    1176         124 :     if (pszCompress && EQUAL(pszCompress, "DEFLATE"))
    1177             :     {
    1178          30 :         int nZLevel = NCDF_DEFLATE_LEVEL;
    1179          30 :         const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
    1180          30 :         if (pszZLevel != nullptr)
    1181             :         {
    1182           2 :             nZLevel = atoi(pszZLevel);
    1183           2 :             if (!(nZLevel >= 1 && nZLevel <= 9))
    1184             :             {
    1185           0 :                 CPLError(CE_Warning, CPLE_IllegalArg,
    1186             :                          "ZLEVEL=%s value not recognised, ignoring.",
    1187             :                          pszZLevel);
    1188           0 :                 nZLevel = NCDF_DEFLATE_LEVEL;
    1189             :             }
    1190             :         }
    1191          30 :         int ret = nc_def_var_deflate(m_gid, nVarId, TRUE /* shuffle */,
    1192             :                                      TRUE /* deflate on */, nZLevel);
    1193          30 :         NCDF_ERR(ret);
    1194          30 :         if (ret != NC_NOERR)
    1195           0 :             return nullptr;
    1196             :     }
    1197             : 
    1198         124 :     const char *pszFilter = CSLFetchNameValue(papszOptions, "FILTER");
    1199         124 :     if (pszFilter)
    1200             :     {
    1201             : #ifdef NC_EFILTER
    1202             :         const auto aosTokens(
    1203           0 :             CPLStringList(CSLTokenizeString2(pszFilter, ",", 0)));
    1204           0 :         if (!aosTokens.empty())
    1205             :         {
    1206             :             const unsigned nFilterId =
    1207           0 :                 static_cast<unsigned>(CPLAtoGIntBig(aosTokens[0]));
    1208           0 :             std::vector<unsigned> anParams;
    1209           0 :             for (int i = 1; i < aosTokens.size(); ++i)
    1210             :             {
    1211           0 :                 anParams.push_back(
    1212           0 :                     static_cast<unsigned>(CPLAtoGIntBig(aosTokens[i])));
    1213             :             }
    1214           0 :             int ret = nc_def_var_filter(m_gid, nVarId, nFilterId,
    1215           0 :                                         anParams.size(), anParams.data());
    1216           0 :             NCDF_ERR(ret);
    1217           0 :             if (ret != NC_NOERR)
    1218           0 :                 return nullptr;
    1219             :         }
    1220             : #else
    1221             :         CPLError(CE_Failure, CPLE_NotSupported,
    1222             :                  "netCDF 4.6 or later needed for FILTER option");
    1223             :         return nullptr;
    1224             : #endif
    1225             :     }
    1226             : 
    1227             :     const bool bChecksum =
    1228         124 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "CHECKSUM", "FALSE"));
    1229         124 :     if (bChecksum)
    1230             :     {
    1231           1 :         int ret = nc_def_var_fletcher32(m_gid, nVarId, TRUE);
    1232           1 :         NCDF_ERR(ret);
    1233           1 :         if (ret != NC_NOERR)
    1234           0 :             return nullptr;
    1235             :     }
    1236             : 
    1237         124 :     return netCDFVariable::Create(
    1238         248 :         m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
    1239         124 :         m_gid, nVarId, dims, papszOptions, true);
    1240             : }
    1241             : 
    1242             : /************************************************************************/
    1243             : /*                          CreateAttribute()                           */
    1244             : /************************************************************************/
    1245             : 
    1246          76 : std::shared_ptr<GDALAttribute> netCDFGroup::CreateAttribute(
    1247             :     const std::string &osName, const std::vector<GUInt64> &anDimensions,
    1248             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
    1249             : {
    1250         152 :     return netCDFAttribute::Create(
    1251         228 :         m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
    1252         152 :         m_gid, NC_GLOBAL, osName, anDimensions, oDataType, papszOptions);
    1253             : }
    1254             : 
    1255             : /************************************************************************/
    1256             : /*                         DeleteAttribute()                            */
    1257             : /************************************************************************/
    1258             : 
    1259           2 : bool netCDFGroup::DeleteAttribute(const std::string &osName,
    1260             :                                   CSLConstList /*papszOptions*/)
    1261             : {
    1262           4 :     CPLMutexHolderD(&hNCMutex);
    1263           2 :     m_poShared->SetDefineMode(true);
    1264             : 
    1265           2 :     int ret = nc_del_att(m_gid, NC_GLOBAL, osName.c_str());
    1266           2 :     NCDF_ERR(ret);
    1267           2 :     if (ret != NC_NOERR)
    1268           1 :         return false;
    1269             : 
    1270           1 :     auto it = m_oMapAttributes.find(osName);
    1271           1 :     if (it != m_oMapAttributes.end())
    1272             :     {
    1273           1 :         it->second->Deleted();
    1274           1 :         m_oMapAttributes.erase(it);
    1275             :     }
    1276             : 
    1277           1 :     return true;
    1278             : }
    1279             : 
    1280             : /************************************************************************/
    1281             : /*                            GetGroupNames()                           */
    1282             : /************************************************************************/
    1283             : 
    1284             : std::vector<std::string>
    1285          27 : netCDFGroup::GetGroupNames(CSLConstList papszOptions) const
    1286             : {
    1287          54 :     CPLMutexHolderD(&hNCMutex);
    1288          27 :     int nSubGroups = 0;
    1289          27 :     NCDF_ERR(nc_inq_grps(m_gid, &nSubGroups, nullptr));
    1290          27 :     if (nSubGroups == 0)
    1291             :     {
    1292          21 :         if (EQUAL(CSLFetchNameValueDef(papszOptions, "GROUP_BY", ""),
    1293             :                   "SAME_DIMENSION"))
    1294             :         {
    1295           4 :             std::vector<std::string> names;
    1296           4 :             std::set<std::string> oSetDimNames;
    1297          30 :             for (const auto &osArrayName : GetMDArrayNames(nullptr))
    1298             :             {
    1299          56 :                 const auto poArray = OpenMDArray(osArrayName, nullptr);
    1300          28 :                 const auto &apoDims = poArray->GetDimensions();
    1301          28 :                 if (apoDims.size() == 1)
    1302             :                 {
    1303          28 :                     const auto &osDimName = apoDims[0]->GetName();
    1304          28 :                     if (oSetDimNames.find(osDimName) == oSetDimNames.end())
    1305             :                     {
    1306           6 :                         oSetDimNames.insert(osDimName);
    1307           6 :                         names.emplace_back(osDimName);
    1308             :                     }
    1309             :                 }
    1310             :             }
    1311           2 :             return names;
    1312             :         }
    1313          19 :         return {};
    1314             :     }
    1315          12 :     std::vector<int> anSubGroupdsIds(nSubGroups);
    1316           6 :     NCDF_ERR(nc_inq_grps(m_gid, nullptr, &anSubGroupdsIds[0]));
    1317          12 :     std::vector<std::string> names;
    1318           6 :     names.reserve(nSubGroups);
    1319          15 :     for (const auto &subgid : anSubGroupdsIds)
    1320             :     {
    1321           9 :         char szName[NC_MAX_NAME + 1] = {};
    1322           9 :         NCDF_ERR(nc_inq_grpname(subgid, szName));
    1323           9 :         if (GetFullName() == "/" && EQUAL(szName, "METADATA"))
    1324             :         {
    1325           4 :             const auto poMetadata = OpenGroup(szName);
    1326           2 :             if (poMetadata && poMetadata->OpenGroup("ISO_METADATA"))
    1327           2 :                 continue;
    1328             :         }
    1329           7 :         names.emplace_back(szName);
    1330             :     }
    1331           6 :     return names;
    1332             : }
    1333             : 
    1334             : /************************************************************************/
    1335             : /*                             OpenGroup()                              */
    1336             : /************************************************************************/
    1337             : 
    1338             : std::shared_ptr<GDALGroup>
    1339          88 : netCDFGroup::OpenGroup(const std::string &osName,
    1340             :                        CSLConstList papszOptions) const
    1341             : {
    1342         176 :     CPLMutexHolderD(&hNCMutex);
    1343          88 :     int nSubGroups = 0;
    1344             :     // This is weird but nc_inq_grp_ncid() succeeds on a single group file.
    1345          88 :     NCDF_ERR(nc_inq_grps(m_gid, &nSubGroups, nullptr));
    1346          88 :     if (nSubGroups == 0)
    1347             :     {
    1348          23 :         if (EQUAL(CSLFetchNameValueDef(papszOptions, "GROUP_BY", ""),
    1349             :                   "SAME_DIMENSION"))
    1350             :         {
    1351           1 :             const auto oCandidateGroupNames = GetGroupNames(papszOptions);
    1352           1 :             for (const auto &osCandidateGroupName : oCandidateGroupNames)
    1353             :             {
    1354           1 :                 if (osCandidateGroupName == osName)
    1355             :                 {
    1356           1 :                     auto poThisGroup = netCDFGroup::Create(m_poShared, m_gid);
    1357           2 :                     return netCDFVirtualGroupBySameDimension::Create(
    1358           1 :                         poThisGroup, osName);
    1359             :                 }
    1360             :             }
    1361             :         }
    1362          22 :         return nullptr;
    1363             :     }
    1364          65 :     int nSubGroupId = 0;
    1365         101 :     if (nc_inq_grp_ncid(m_gid, osName.c_str(), &nSubGroupId) != NC_NOERR ||
    1366          36 :         nSubGroupId <= 0)
    1367          29 :         return nullptr;
    1368          36 :     return netCDFGroup::Create(
    1369          72 :         m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
    1370          36 :         nSubGroupId);
    1371             : }
    1372             : 
    1373             : /************************************************************************/
    1374             : /*                         GetMDArrayNames()                            */
    1375             : /************************************************************************/
    1376             : 
    1377             : std::vector<std::string>
    1378         338 : netCDFGroup::GetMDArrayNames(CSLConstList papszOptions) const
    1379             : {
    1380         676 :     CPLMutexHolderD(&hNCMutex);
    1381         338 :     int nVars = 0;
    1382         338 :     NCDF_ERR(nc_inq_nvars(m_gid, &nVars));
    1383         338 :     if (nVars == 0)
    1384          12 :         return {};
    1385         652 :     std::vector<int> anVarIds(nVars);
    1386         326 :     NCDF_ERR(nc_inq_varids(m_gid, nullptr, &anVarIds[0]));
    1387         652 :     std::vector<std::string> names;
    1388         326 :     names.reserve(nVars);
    1389             :     const bool bAll =
    1390         326 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_ALL", "NO"));
    1391             :     const bool bZeroDim =
    1392         651 :         bAll ||
    1393         325 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_ZERO_DIM", "NO"));
    1394             :     const bool bCoordinates =
    1395         326 :         bAll || CPLTestBool(CSLFetchNameValueDef(papszOptions,
    1396         326 :                                                  "SHOW_COORDINATES", "YES"));
    1397             :     const bool bBounds =
    1398         651 :         bAll ||
    1399         325 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_BOUNDS", "YES"));
    1400             :     const bool bIndexing =
    1401         651 :         bAll ||
    1402         325 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_INDEXING", "YES"));
    1403             :     const bool bTime =
    1404         651 :         bAll ||
    1405         325 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_TIME", "YES"));
    1406         652 :     std::set<std::string> ignoreList;
    1407         326 :     if (!bCoordinates || !bBounds)
    1408             :     {
    1409          12 :         for (const auto &varid : anVarIds)
    1410             :         {
    1411           9 :             char **papszTokens = nullptr;
    1412           9 :             if (!bCoordinates)
    1413             :             {
    1414           5 :                 char *pszTemp = nullptr;
    1415           5 :                 if (NCDFGetAttr(m_gid, varid, "coordinates", &pszTemp) ==
    1416             :                     CE_None)
    1417           1 :                     papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
    1418           5 :                 CPLFree(pszTemp);
    1419             :             }
    1420           9 :             if (!bBounds)
    1421             :             {
    1422           4 :                 char *pszTemp = nullptr;
    1423           4 :                 if (NCDFGetAttr(m_gid, varid, "bounds", &pszTemp) == CE_None &&
    1424           4 :                     pszTemp != nullptr && !EQUAL(pszTemp, ""))
    1425           2 :                     papszTokens = CSLAddString(papszTokens, pszTemp);
    1426           4 :                 CPLFree(pszTemp);
    1427             :             }
    1428          15 :             for (char **iter = papszTokens; iter && iter[0]; ++iter)
    1429           6 :                 ignoreList.insert(*iter);
    1430           9 :             CSLDestroy(papszTokens);
    1431             :         }
    1432             :     }
    1433             : 
    1434         326 :     const bool bGroupBySameDimension = EQUAL(
    1435             :         CSLFetchNameValueDef(papszOptions, "GROUP_BY", ""), "SAME_DIMENSION");
    1436        1606 :     for (const auto &varid : anVarIds)
    1437             :     {
    1438        1280 :         int nVarDims = 0;
    1439        1280 :         NCDF_ERR(nc_inq_varndims(m_gid, varid, &nVarDims));
    1440        1280 :         if (nVarDims == 0 && !bZeroDim)
    1441             :         {
    1442         149 :             continue;
    1443             :         }
    1444        1149 :         if (nVarDims == 1 && bGroupBySameDimension)
    1445             :         {
    1446          14 :             continue;
    1447             :         }
    1448             : 
    1449        1135 :         char szName[NC_MAX_NAME + 1] = {};
    1450        1135 :         NCDF_ERR(nc_inq_varname(m_gid, varid, szName));
    1451        1135 :         if (!bIndexing && nVarDims == 1)
    1452             :         {
    1453           1 :             int nDimId = 0;
    1454           1 :             NCDF_ERR(nc_inq_vardimid(m_gid, varid, &nDimId));
    1455           1 :             char szDimName[NC_MAX_NAME + 1] = {};
    1456           1 :             NCDF_ERR(nc_inq_dimname(m_gid, nDimId, szDimName));
    1457           1 :             if (strcmp(szDimName, szName) == 0)
    1458             :             {
    1459           1 :                 continue;
    1460             :             }
    1461             :         }
    1462             : 
    1463        1134 :         if (!bTime)
    1464             :         {
    1465          14 :             char *pszTemp = nullptr;
    1466          14 :             bool bSkip = false;
    1467          14 :             if (NCDFGetAttr(m_gid, varid, "standard_name", &pszTemp) == CE_None)
    1468             :             {
    1469          10 :                 bSkip = pszTemp && strcmp(pszTemp, "time") == 0;
    1470             :             }
    1471          14 :             CPLFree(pszTemp);
    1472          14 :             if (bSkip)
    1473             :             {
    1474           3 :                 continue;
    1475             :             }
    1476             :         }
    1477             : 
    1478        1131 :         if (ignoreList.find(szName) == ignoreList.end())
    1479             :         {
    1480        1125 :             names.emplace_back(szName);
    1481             :         }
    1482             :     }
    1483         326 :     return names;
    1484             : }
    1485             : 
    1486             : /************************************************************************/
    1487             : /*                              Rename()                                */
    1488             : /************************************************************************/
    1489             : 
    1490           5 : bool netCDFGroup::Rename(const std::string &osNewName)
    1491             : {
    1492           5 :     if (m_poShared->IsReadOnly())
    1493             :     {
    1494           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1495             :                  "Rename() not supported on read-only file");
    1496           1 :         return false;
    1497             :     }
    1498           4 :     if (osNewName.empty())
    1499             :     {
    1500           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
    1501           1 :         return false;
    1502             :     }
    1503           3 :     if (m_osName == "/")
    1504             :     {
    1505           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Cannot rename root group");
    1506           1 :         return false;
    1507             :     }
    1508             : 
    1509           4 :     CPLMutexHolderD(&hNCMutex);
    1510           2 :     m_poShared->SetDefineMode(true);
    1511             : 
    1512           2 :     int ret = nc_rename_grp(m_gid, osNewName.c_str());
    1513           2 :     NCDF_ERR(ret);
    1514           2 :     if (ret != NC_NOERR)
    1515           1 :         return false;
    1516             : 
    1517           1 :     BaseRename(osNewName);
    1518             : 
    1519           1 :     return true;
    1520             : }
    1521             : 
    1522             : /************************************************************************/
    1523             : /*                       NotifyChildrenOfRenaming()                     */
    1524             : /************************************************************************/
    1525             : 
    1526           2 : void netCDFGroup::NotifyChildrenOfRenaming()
    1527             : {
    1528           3 :     for (const auto poSubGroup : m_oSetGroups)
    1529           1 :         poSubGroup->ParentRenamed(m_osFullName);
    1530             : 
    1531           3 :     for (const auto poDim : m_oSetDimensions)
    1532           1 :         poDim->ParentRenamed(m_osFullName);
    1533             : 
    1534           4 :     for (const auto poArray : m_oSetArrays)
    1535           2 :         poArray->ParentRenamed(m_osFullName);
    1536             : 
    1537           4 :     for (const auto &iter : m_oMapAttributes)
    1538           2 :         iter.second->ParentRenamed(m_osFullName);
    1539           2 : }
    1540             : 
    1541             : /************************************************************************/
    1542             : /*                           OpenMDArray()                              */
    1543             : /************************************************************************/
    1544             : 
    1545             : std::shared_ptr<GDALMDArray>
    1546        1284 : netCDFGroup::OpenMDArray(const std::string &osName,
    1547             :                          CSLConstList papszOptions) const
    1548             : {
    1549        2568 :     CPLMutexHolderD(&hNCMutex);
    1550        1284 :     int nVarId = 0;
    1551        1284 :     if (nc_inq_varid(m_gid, osName.c_str(), &nVarId) != NC_NOERR)
    1552          31 :         return nullptr;
    1553             : 
    1554             :     auto poVar = netCDFVariable::Create(
    1555        2506 :         m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
    1556        1253 :         m_gid, nVarId, std::vector<std::shared_ptr<GDALDimension>>(),
    1557        3759 :         papszOptions, false);
    1558        1253 :     if (poVar)
    1559             :     {
    1560        1253 :         poVar->SetUseDefaultFillAsNoData(CPLTestBool(CSLFetchNameValueDef(
    1561             :             papszOptions, "USE_DEFAULT_FILL_AS_NODATA", "NO")));
    1562             :     }
    1563        1253 :     return poVar;
    1564             : }
    1565             : 
    1566             : /************************************************************************/
    1567             : /*                         GetDimensions()                              */
    1568             : /************************************************************************/
    1569             : 
    1570             : std::vector<std::shared_ptr<GDALDimension>>
    1571          35 : netCDFGroup::GetDimensions(CSLConstList) const
    1572             : {
    1573          70 :     CPLMutexHolderD(&hNCMutex);
    1574          35 :     int nbDims = 0;
    1575          35 :     NCDF_ERR(nc_inq_ndims(m_gid, &nbDims));
    1576          35 :     if (nbDims == 0)
    1577           2 :         return {};
    1578          66 :     std::vector<int> dimids(nbDims);
    1579          33 :     NCDF_ERR(nc_inq_dimids(m_gid, &nbDims, &dimids[0], FALSE));
    1580          66 :     std::vector<std::shared_ptr<GDALDimension>> res;
    1581         102 :     for (int i = 0; i < nbDims; i++)
    1582             :     {
    1583         138 :         auto poCachedDim = m_poShared->GetCachedDimension(dimids[i]);
    1584          69 :         if (poCachedDim == nullptr)
    1585             :         {
    1586          62 :             poCachedDim = netCDFDimension::Create(
    1587          62 :                 m_poShared,
    1588         124 :                 std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()), m_gid,
    1589         186 :                 dimids[i], 0, std::string());
    1590          62 :             m_poShared->CacheDimension(dimids[i], poCachedDim);
    1591             :         }
    1592          69 :         res.emplace_back(poCachedDim);
    1593             :     }
    1594          33 :     return res;
    1595             : }
    1596             : 
    1597             : /************************************************************************/
    1598             : /*                         GetAttribute()                               */
    1599             : /************************************************************************/
    1600             : 
    1601             : static const char *const apszJSONMDKeys[] = {
    1602             :     "ISO_METADATA",  "ESA_METADATA",        "EOP_METADATA",
    1603             :     "QA_STATISTICS", "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"};
    1604             : 
    1605             : std::shared_ptr<GDALAttribute>
    1606          14 : netCDFGroup::GetAttribute(const std::string &osName) const
    1607             : {
    1608          28 :     CPLMutexHolderD(&hNCMutex);
    1609          14 :     int nAttId = -1;
    1610          14 :     if (nc_inq_attid(m_gid, NC_GLOBAL, osName.c_str(), &nAttId) != NC_NOERR)
    1611             :     {
    1612           2 :         if (GetFullName() == "/")
    1613             :         {
    1614           8 :             for (const char *key : apszJSONMDKeys)
    1615             :             {
    1616           7 :                 if (osName == key)
    1617             :                 {
    1618           2 :                     auto poMetadata = OpenGroup("METADATA");
    1619           1 :                     if (poMetadata)
    1620             :                     {
    1621             :                         auto poSubMetadata =
    1622             :                             std::dynamic_pointer_cast<netCDFGroup>(
    1623           2 :                                 poMetadata->OpenGroup(key));
    1624           1 :                         if (poSubMetadata)
    1625             :                         {
    1626             :                             const auto osJson =
    1627           1 :                                 NCDFReadMetadataAsJson(poSubMetadata->m_gid);
    1628           2 :                             return std::make_shared<GDALAttributeString>(
    1629           3 :                                 GetFullName(), key, osJson, GEDTST_JSON);
    1630             :                         }
    1631             :                     }
    1632           0 :                     break;
    1633             :                 }
    1634             :             }
    1635             :         }
    1636           1 :         return nullptr;
    1637             :     }
    1638          24 :     return netCDFAttribute::Create(
    1639          36 :         m_poShared, std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()),
    1640          24 :         m_gid, NC_GLOBAL, osName);
    1641             : }
    1642             : 
    1643             : /************************************************************************/
    1644             : /*                         GetAttributes()                              */
    1645             : /************************************************************************/
    1646             : 
    1647             : std::vector<std::shared_ptr<GDALAttribute>>
    1648          40 : netCDFGroup::GetAttributes(CSLConstList) const
    1649             : {
    1650          80 :     CPLMutexHolderD(&hNCMutex);
    1651          40 :     std::vector<std::shared_ptr<GDALAttribute>> res;
    1652          40 :     int nbAttr = 0;
    1653          40 :     NCDF_ERR(nc_inq_varnatts(m_gid, NC_GLOBAL, &nbAttr));
    1654          40 :     res.reserve(nbAttr);
    1655         158 :     for (int i = 0; i < nbAttr; i++)
    1656             :     {
    1657             :         char szAttrName[NC_MAX_NAME + 1];
    1658         118 :         szAttrName[0] = 0;
    1659         118 :         NCDF_ERR(nc_inq_attname(m_gid, NC_GLOBAL, i, szAttrName));
    1660         118 :         if (!EQUAL(szAttrName, "_NCProperties"))
    1661             :         {
    1662         472 :             res.emplace_back(netCDFAttribute::Create(
    1663         118 :                 m_poShared,
    1664         236 :                 std::dynamic_pointer_cast<netCDFGroup>(m_pSelf.lock()), m_gid,
    1665         118 :                 NC_GLOBAL, szAttrName));
    1666             :         }
    1667             :     }
    1668             : 
    1669          40 :     if (GetFullName() == "/")
    1670             :     {
    1671         105 :         auto poMetadata = OpenGroup("METADATA");
    1672          35 :         if (poMetadata)
    1673             :         {
    1674          14 :             for (const char *key : apszJSONMDKeys)
    1675             :             {
    1676             :                 auto poSubMetadata = std::dynamic_pointer_cast<netCDFGroup>(
    1677          36 :                     poMetadata->OpenGroup(key));
    1678          12 :                 if (poSubMetadata)
    1679             :                 {
    1680             :                     const auto osJson =
    1681           2 :                         NCDFReadMetadataAsJson(poSubMetadata->m_gid);
    1682           4 :                     res.emplace_back(std::make_shared<GDALAttributeString>(
    1683           6 :                         GetFullName(), key, osJson, GEDTST_JSON));
    1684             :                 }
    1685             :             }
    1686             :         }
    1687             :     }
    1688             : 
    1689          80 :     return res;
    1690             : }
    1691             : 
    1692             : /************************************************************************/
    1693             : /*                         GetStructuralInfo()                          */
    1694             : /************************************************************************/
    1695             : 
    1696          12 : CSLConstList netCDFGroup::GetStructuralInfo() const
    1697             : {
    1698          12 :     return m_aosStructuralInfo.List();
    1699             : }
    1700             : 
    1701             : /************************************************************************/
    1702             : /*                          ClearStatistics()                           */
    1703             : /************************************************************************/
    1704             : 
    1705           1 : void netCDFGroup::ClearStatistics()
    1706             : {
    1707           1 :     m_poShared->GetPAM()->ClearStatistics();
    1708           1 : }
    1709             : 
    1710             : /************************************************************************/
    1711             : /*                   netCDFVirtualGroupBySameDimension()                */
    1712             : /************************************************************************/
    1713             : 
    1714           1 : netCDFVirtualGroupBySameDimension::netCDFVirtualGroupBySameDimension(
    1715           1 :     const std::shared_ptr<netCDFGroup> &poGroup, const std::string &osDimName)
    1716           1 :     : GDALGroup(poGroup->GetName(), osDimName), m_poGroup(poGroup),
    1717           2 :       m_osDimName(osDimName)
    1718             : {
    1719           1 : }
    1720             : 
    1721             : /************************************************************************/
    1722             : /*                              Create()                                */
    1723             : /************************************************************************/
    1724             : 
    1725             : /* static */ std::shared_ptr<netCDFVirtualGroupBySameDimension>
    1726           1 : netCDFVirtualGroupBySameDimension::Create(
    1727             :     const std::shared_ptr<netCDFGroup> &poGroup, const std::string &osDimName)
    1728             : {
    1729             :     auto poNewGroup = std::shared_ptr<netCDFVirtualGroupBySameDimension>(
    1730           1 :         new netCDFVirtualGroupBySameDimension(poGroup, osDimName));
    1731           1 :     poNewGroup->SetSelf(poNewGroup);
    1732           1 :     return poNewGroup;
    1733             : }
    1734             : 
    1735             : /************************************************************************/
    1736             : /*                         GetMDArrayNames()                            */
    1737             : /************************************************************************/
    1738             : 
    1739             : std::vector<std::string>
    1740           1 : netCDFVirtualGroupBySameDimension::GetMDArrayNames(CSLConstList) const
    1741             : {
    1742           2 :     const auto srcNames = m_poGroup->GetMDArrayNames(nullptr);
    1743           1 :     std::vector<std::string> names;
    1744          15 :     for (const auto &srcName : srcNames)
    1745             :     {
    1746          28 :         auto poArray = m_poGroup->OpenMDArray(srcName, nullptr);
    1747          14 :         if (poArray)
    1748             :         {
    1749          14 :             const auto &apoArrayDims = poArray->GetDimensions();
    1750          28 :             if (apoArrayDims.size() == 1 &&
    1751          14 :                 apoArrayDims[0]->GetName() == m_osDimName)
    1752             :             {
    1753           7 :                 names.emplace_back(srcName);
    1754             :             }
    1755             :         }
    1756             :     }
    1757           2 :     return names;
    1758             : }
    1759             : 
    1760             : /************************************************************************/
    1761             : /*                           OpenMDArray()                              */
    1762             : /************************************************************************/
    1763             : 
    1764             : std::shared_ptr<GDALMDArray>
    1765           7 : netCDFVirtualGroupBySameDimension::OpenMDArray(const std::string &osName,
    1766             :                                                CSLConstList papszOptions) const
    1767             : {
    1768           7 :     return m_poGroup->OpenMDArray(osName, papszOptions);
    1769             : }
    1770             : 
    1771             : /************************************************************************/
    1772             : /*                           netCDFDimension()                          */
    1773             : /************************************************************************/
    1774             : 
    1775        1721 : netCDFDimension::netCDFDimension(
    1776             :     const std::shared_ptr<netCDFSharedResources> &poShared, int cfid, int dimid,
    1777        1721 :     size_t nForcedSize, const std::string &osType)
    1778        3442 :     : GDALDimension(NCDFGetGroupFullName(cfid), retrieveName(cfid, dimid),
    1779             :                     osType,         // type
    1780        3442 :                     std::string(),  // direction
    1781        1588 :                     nForcedSize ? nForcedSize : retrieveSize(cfid, dimid)),
    1782        5163 :       m_poShared(poShared), m_gid(cfid), m_dimid(dimid)
    1783             : {
    1784        1721 :     if (m_osType.empty() && nForcedSize == 0)
    1785             :     {
    1786             :         auto var =
    1787        3176 :             std::dynamic_pointer_cast<netCDFVariable>(GetIndexingVariable());
    1788        1588 :         if (var)
    1789             :         {
    1790         233 :             const auto gid = var->GetGroupId();
    1791         233 :             const auto varid = var->GetVarId();
    1792         233 :             const auto varname = var->GetName().c_str();
    1793         453 :             if (NCDFIsVarLongitude(gid, varid, varname) ||
    1794         220 :                 NCDFIsVarProjectionX(gid, varid, varname))
    1795             :             {
    1796          41 :                 m_osType = GDAL_DIM_TYPE_HORIZONTAL_X;
    1797         123 :                 auto attrPositive = var->GetAttribute(CF_UNITS);
    1798          41 :                 if (attrPositive)
    1799             :                 {
    1800          38 :                     const auto val = attrPositive->ReadAsString();
    1801          38 :                     if (val)
    1802             :                     {
    1803          38 :                         if (EQUAL(val, CF_DEGREES_EAST))
    1804             :                         {
    1805          10 :                             m_osDirection = "EAST";
    1806             :                         }
    1807             :                     }
    1808             :                 }
    1809             :             }
    1810         369 :             else if (NCDFIsVarLatitude(gid, varid, varname) ||
    1811         177 :                      NCDFIsVarProjectionY(gid, varid, varname))
    1812             :             {
    1813          42 :                 m_osType = GDAL_DIM_TYPE_HORIZONTAL_Y;
    1814         126 :                 auto attrPositive = var->GetAttribute(CF_UNITS);
    1815          42 :                 if (attrPositive)
    1816             :                 {
    1817          39 :                     const auto val = attrPositive->ReadAsString();
    1818          39 :                     if (val)
    1819             :                     {
    1820          39 :                         if (EQUAL(val, CF_DEGREES_NORTH))
    1821             :                         {
    1822          12 :                             m_osDirection = "NORTH";
    1823             :                         }
    1824             :                     }
    1825             :                 }
    1826             :             }
    1827         150 :             else if (NCDFIsVarVerticalCoord(gid, varid, varname))
    1828             :             {
    1829           9 :                 m_osType = GDAL_DIM_TYPE_VERTICAL;
    1830          27 :                 auto attrPositive = var->GetAttribute("positive");
    1831           9 :                 if (attrPositive)
    1832             :                 {
    1833           0 :                     const auto val = attrPositive->ReadAsString();
    1834           0 :                     if (val)
    1835             :                     {
    1836           0 :                         if (EQUAL(val, "up"))
    1837             :                         {
    1838           0 :                             m_osDirection = "UP";
    1839             :                         }
    1840           0 :                         else if (EQUAL(val, "down"))
    1841             :                         {
    1842           0 :                             m_osDirection = "DOWN";
    1843             :                         }
    1844             :                     }
    1845             :                 }
    1846             :             }
    1847         141 :             else if (NCDFIsVarTimeCoord(gid, varid, varname))
    1848             :             {
    1849          49 :                 m_osType = GDAL_DIM_TYPE_TEMPORAL;
    1850             :             }
    1851             :         }
    1852             :     }
    1853        1721 : }
    1854             : 
    1855             : /************************************************************************/
    1856             : /*                          ~netCDFDimension()                          */
    1857             : /************************************************************************/
    1858             : 
    1859        1721 : netCDFDimension::~netCDFDimension()
    1860             : {
    1861        3442 :     auto poParent = m_poParent.lock();
    1862        1721 :     if (poParent)
    1863         235 :         poParent->UnRegisterDimension(this);
    1864        1721 : }
    1865             : 
    1866             : /************************************************************************/
    1867             : /*                             Create()                                 */
    1868             : /************************************************************************/
    1869             : 
    1870             : /* static */
    1871             : std::shared_ptr<netCDFDimension>
    1872        1721 : netCDFDimension::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
    1873             :                         const std::shared_ptr<netCDFGroup> &poParent, int cfid,
    1874             :                         int dimid, size_t nForcedSize,
    1875             :                         const std::string &osType)
    1876             : {
    1877             :     auto poDim(std::make_shared<netCDFDimension>(poShared, cfid, dimid,
    1878        1721 :                                                  nForcedSize, osType));
    1879        1721 :     poDim->m_poParent = poParent;
    1880        1721 :     if (poParent)
    1881         515 :         poParent->RegisterDimension(poDim.get());
    1882        1721 :     return poDim;
    1883             : }
    1884             : 
    1885             : /************************************************************************/
    1886             : /*                         GetIndexingVariable()                        */
    1887             : /************************************************************************/
    1888             : 
    1889             : namespace
    1890             : {
    1891             : struct SetIsInGetIndexingVariable
    1892             : {
    1893             :     netCDFSharedResources *m_poShared;
    1894             : 
    1895         568 :     explicit SetIsInGetIndexingVariable(
    1896             :         netCDFSharedResources *poSharedResources)
    1897         568 :         : m_poShared(poSharedResources)
    1898             :     {
    1899         568 :         m_poShared->SetIsInGetIndexingVariable(true);
    1900         568 :     }
    1901             : 
    1902         568 :     ~SetIsInGetIndexingVariable()
    1903         568 :     {
    1904         568 :         m_poShared->SetIsInGetIndexingVariable(false);
    1905         568 :     }
    1906             : };
    1907             : }  // namespace
    1908             : 
    1909        1743 : std::shared_ptr<GDALMDArray> netCDFDimension::GetIndexingVariable() const
    1910             : {
    1911        1743 :     if (m_poShared->GetIsInIndexingVariable())
    1912        1175 :         return nullptr;
    1913             : 
    1914        1136 :     SetIsInGetIndexingVariable setterIsInGetIndexingVariable(m_poShared.get());
    1915             : 
    1916        1136 :     CPLMutexHolderD(&hNCMutex);
    1917             : 
    1918             :     // First try to find a variable in this group with the same name as the
    1919             :     // dimension
    1920         568 :     int nVarId = 0;
    1921         568 :     if (nc_inq_varid(m_gid, GetName().c_str(), &nVarId) == NC_NOERR)
    1922             :     {
    1923         273 :         int nDims = 0;
    1924         273 :         NCDF_ERR(nc_inq_varndims(m_gid, nVarId, &nDims));
    1925         273 :         int nVarType = NC_NAT;
    1926         273 :         NCDF_ERR(nc_inq_vartype(m_gid, nVarId, &nVarType));
    1927         273 :         if (nDims == 1 || (nDims == 2 && nVarType == NC_CHAR))
    1928             :         {
    1929         273 :             int anDimIds[2] = {};
    1930         273 :             NCDF_ERR(nc_inq_vardimid(m_gid, nVarId, anDimIds));
    1931         273 :             if (anDimIds[0] == m_dimid)
    1932             :             {
    1933         273 :                 if (nDims == 2)
    1934             :                 {
    1935             :                     // Check that there is no variable with the same of the
    1936             :                     // second dimension.
    1937           2 :                     char szExtraDim[NC_MAX_NAME + 1] = {};
    1938           2 :                     NCDF_ERR(nc_inq_dimname(m_gid, anDimIds[1], szExtraDim));
    1939             :                     int nUnused;
    1940           2 :                     if (nc_inq_varid(m_gid, szExtraDim, &nUnused) == NC_NOERR)
    1941           0 :                         return nullptr;
    1942             :                 }
    1943             : 
    1944         273 :                 return netCDFVariable::Create(
    1945         546 :                     m_poShared, m_poParent.lock(), m_gid, nVarId,
    1946         546 :                     std::vector<std::shared_ptr<GDALDimension>>(), nullptr,
    1947         273 :                     false);
    1948             :             }
    1949             :         }
    1950             :     }
    1951             : 
    1952             :     // Otherwise explore the variables in this group to find one that has a
    1953             :     // "coordinates" attribute that references this dimension. If so, let's
    1954             :     // return the variable pointed by the value of "coordinates" as the indexing
    1955             :     // variable. This assumes that there is no other variable that would use
    1956             :     // another variable for the matching dimension of its "coordinates".
    1957         590 :     netCDFGroup oGroup(m_poShared, m_gid);
    1958         590 :     const auto arrayNames = oGroup.GetMDArrayNames(nullptr);
    1959         295 :     std::shared_ptr<GDALMDArray> candidateIndexingVariable;
    1960        1188 :     for (const auto &arrayName : arrayNames)
    1961             :     {
    1962         913 :         auto poArray = oGroup.OpenMDArray(arrayName, nullptr);
    1963             :         const auto poArrayNC =
    1964         913 :             std::dynamic_pointer_cast<netCDFVariable>(poArray);
    1965         913 :         if (!poArrayNC)
    1966           0 :             continue;
    1967             : 
    1968         913 :         const auto &apoArrayDims = poArray->GetDimensions();
    1969         913 :         if (apoArrayDims.size() == 1)
    1970             :         {
    1971         176 :             const auto &poArrayDim = apoArrayDims[0];
    1972             :             const auto poArrayDimNC =
    1973         176 :                 std::dynamic_pointer_cast<netCDFDimension>(poArrayDim);
    1974         352 :             if (poArrayDimNC && poArrayDimNC->m_gid == m_gid &&
    1975         176 :                 poArrayDimNC->m_dimid == m_dimid)
    1976             :             {
    1977             :                 // If the array doesn't have a coordinates variable, but is a 1D
    1978             :                 // array indexed by our dimension, then use it as the indexing
    1979             :                 // variable, provided it is the only such variable.
    1980          59 :                 if (!candidateIndexingVariable)
    1981             :                 {
    1982          51 :                     candidateIndexingVariable = std::move(poArray);
    1983             :                 }
    1984             :                 else
    1985             :                 {
    1986           8 :                     return nullptr;
    1987             :                 }
    1988          51 :                 continue;
    1989             :             }
    1990             :         }
    1991             : 
    1992        1708 :         const auto poCoordinates = poArray->GetAttribute("coordinates");
    1993         854 :         if (!(poCoordinates &&
    1994         854 :               poCoordinates->GetDataType().GetClass() == GEDTC_STRING))
    1995             :         {
    1996         842 :             continue;
    1997             :         }
    1998             : 
    1999             :         // Check that the arrays has as many dimensions as its coordinates
    2000             :         // attribute
    2001             :         const CPLStringList aosCoordinates(
    2002          12 :             NCDFTokenizeCoordinatesAttribute(poCoordinates->ReadAsString()));
    2003          12 :         if (apoArrayDims.size() != static_cast<size_t>(aosCoordinates.size()))
    2004           0 :             continue;
    2005             : 
    2006          26 :         for (size_t i = 0; i < apoArrayDims.size(); ++i)
    2007             :         {
    2008          26 :             const auto &poArrayDim = apoArrayDims[i];
    2009             :             const auto poArrayDimNC =
    2010          26 :                 std::dynamic_pointer_cast<netCDFDimension>(poArrayDim);
    2011             : 
    2012             :             // Check if the array is indexed by the current dimension
    2013          52 :             if (!(poArrayDimNC && poArrayDimNC->m_gid == m_gid &&
    2014          26 :                   poArrayDimNC->m_dimid == m_dimid))
    2015             :             {
    2016          14 :                 continue;
    2017             :             }
    2018             : 
    2019             :             // Caution: some datasets have their coordinates variables in the
    2020             :             // same order than dimensions (i.e. from slowest varying to
    2021             :             // fastest varying), while others have the coordinates variables
    2022             :             // in the opposite order.
    2023             :             // Assume same order by default, but if we find the first variable
    2024             :             // to be of longitude/X type, then assume the opposite order.
    2025          12 :             bool coordinatesInSameOrderThanDimensions = true;
    2026          12 :             if (aosCoordinates.size() > 1)
    2027             :             {
    2028          12 :                 int bFirstGroupId = -1;
    2029          12 :                 int nFirstVarId = -1;
    2030          12 :                 if (NCDFResolveVar(poArrayNC->GetGroupId(), aosCoordinates[0],
    2031          24 :                                    &bFirstGroupId, &nVarId, false) == CE_None &&
    2032          12 :                     (NCDFIsVarLongitude(bFirstGroupId, nFirstVarId,
    2033           4 :                                         aosCoordinates[0]) ||
    2034           4 :                      NCDFIsVarProjectionX(bFirstGroupId, nFirstVarId,
    2035             :                                           aosCoordinates[0])))
    2036             :                 {
    2037           8 :                     coordinatesInSameOrderThanDimensions = false;
    2038             :                 }
    2039             :             }
    2040             : 
    2041          12 :             int nIndexingVarGroupId = -1;
    2042          12 :             int nIndexingVarId = -1;
    2043             :             const size_t nIdxCoordinate = coordinatesInSameOrderThanDimensions
    2044          12 :                                               ? i
    2045           8 :                                               : aosCoordinates.size() - 1 - i;
    2046          12 :             if (NCDFResolveVar(
    2047             :                     poArrayNC->GetGroupId(), aosCoordinates[nIdxCoordinate],
    2048          12 :                     &nIndexingVarGroupId, &nIndexingVarId, false) == CE_None)
    2049             :             {
    2050          12 :                 return netCDFVariable::Create(
    2051          24 :                     m_poShared, m_poParent.lock(), nIndexingVarGroupId,
    2052             :                     nIndexingVarId,
    2053          24 :                     std::vector<std::shared_ptr<GDALDimension>>(), nullptr,
    2054          12 :                     false);
    2055             :             }
    2056             :         }
    2057             :     }
    2058             : 
    2059         275 :     return candidateIndexingVariable;
    2060             : }
    2061             : 
    2062             : /************************************************************************/
    2063             : /*                              Rename()                                */
    2064             : /************************************************************************/
    2065             : 
    2066           4 : bool netCDFDimension::Rename(const std::string &osNewName)
    2067             : {
    2068           4 :     if (m_poShared->IsReadOnly())
    2069             :     {
    2070           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2071             :                  "Rename() not supported on read-only file");
    2072           1 :         return false;
    2073             :     }
    2074           3 :     if (osNewName.empty())
    2075             :     {
    2076           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
    2077           1 :         return false;
    2078             :     }
    2079           4 :     CPLMutexHolderD(&hNCMutex);
    2080           2 :     m_poShared->SetDefineMode(true);
    2081             : 
    2082           2 :     int ret = nc_rename_dim(m_gid, m_dimid, osNewName.c_str());
    2083           2 :     NCDF_ERR(ret);
    2084           2 :     if (ret != NC_NOERR)
    2085           1 :         return false;
    2086             : 
    2087           1 :     BaseRename(osNewName);
    2088             : 
    2089           1 :     return true;
    2090             : }
    2091             : 
    2092             : /************************************************************************/
    2093             : /*                          netCDFVariable()                            */
    2094             : /************************************************************************/
    2095             : 
    2096        1668 : netCDFVariable::netCDFVariable(
    2097             :     const std::shared_ptr<netCDFSharedResources> &poShared, int gid, int varid,
    2098             :     const std::vector<std::shared_ptr<GDALDimension>> &dims,
    2099        1668 :     CSLConstList papszOptions)
    2100        3336 :     : GDALAbstractMDArray(NCDFGetGroupFullName(gid), retrieveName(gid, varid)),
    2101        3336 :       GDALPamMDArray(NCDFGetGroupFullName(gid), retrieveName(gid, varid),
    2102             :                      poShared->GetPAM()),
    2103        8340 :       m_poShared(poShared), m_gid(gid), m_varid(varid), m_dims(dims)
    2104             : {
    2105             :     {
    2106             :         // Cf https://docs.unidata.ucar.edu/netcdf-c/current/group__variables.html#gae6b59e92d1140b5fec56481b0f41b610
    2107        1668 :         size_t nRawDataChunkCacheSize = 0;
    2108        1668 :         size_t nChunkSlots = 0;
    2109        1668 :         float fPreemption = 0.0f;
    2110             :         int ret =
    2111        1668 :             nc_get_var_chunk_cache(m_gid, m_varid, &nRawDataChunkCacheSize,
    2112             :                                    &nChunkSlots, &fPreemption);
    2113        1668 :         if (ret == NC_NOERR)
    2114             :         {
    2115        1343 :             if (const char *pszVar = CSLFetchNameValue(
    2116             :                     papszOptions, "RAW_DATA_CHUNK_CACHE_SIZE"))
    2117             :             {
    2118           1 :                 nRawDataChunkCacheSize =
    2119           1 :                     static_cast<size_t>(std::min<unsigned long long>(
    2120           3 :                         std::strtoull(pszVar, nullptr, 10),
    2121           1 :                         std::numeric_limits<size_t>::max()));
    2122             :             }
    2123        1343 :             if (const char *pszVar =
    2124        1343 :                     CSLFetchNameValue(papszOptions, "CHUNK_SLOTS"))
    2125             :             {
    2126           1 :                 nChunkSlots = static_cast<size_t>(std::min<unsigned long long>(
    2127           3 :                     std::strtoull(pszVar, nullptr, 10),
    2128           1 :                     std::numeric_limits<size_t>::max()));
    2129             :             }
    2130        1343 :             if (const char *pszVar =
    2131        1343 :                     CSLFetchNameValue(papszOptions, "PREEMPTION"))
    2132             :             {
    2133           1 :                 fPreemption = std::max(
    2134           1 :                     0.0f, std::min(1.0f, static_cast<float>(CPLAtof(pszVar))));
    2135             :             }
    2136        1343 :             NCDF_ERR(nc_set_var_chunk_cache(m_gid, m_varid,
    2137             :                                             nRawDataChunkCacheSize, nChunkSlots,
    2138             :                                             fPreemption));
    2139             :         }
    2140         325 :         else if (ret != NC_ENOTNC4)
    2141             :         {
    2142           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2143             :                      "netcdf error #%d : %s .\nat (%s,%s,%d)\n", ret,
    2144             :                      nc_strerror(ret), __FILE__, __FUNCTION__, __LINE__);
    2145             :         }
    2146             :     }
    2147             : 
    2148        1668 :     NCDF_ERR(nc_inq_varndims(m_gid, m_varid, &m_nDims));
    2149        1668 :     NCDF_ERR(nc_inq_vartype(m_gid, m_varid, &m_nVarType));
    2150        1668 :     if (m_nDims == 2 && m_nVarType == NC_CHAR)
    2151             :     {
    2152          35 :         int anDimIds[2] = {};
    2153          35 :         NCDF_ERR(nc_inq_vardimid(m_gid, m_varid, &anDimIds[0]));
    2154             : 
    2155             :         // Check that there is no variable with the same of the
    2156             :         // second dimension.
    2157          35 :         char szExtraDim[NC_MAX_NAME + 1] = {};
    2158          35 :         NCDF_ERR(nc_inq_dimname(m_gid, anDimIds[1], szExtraDim));
    2159             :         int nUnused;
    2160          35 :         if (nc_inq_varid(m_gid, szExtraDim, &nUnused) != NC_NOERR)
    2161             :         {
    2162          13 :             NCDF_ERR(nc_inq_dimlen(m_gid, anDimIds[1], &m_nTextLength));
    2163             :         }
    2164             :     }
    2165             : 
    2166        1668 :     int nShuffle = 0;
    2167        1668 :     int nDeflate = 0;
    2168        1668 :     int nDeflateLevel = 0;
    2169        1668 :     if (nc_inq_var_deflate(m_gid, m_varid, &nShuffle, &nDeflate,
    2170        1668 :                            &nDeflateLevel) == NC_NOERR)
    2171             :     {
    2172        1668 :         if (nDeflate)
    2173             :         {
    2174         107 :             m_aosStructuralInfo.SetNameValue("COMPRESS", "DEFLATE");
    2175             :         }
    2176             :     }
    2177        5004 :     auto unit = netCDFVariable::GetAttribute(CF_UNITS);
    2178        1668 :     if (unit && unit->GetDataType().GetClass() == GEDTC_STRING)
    2179             :     {
    2180         404 :         const char *pszVal = unit->ReadAsString();
    2181         404 :         if (pszVal)
    2182         404 :             m_osUnit = pszVal;
    2183             :     }
    2184        1668 :     m_bWriteGDALTags = CPLTestBool(
    2185             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_TAGS", "YES"));
    2186             : 
    2187             :     // Non-documented option. Only used for test purposes.
    2188        1668 :     if (CPLTestBool(CSLFetchNameValueDef(
    2189             :             papszOptions, "INCLUDE_CHUNK_CACHE_PARAMETERS_IN_STRUCTURAL_INFO",
    2190             :             "NO")))
    2191             :     {
    2192           1 :         size_t nRawDataChunkCacheSize = 0;
    2193           1 :         size_t nChunkSlots = 0;
    2194           1 :         float fPreemption = 0.0f;
    2195           1 :         NCDF_ERR(nc_get_var_chunk_cache(m_gid, m_varid, &nRawDataChunkCacheSize,
    2196             :                                         &nChunkSlots, &fPreemption));
    2197             :         m_aosStructuralInfo.SetNameValue(
    2198             :             "RAW_DATA_CHUNK_CACHE_SIZE",
    2199             :             CPLSPrintf("%" PRIu64,
    2200           1 :                        static_cast<uint64_t>(nRawDataChunkCacheSize)));
    2201             :         m_aosStructuralInfo.SetNameValue(
    2202             :             "CHUNK_SLOTS",
    2203           1 :             CPLSPrintf("%" PRIu64, static_cast<uint64_t>(nChunkSlots)));
    2204             :         m_aosStructuralInfo.SetNameValue("PREEMPTION",
    2205           1 :                                          CPLSPrintf("%f", fPreemption));
    2206             :     }
    2207        1668 : }
    2208             : 
    2209             : /************************************************************************/
    2210             : /*                          ~netCDFVariable()                           */
    2211             : /************************************************************************/
    2212             : 
    2213        3336 : netCDFVariable::~netCDFVariable()
    2214             : {
    2215        3336 :     auto poParent = m_poParent.lock();
    2216        1668 :     if (poParent)
    2217         335 :         poParent->UnRegisterArray(this);
    2218             : 
    2219        1668 :     if (!m_poShared->IsReadOnly() && !m_dims.empty())
    2220             :     {
    2221         254 :         bool bNeedToWriteDummy = false;
    2222         705 :         for (auto &poDim : m_dims)
    2223             :         {
    2224         454 :             auto netCDFDim = std::dynamic_pointer_cast<netCDFDimension>(poDim);
    2225         454 :             CPLAssert(netCDFDim);
    2226         454 :             if (netCDFDim->GetSize() > netCDFDim->GetActualSize())
    2227             :             {
    2228           3 :                 bNeedToWriteDummy = true;
    2229           3 :                 break;
    2230             :             }
    2231             :         }
    2232         254 :         if (bNeedToWriteDummy)
    2233             :         {
    2234           3 :             CPLDebug("netCDF", "Extending array %s to new dimension sizes",
    2235           3 :                      GetName().c_str());
    2236           3 :             m_bGetRawNoDataValueHasRun = false;
    2237           3 :             m_bUseDefaultFillAsNoData = true;
    2238           3 :             const void *pNoData = GetRawNoDataValue();
    2239           6 :             std::vector<GByte> abyDummy(GetDataType().GetSize());
    2240           3 :             if (pNoData == nullptr)
    2241           0 :                 pNoData = abyDummy.data();
    2242           3 :             const auto nDimCount = m_dims.size();
    2243           6 :             std::vector<GUInt64> arrayStartIdx(nDimCount);
    2244           6 :             std::vector<size_t> count(nDimCount, 1);
    2245           6 :             std::vector<GInt64> arrayStep(nDimCount, 0);
    2246           6 :             std::vector<GPtrDiff_t> bufferStride(nDimCount, 0);
    2247           9 :             for (size_t i = 0; i < nDimCount; ++i)
    2248             :             {
    2249           6 :                 arrayStartIdx[i] = m_dims[i]->GetSize() - 1;
    2250             :             }
    2251           6 :             Write(arrayStartIdx.data(), count.data(), arrayStep.data(),
    2252           3 :                   bufferStride.data(), GetDataType(), pNoData);
    2253             :         }
    2254             :     }
    2255        3336 : }
    2256             : 
    2257             : /************************************************************************/
    2258             : /*                             GetDimensions()                          */
    2259             : /************************************************************************/
    2260             : 
    2261             : const std::vector<std::shared_ptr<GDALDimension>> &
    2262        4140 : netCDFVariable::GetDimensions() const
    2263             : {
    2264        4140 :     if (m_nDims == 0 || !m_dims.empty())
    2265        2921 :         return m_dims;
    2266        2438 :     CPLMutexHolderD(&hNCMutex);
    2267        1219 :     std::vector<int> anDimIds(m_nDims);
    2268        1219 :     NCDF_ERR(nc_inq_vardimid(m_gid, m_varid, &anDimIds[0]));
    2269        1219 :     if (m_nDims == 2 && m_nVarType == NC_CHAR && m_nTextLength > 0)
    2270           9 :         anDimIds.resize(1);
    2271        1219 :     m_dims.reserve(m_nDims);
    2272        3513 :     for (const auto &dimid : anDimIds)
    2273             :     {
    2274        4588 :         auto poCachedDim = m_poShared->GetCachedDimension(dimid);
    2275        2294 :         if (poCachedDim == nullptr)
    2276             :         {
    2277             :             const int groupDim =
    2278        1522 :                 m_poShared->GetBelongingGroupOfDim(m_gid, dimid);
    2279             :             poCachedDim =
    2280        3044 :                 netCDFDimension::Create(m_poShared, m_poParent.lock(), groupDim,
    2281        4566 :                                         dimid, 0, std::string());
    2282        1522 :             m_poShared->CacheDimension(dimid, poCachedDim);
    2283             :         }
    2284        2294 :         m_dims.emplace_back(poCachedDim);
    2285             :     }
    2286        1219 :     return m_dims;
    2287             : }
    2288             : 
    2289             : /************************************************************************/
    2290             : /*                         GetStructuralInfo()                          */
    2291             : /************************************************************************/
    2292             : 
    2293          28 : CSLConstList netCDFVariable::GetStructuralInfo() const
    2294             : {
    2295          28 :     return m_aosStructuralInfo.List();
    2296             : }
    2297             : 
    2298             : /************************************************************************/
    2299             : /*                          GetComplexDataType()                        */
    2300             : /************************************************************************/
    2301             : 
    2302         305 : static GDALDataType GetComplexDataType(int gid, int nVarType)
    2303             : {
    2304             :     // First enquire and check that the number of fields is 2
    2305         305 :     size_t nfields = 0;
    2306         305 :     size_t compoundsize = 0;
    2307         305 :     char szName[NC_MAX_NAME + 1] = {};
    2308         305 :     if (nc_inq_compound(gid, nVarType, szName, &compoundsize, &nfields) !=
    2309             :         NC_NOERR)
    2310             :     {
    2311           0 :         return GDT_Unknown;
    2312             :     }
    2313             : 
    2314         305 :     if (nfields != 2 || !STARTS_WITH_CI(szName, "complex"))
    2315             :     {
    2316         136 :         return GDT_Unknown;
    2317             :     }
    2318             : 
    2319             :     // Now check that that two types are the same in the struct.
    2320             :     nc_type field_type1, field_type2;
    2321             :     int field_dims1, field_dims2;
    2322         169 :     if (nc_inq_compound_field(gid, nVarType, 0, nullptr, nullptr, &field_type1,
    2323         169 :                               &field_dims1, nullptr) != NC_NOERR)
    2324             :     {
    2325           0 :         return GDT_Unknown;
    2326             :     }
    2327             : 
    2328         169 :     if (nc_inq_compound_field(gid, nVarType, 0, nullptr, nullptr, &field_type2,
    2329         169 :                               &field_dims2, nullptr) != NC_NOERR)
    2330             :     {
    2331           0 :         return GDT_Unknown;
    2332             :     }
    2333             : 
    2334         169 :     if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
    2335         169 :         (field_dims1 != 0))
    2336             :     {
    2337           0 :         return GDT_Unknown;
    2338             :     }
    2339             : 
    2340         169 :     if (field_type1 == NC_SHORT)
    2341             :     {
    2342          41 :         return GDT_CInt16;
    2343             :     }
    2344         128 :     else if (field_type1 == NC_INT)
    2345             :     {
    2346          40 :         return GDT_CInt32;
    2347             :     }
    2348          88 :     else if (field_type1 == NC_FLOAT)
    2349             :     {
    2350          44 :         return GDT_CFloat32;
    2351             :     }
    2352          44 :     else if (field_type1 == NC_DOUBLE)
    2353             :     {
    2354          44 :         return GDT_CFloat64;
    2355             :     }
    2356             : 
    2357           0 :     return GDT_Unknown;
    2358             : }
    2359             : 
    2360             : /************************************************************************/
    2361             : /*                       GetCompoundDataType()                          */
    2362             : /************************************************************************/
    2363             : 
    2364         136 : static bool GetCompoundDataType(int gid, int nVarType,
    2365             :                                 std::unique_ptr<GDALExtendedDataType> &dt,
    2366             :                                 bool &bPerfectDataTypeMatch)
    2367             : {
    2368         136 :     size_t nfields = 0;
    2369         136 :     size_t compoundsize = 0;
    2370         136 :     char szName[NC_MAX_NAME + 1] = {};
    2371         136 :     if (nc_inq_compound(gid, nVarType, szName, &compoundsize, &nfields) !=
    2372             :         NC_NOERR)
    2373             :     {
    2374           0 :         return false;
    2375             :     }
    2376         136 :     bPerfectDataTypeMatch = true;
    2377         272 :     std::vector<std::unique_ptr<GDALEDTComponent>> comps;
    2378         439 :     for (size_t i = 0; i < nfields; i++)
    2379             :     {
    2380         303 :         nc_type field_type = 0;
    2381         303 :         int field_dims = 0;
    2382         303 :         size_t field_offset = 0;
    2383         303 :         char field_name[NC_MAX_NAME + 1] = {};
    2384         303 :         if (nc_inq_compound_field(gid, nVarType, static_cast<int>(i),
    2385             :                                   field_name, &field_offset, &field_type,
    2386         303 :                                   &field_dims, nullptr) != NC_NOERR)
    2387             :         {
    2388           0 :             return false;
    2389             :         }
    2390         303 :         if (field_dims != 0)
    2391             :         {
    2392             :             // We don't support that
    2393           0 :             return false;
    2394             :         }
    2395           0 :         std::unique_ptr<GDALExtendedDataType> subDt;
    2396         303 :         bool bSubPerfectDataTypeMatch = false;
    2397         303 :         if (!BuildDataType(gid, -1, field_type, subDt,
    2398             :                            bSubPerfectDataTypeMatch))
    2399             :         {
    2400           0 :             return false;
    2401             :         }
    2402         303 :         if (!bSubPerfectDataTypeMatch)
    2403             :         {
    2404           0 :             CPLError(
    2405             :                 CE_Failure, CPLE_NotSupported,
    2406             :                 "Non native GDAL type found in a component of a compound type");
    2407           0 :             return false;
    2408             :         }
    2409             :         auto comp = std::unique_ptr<GDALEDTComponent>(new GDALEDTComponent(
    2410         909 :             std::string(field_name), field_offset, *subDt));
    2411         303 :         comps.emplace_back(std::move(comp));
    2412             :     }
    2413         272 :     dt.reset(new GDALExtendedDataType(
    2414         136 :         GDALExtendedDataType::Create(szName, compoundsize, std::move(comps))));
    2415             : 
    2416         136 :     return dt->GetClass() == GEDTC_COMPOUND;
    2417             : }
    2418             : 
    2419             : /************************************************************************/
    2420             : /*                            BuildDataType()                           */
    2421             : /************************************************************************/
    2422             : 
    2423        1048 : static bool BuildDataType(int gid, int varid, const int nVarTypeIn,
    2424             :                           std::unique_ptr<GDALExtendedDataType> &dt,
    2425             :                           bool &bPerfectDataTypeMatch)
    2426             : {
    2427        1048 :     int nVarType = nVarTypeIn;
    2428        1048 :     GDALDataType eDataType = GDT_Unknown;
    2429        1048 :     bPerfectDataTypeMatch = false;
    2430        1048 :     int eClass = 0;
    2431        1048 :     if (NCDFIsUserDefinedType(gid, nVarType))
    2432             :     {
    2433         368 :         nc_type nBaseType = NC_NAT;
    2434         368 :         nc_inq_user_type(gid, nVarType, nullptr, nullptr, &nBaseType, nullptr,
    2435             :                          &eClass);
    2436         368 :         if (eClass == NC_COMPOUND)
    2437             :         {
    2438         305 :             eDataType = GetComplexDataType(gid, nVarType);
    2439         305 :             if (eDataType != GDT_Unknown)
    2440             :             {
    2441         169 :                 bPerfectDataTypeMatch = true;
    2442         169 :                 dt.reset(new GDALExtendedDataType(
    2443         169 :                     GDALExtendedDataType::Create(eDataType)));
    2444         305 :                 return true;
    2445             :             }
    2446         136 :             else if (GetCompoundDataType(gid, nVarType, dt,
    2447             :                                          bPerfectDataTypeMatch))
    2448             :             {
    2449         136 :                 return true;
    2450             :             }
    2451             :             else
    2452             :             {
    2453           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    2454             :                          "Unsupported netCDF compound data type encountered.");
    2455           0 :                 return false;
    2456             :             }
    2457             :         }
    2458          63 :         else if (eClass == NC_ENUM)
    2459             :         {
    2460          63 :             nVarType = nBaseType;
    2461             :         }
    2462           0 :         else if (eClass == NC_VLEN)
    2463             :         {
    2464           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2465             :                      "VLen data type not supported");
    2466           0 :             return false;
    2467             :         }
    2468           0 :         else if (eClass == NC_OPAQUE)
    2469             :         {
    2470           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2471             :                      "Opaque data type not supported");
    2472           0 :             return false;
    2473             :         }
    2474             :         else
    2475             :         {
    2476           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2477             :                      "Unsupported  netCDF data type encountered.");
    2478           0 :             return false;
    2479             :         }
    2480             :     }
    2481             : 
    2482         743 :     if (nVarType == NC_STRING)
    2483             :     {
    2484          39 :         bPerfectDataTypeMatch = true;
    2485          39 :         dt.reset(
    2486          39 :             new GDALExtendedDataType(GDALExtendedDataType::CreateString()));
    2487          39 :         return true;
    2488             :     }
    2489             :     else
    2490             :     {
    2491         704 :         if (nVarType == NC_BYTE)
    2492             :         {
    2493          60 :             char *pszTemp = nullptr;
    2494          60 :             bool bSignedData = true;
    2495         118 :             if (varid >= 0 &&
    2496          58 :                 NCDFGetAttr(gid, varid, "_Unsigned", &pszTemp) == CE_None)
    2497             :             {
    2498          48 :                 if (EQUAL(pszTemp, "true"))
    2499          47 :                     bSignedData = false;
    2500           1 :                 else if (EQUAL(pszTemp, "false"))
    2501           1 :                     bSignedData = true;
    2502          48 :                 CPLFree(pszTemp);
    2503             :             }
    2504          60 :             if (!bSignedData)
    2505             :             {
    2506          47 :                 eDataType = GDT_Byte;
    2507          47 :                 bPerfectDataTypeMatch = true;
    2508             :             }
    2509             :             else
    2510             :             {
    2511          13 :                 eDataType = GDT_Int8;
    2512          13 :                 bPerfectDataTypeMatch = true;
    2513             :             }
    2514             :         }
    2515         644 :         else if (nVarType == NC_CHAR)
    2516             :         {
    2517             :             // Not sure of this
    2518           1 :             bPerfectDataTypeMatch = true;
    2519           1 :             eDataType = GDT_Byte;
    2520             :         }
    2521         643 :         else if (nVarType == NC_SHORT)
    2522             :         {
    2523         152 :             bPerfectDataTypeMatch = true;
    2524         152 :             char *pszTemp = nullptr;
    2525         152 :             bool bSignedData = true;
    2526         199 :             if (varid >= 0 &&
    2527          47 :                 NCDFGetAttr(gid, varid, "_Unsigned", &pszTemp) == CE_None)
    2528             :             {
    2529          34 :                 if (EQUAL(pszTemp, "true"))
    2530          34 :                     bSignedData = false;
    2531           0 :                 else if (EQUAL(pszTemp, "false"))
    2532           0 :                     bSignedData = true;
    2533          34 :                 CPLFree(pszTemp);
    2534             :             }
    2535         152 :             if (!bSignedData)
    2536             :             {
    2537          34 :                 eDataType = GDT_UInt16;
    2538             :             }
    2539             :             else
    2540             :             {
    2541         118 :                 eDataType = GDT_Int16;
    2542             :             }
    2543             :         }
    2544         491 :         else if (nVarType == NC_INT)
    2545             :         {
    2546         186 :             bPerfectDataTypeMatch = true;
    2547         186 :             eDataType = GDT_Int32;
    2548             :         }
    2549         305 :         else if (nVarType == NC_FLOAT)
    2550             :         {
    2551          78 :             bPerfectDataTypeMatch = true;
    2552          78 :             eDataType = GDT_Float32;
    2553             :         }
    2554         227 :         else if (nVarType == NC_DOUBLE)
    2555             :         {
    2556         101 :             bPerfectDataTypeMatch = true;
    2557         101 :             eDataType = GDT_Float64;
    2558             :         }
    2559         126 :         else if (nVarType == NC_UBYTE)
    2560             :         {
    2561          96 :             bPerfectDataTypeMatch = true;
    2562          96 :             eDataType = GDT_Byte;
    2563             :         }
    2564          30 :         else if (nVarType == NC_USHORT)
    2565             :         {
    2566           9 :             bPerfectDataTypeMatch = true;
    2567           9 :             eDataType = GDT_UInt16;
    2568             :         }
    2569          21 :         else if (nVarType == NC_UINT)
    2570             :         {
    2571           7 :             bPerfectDataTypeMatch = true;
    2572           7 :             eDataType = GDT_UInt32;
    2573             :         }
    2574          14 :         else if (nVarType == NC_INT64)
    2575             :         {
    2576           8 :             bPerfectDataTypeMatch = true;
    2577           8 :             eDataType = GDT_Int64;
    2578             :         }
    2579           6 :         else if (nVarType == NC_UINT64)
    2580             :         {
    2581           6 :             bPerfectDataTypeMatch = true;
    2582           6 :             eDataType = GDT_UInt64;
    2583             :         }
    2584             :         else
    2585             :         {
    2586           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2587             :                      "Unsupported netCDF data type encountered.");
    2588           0 :             return false;
    2589             :         }
    2590             :     }
    2591             : 
    2592         767 :     if (eClass == NC_ENUM && GDALDataTypeIsInteger(eDataType) &&
    2593          63 :         !GDALDataTypeIsComplex(eDataType))
    2594             :     {
    2595          63 :         char szEnumName[NC_MAX_NAME + 1] = {};
    2596          63 :         size_t nMemberCount = 0;
    2597          63 :         NCDF_ERR(nc_inq_enum(gid, nVarTypeIn, szEnumName, nullptr, nullptr,
    2598             :                              &nMemberCount));
    2599         126 :         auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
    2600          63 :         poRAT->CreateColumn("value", GFT_Integer, GFU_MinMax);
    2601          63 :         poRAT->CreateColumn("name", GFT_String, GFU_Name);
    2602          63 :         std::vector<GByte> abyValue(GDALGetDataTypeSizeBytes(eDataType));
    2603          63 :         char szName[NC_MAX_NAME + 1] = {};
    2604         190 :         for (int i = 0;
    2605         190 :              i < static_cast<int>(std::min<size_t>(nMemberCount, INT_MAX)); ++i)
    2606             :         {
    2607         127 :             szName[0] = 0;
    2608         127 :             NCDF_ERR(nc_inq_enum_member(gid, nVarTypeIn, i, szName,
    2609             :                                         abyValue.data()));
    2610         127 :             int nValue = 0;
    2611         127 :             GDALCopyWords(abyValue.data(), eDataType, 0, &nValue, GDT_Int32, 0,
    2612             :                           1);
    2613         127 :             poRAT->SetValue(i, 0, nValue);
    2614         127 :             poRAT->SetValue(i, 1, szName);
    2615             :         }
    2616         189 :         dt.reset(new GDALExtendedDataType(GDALExtendedDataType::Create(
    2617         126 :             szEnumName, eDataType, std::move(poRAT))));
    2618             :     }
    2619             :     else
    2620             :     {
    2621         641 :         dt.reset(
    2622         641 :             new GDALExtendedDataType(GDALExtendedDataType::Create(eDataType)));
    2623             :     }
    2624         704 :     return true;
    2625             : }
    2626             : 
    2627             : /************************************************************************/
    2628             : /*                             GetDataType()                            */
    2629             : /************************************************************************/
    2630             : 
    2631        2255 : const GDALExtendedDataType &netCDFVariable::GetDataType() const
    2632             : {
    2633        2255 :     if (m_dt)
    2634        1943 :         return *m_dt;
    2635         624 :     CPLMutexHolderD(&hNCMutex);
    2636             : 
    2637         312 :     if (m_nDims == 2 && m_nVarType == NC_CHAR && m_nTextLength > 0)
    2638             :     {
    2639           7 :         m_bPerfectDataTypeMatch = true;
    2640           7 :         m_dt.reset(new GDALExtendedDataType(
    2641           7 :             GDALExtendedDataType::CreateString(m_nTextLength)));
    2642             :     }
    2643             :     else
    2644             :     {
    2645         305 :         m_dt.reset(new GDALExtendedDataType(
    2646         305 :             GDALExtendedDataType::Create(GDT_Unknown)));
    2647             : 
    2648         305 :         BuildDataType(m_gid, m_varid, m_nVarType, m_dt,
    2649         305 :                       m_bPerfectDataTypeMatch);
    2650             :     }
    2651         312 :     return *m_dt;
    2652             : }
    2653             : 
    2654             : /************************************************************************/
    2655             : /*                              SetUnit()                               */
    2656             : /************************************************************************/
    2657             : 
    2658           1 : bool netCDFVariable::SetUnit(const std::string &osUnit)
    2659             : {
    2660           1 :     if (osUnit.empty())
    2661             :     {
    2662           0 :         nc_del_att(m_gid, m_varid, CF_UNITS);
    2663           0 :         return true;
    2664             :     }
    2665           3 :     auto poUnits(GetAttribute(CF_UNITS));
    2666           1 :     if (!poUnits)
    2667             :     {
    2668           2 :         poUnits = CreateAttribute(
    2669           3 :             CF_UNITS, {}, GDALExtendedDataType::CreateString(), nullptr);
    2670           1 :         if (!poUnits)
    2671           0 :             return false;
    2672             :     }
    2673           1 :     return poUnits->Write(osUnit.c_str());
    2674             : }
    2675             : 
    2676             : /************************************************************************/
    2677             : /*                            GetSpatialRef()                           */
    2678             : /************************************************************************/
    2679             : 
    2680         124 : std::shared_ptr<OGRSpatialReference> netCDFVariable::GetSpatialRef() const
    2681             : {
    2682         124 :     if (m_bSRSRead)
    2683          50 :         return m_poSRS;
    2684             : 
    2685          74 :     m_bSRSRead = true;
    2686         148 :     netCDFDataset poDS;
    2687          74 :     poDS.ReadAttributes(m_gid, m_varid);
    2688          74 :     int iDimX = 0;
    2689          74 :     int iDimY = 0;
    2690          74 :     int iCount = 1;
    2691         208 :     for (const auto &poDim : GetDimensions())
    2692             :     {
    2693         134 :         if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X)
    2694          28 :             iDimX = iCount;
    2695         106 :         else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y)
    2696          36 :             iDimY = iCount;
    2697         134 :         poDS.papszDimName.AddString(poDim->GetName().c_str());
    2698         134 :         iCount++;
    2699             :     }
    2700          74 :     if ((iDimX == 0 || iDimY == 0) && GetDimensionCount() >= 2)
    2701             :     {
    2702          26 :         iDimX = static_cast<int>(GetDimensionCount());
    2703          26 :         iDimY = iDimX - 1;
    2704             :     }
    2705          74 :     poDS.SetProjectionFromVar(m_gid, m_varid, true);
    2706          74 :     auto poSRS = poDS.GetSpatialRef();
    2707          74 :     if (poSRS)
    2708             :     {
    2709          24 :         m_poSRS.reset(poSRS->Clone());
    2710          24 :         if (iDimX > 0 && iDimY > 0)
    2711             :         {
    2712          24 :             const auto &oMapping = m_poSRS->GetDataAxisToSRSAxisMapping();
    2713          67 :             if (oMapping == std::vector<int>{2, 1} ||
    2714          43 :                 oMapping == std::vector<int>{2, 1, 3})
    2715           5 :                 m_poSRS->SetDataAxisToSRSAxisMapping({iDimY, iDimX});
    2716             :             else
    2717          19 :                 m_poSRS->SetDataAxisToSRSAxisMapping({iDimX, iDimY});
    2718             :         }
    2719             :     }
    2720             : 
    2721          74 :     return m_poSRS;
    2722             : }
    2723             : 
    2724             : /************************************************************************/
    2725             : /*                            SetSpatialRef()                           */
    2726             : /************************************************************************/
    2727             : 
    2728          72 : static void WriteDimAttr(std::shared_ptr<GDALMDArray> &poVar,
    2729             :                          const char *pszAttrName, const char *pszAttrValue)
    2730             : {
    2731         216 :     auto poAttr = poVar->GetAttribute(pszAttrName);
    2732          72 :     if (poAttr)
    2733             :     {
    2734          24 :         const char *pszVal = poAttr->ReadAsString();
    2735          24 :         if (pszVal && !EQUAL(pszVal, pszAttrValue))
    2736             :         {
    2737           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2738             :                      "Variable %s has a %s which is %s and not %s",
    2739           0 :                      poVar->GetName().c_str(), pszAttrName, pszVal,
    2740             :                      pszAttrValue);
    2741             :         }
    2742             :     }
    2743             :     else
    2744             :     {
    2745         144 :         poAttr = poVar->CreateAttribute(
    2746         144 :             pszAttrName, {}, GDALExtendedDataType::CreateString(), nullptr);
    2747          48 :         if (poAttr)
    2748          48 :             poAttr->Write(pszAttrValue);
    2749             :     }
    2750          72 : }
    2751             : 
    2752          28 : static void WriteDimAttrs(const std::shared_ptr<GDALDimension> &dim,
    2753             :                           const char *pszStandardName, const char *pszLongName,
    2754             :                           const char *pszUnits)
    2755             : {
    2756          56 :     auto poVar = dim->GetIndexingVariable();
    2757          28 :     if (poVar)
    2758             :     {
    2759          24 :         WriteDimAttr(poVar, CF_STD_NAME, pszStandardName);
    2760          24 :         WriteDimAttr(poVar, CF_LNG_NAME, pszLongName);
    2761          24 :         WriteDimAttr(poVar, CF_UNITS, pszUnits);
    2762             :     }
    2763             :     else
    2764             :     {
    2765           4 :         CPLError(CE_Warning, CPLE_AppDefined,
    2766             :                  "Dimension %s lacks a indexing variable",
    2767           4 :                  dim->GetName().c_str());
    2768             :     }
    2769          28 : }
    2770             : 
    2771          18 : bool netCDFVariable::SetSpatialRef(const OGRSpatialReference *poSRS)
    2772             : {
    2773          18 :     m_bSRSRead = false;
    2774          18 :     m_poSRS.reset();
    2775             : 
    2776          36 :     CPLMutexHolderD(&hNCMutex);
    2777          18 :     m_poShared->SetDefineMode(true);
    2778             : 
    2779          18 :     if (poSRS == nullptr)
    2780             :     {
    2781           2 :         nc_del_att(m_gid, m_varid, CF_GRD_MAPPING);
    2782           2 :         return true;
    2783             :     }
    2784             : 
    2785          16 :     char *pszCFProjection = nullptr;
    2786             :     int nSRSVarId =
    2787          16 :         NCDFWriteSRSVariable(m_gid, poSRS, &pszCFProjection, m_bWriteGDALTags);
    2788          16 :     if (nSRSVarId < 0 || pszCFProjection == nullptr)
    2789           0 :         return false;
    2790             : 
    2791          16 :     NCDF_ERR(nc_put_att_text(m_gid, m_varid, CF_GRD_MAPPING,
    2792             :                              strlen(pszCFProjection), pszCFProjection));
    2793          16 :     CPLFree(pszCFProjection);
    2794             : 
    2795          16 :     auto apoDims = GetDimensions();
    2796          16 :     if (poSRS->IsProjected())
    2797             :     {
    2798          11 :         bool bWriteX = false;
    2799          11 :         bool bWriteY = false;
    2800          22 :         const std::string osUnits = NCDFGetProjectedCFUnit(poSRS);
    2801          33 :         for (const auto &poDim : apoDims)
    2802             :         {
    2803          22 :             const char *pszStandardName = nullptr;
    2804          22 :             const char *pszLongName = nullptr;
    2805          39 :             if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
    2806          17 :                 EQUAL(poDim->GetName().c_str(), CF_PROJ_X_VAR_NAME))
    2807             :             {
    2808           8 :                 pszStandardName = CF_PROJ_X_COORD;
    2809           8 :                 pszLongName = CF_PROJ_X_COORD_LONG_NAME;
    2810           8 :                 bWriteX = true;
    2811             :             }
    2812          23 :             else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
    2813           9 :                      EQUAL(poDim->GetName().c_str(), CF_PROJ_Y_VAR_NAME))
    2814             :             {
    2815           8 :                 pszStandardName = CF_PROJ_Y_COORD;
    2816           8 :                 pszLongName = CF_PROJ_Y_COORD_LONG_NAME;
    2817           8 :                 bWriteY = true;
    2818             :             }
    2819          22 :             if (pszStandardName && pszLongName)
    2820             :             {
    2821          16 :                 WriteDimAttrs(poDim, pszStandardName, pszLongName,
    2822             :                               osUnits.c_str());
    2823             :             }
    2824             :         }
    2825           6 :         if (!bWriteX && !bWriteY && apoDims.size() >= 2 &&
    2826           6 :             apoDims[apoDims.size() - 2]->GetType().empty() &&
    2827           6 :             apoDims[apoDims.size() - 1]->GetType().empty() &&
    2828          28 :             apoDims[apoDims.size() - 2]->GetIndexingVariable() &&
    2829          12 :             apoDims[apoDims.size() - 1]->GetIndexingVariable())
    2830             :         {
    2831           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    2832             :                      "Dimensions of variable %s have no type declared. "
    2833             :                      "Assuming the last one is X, and the preceding one Y",
    2834           1 :                      GetName().c_str());
    2835           1 :             WriteDimAttrs(apoDims[apoDims.size() - 1], CF_PROJ_X_COORD,
    2836             :                           CF_PROJ_X_COORD_LONG_NAME, osUnits.c_str());
    2837           1 :             WriteDimAttrs(apoDims[apoDims.size() - 2], CF_PROJ_Y_COORD,
    2838             :                           CF_PROJ_Y_COORD_LONG_NAME, osUnits.c_str());
    2839             :         }
    2840             :     }
    2841           5 :     else if (poSRS->IsGeographic())
    2842             :     {
    2843           5 :         bool bWriteX = false;
    2844           5 :         bool bWriteY = false;
    2845          15 :         for (const auto &poDim : apoDims)
    2846             :         {
    2847          10 :             const char *pszStandardName = nullptr;
    2848          10 :             const char *pszLongName = nullptr;
    2849          10 :             const char *pszUnits = "";
    2850          16 :             if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_X ||
    2851           6 :                 EQUAL(poDim->GetName().c_str(), CF_LONGITUDE_VAR_NAME))
    2852             :             {
    2853           4 :                 pszStandardName = CF_LONGITUDE_STD_NAME;
    2854           4 :                 pszLongName = CF_LONGITUDE_LNG_NAME;
    2855           4 :                 pszUnits = CF_DEGREES_EAST;
    2856           4 :                 bWriteX = true;
    2857             :             }
    2858           8 :             else if (poDim->GetType() == GDAL_DIM_TYPE_HORIZONTAL_Y ||
    2859           2 :                      EQUAL(poDim->GetName().c_str(), CF_LATITUDE_VAR_NAME))
    2860             :             {
    2861           4 :                 pszStandardName = CF_LATITUDE_STD_NAME;
    2862           4 :                 pszLongName = CF_LATITUDE_LNG_NAME;
    2863           4 :                 pszUnits = CF_DEGREES_NORTH;
    2864           4 :                 bWriteY = true;
    2865             :             }
    2866          10 :             if (pszStandardName && pszLongName)
    2867             :             {
    2868           8 :                 WriteDimAttrs(poDim, pszStandardName, pszLongName, pszUnits);
    2869             :             }
    2870             :         }
    2871           2 :         if (!bWriteX && !bWriteY && apoDims.size() >= 2 &&
    2872           2 :             apoDims[apoDims.size() - 2]->GetType().empty() &&
    2873           2 :             apoDims[apoDims.size() - 1]->GetType().empty() &&
    2874          12 :             apoDims[apoDims.size() - 2]->GetIndexingVariable() &&
    2875           6 :             apoDims[apoDims.size() - 1]->GetIndexingVariable())
    2876             :         {
    2877           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    2878             :                      "Dimensions of variable %s have no type declared. "
    2879             :                      "Assuming the last one is longitude, "
    2880             :                      "and the preceding one latitude",
    2881           1 :                      GetName().c_str());
    2882           1 :             WriteDimAttrs(apoDims[apoDims.size() - 1], CF_LONGITUDE_STD_NAME,
    2883             :                           CF_LONGITUDE_LNG_NAME, CF_DEGREES_EAST);
    2884           1 :             WriteDimAttrs(apoDims[apoDims.size() - 2], CF_LATITUDE_STD_NAME,
    2885             :                           CF_LATITUDE_LNG_NAME, CF_DEGREES_NORTH);
    2886             :         }
    2887             :     }
    2888             : 
    2889          16 :     return true;
    2890             : }
    2891             : 
    2892             : /************************************************************************/
    2893             : /*                           SetStatistics()                            */
    2894             : /************************************************************************/
    2895             : 
    2896           5 : bool netCDFVariable::SetStatistics(bool bApproxStats, double dfMin,
    2897             :                                    double dfMax, double dfMean, double dfStdDev,
    2898             :                                    GUInt64 nValidCount,
    2899             :                                    CSLConstList papszOptions)
    2900             : {
    2901           6 :     if (!bApproxStats && !m_poShared->IsReadOnly() &&
    2902           1 :         CPLTestBool(
    2903             :             CSLFetchNameValueDef(papszOptions, "UPDATE_METADATA", "NO")))
    2904             :     {
    2905           3 :         auto poAttr = GetAttribute("actual_range");
    2906           1 :         if (!poAttr)
    2907             :         {
    2908             :             poAttr =
    2909           1 :                 CreateAttribute("actual_range", {2}, GetDataType(), nullptr);
    2910             :         }
    2911           1 :         if (poAttr)
    2912             :         {
    2913           2 :             std::vector<GUInt64> startIdx = {0};
    2914           2 :             std::vector<size_t> count = {2};
    2915           1 :             std::vector<double> values = {dfMin, dfMax};
    2916           2 :             poAttr->Write(startIdx.data(), count.data(), nullptr, nullptr,
    2917           2 :                           GDALExtendedDataType::Create(GDT_Float64),
    2918           1 :                           values.data(), nullptr, 0);
    2919             :         }
    2920             :     }
    2921           5 :     return GDALPamMDArray::SetStatistics(bApproxStats, dfMin, dfMax, dfMean,
    2922           5 :                                          dfStdDev, nValidCount, papszOptions);
    2923             : }
    2924             : 
    2925             : /************************************************************************/
    2926             : /*                             GetNCTypeSize()                          */
    2927             : /************************************************************************/
    2928             : 
    2929         659 : static size_t GetNCTypeSize(const GDALExtendedDataType &dt,
    2930             :                             bool bPerfectDataTypeMatch, int nAttType)
    2931             : {
    2932         659 :     auto nElementSize = dt.GetSize();
    2933         659 :     if (!bPerfectDataTypeMatch)
    2934             :     {
    2935          10 :         if (nAttType == NC_BYTE)
    2936             :         {
    2937           4 :             CPLAssert(dt.GetNumericDataType() == GDT_Int16);
    2938           4 :             nElementSize = sizeof(signed char);
    2939             :         }
    2940           6 :         else if (nAttType == NC_INT64)
    2941             :         {
    2942           4 :             CPLAssert(dt.GetNumericDataType() == GDT_Float64);
    2943           4 :             nElementSize = sizeof(GInt64);
    2944             :         }
    2945           2 :         else if (nAttType == NC_UINT64)
    2946             :         {
    2947           2 :             CPLAssert(dt.GetNumericDataType() == GDT_Float64);
    2948           2 :             nElementSize = sizeof(GUInt64);
    2949             :         }
    2950             :         else
    2951             :         {
    2952           0 :             CPLAssert(false);
    2953             :         }
    2954             :     }
    2955         659 :     return nElementSize;
    2956             : }
    2957             : 
    2958             : /************************************************************************/
    2959             : /*                   ConvertNCStringsToCPLStrings()                     */
    2960             : /************************************************************************/
    2961             : 
    2962          73 : static void ConvertNCStringsToCPLStrings(GByte *pBuffer,
    2963             :                                          const GDALExtendedDataType &dt)
    2964             : {
    2965          73 :     switch (dt.GetClass())
    2966             :     {
    2967           0 :         case GEDTC_STRING:
    2968             :         {
    2969             :             char *pszStr;
    2970             :             // cppcheck-suppress pointerSize
    2971           0 :             memcpy(&pszStr, pBuffer, sizeof(char *));
    2972           0 :             if (pszStr)
    2973             :             {
    2974           0 :                 char *pszNewStr = VSIStrdup(pszStr);
    2975           0 :                 nc_free_string(1, &pszStr);
    2976             :                 // cppcheck-suppress pointerSize
    2977           0 :                 memcpy(pBuffer, &pszNewStr, sizeof(char *));
    2978             :             }
    2979           0 :             break;
    2980             :         }
    2981             : 
    2982          69 :         case GEDTC_NUMERIC:
    2983             :         {
    2984          69 :             break;
    2985             :         }
    2986             : 
    2987           4 :         case GEDTC_COMPOUND:
    2988             :         {
    2989           4 :             const auto &comps = dt.GetComponents();
    2990          12 :             for (const auto &comp : comps)
    2991             :             {
    2992           8 :                 ConvertNCStringsToCPLStrings(pBuffer + comp->GetOffset(),
    2993             :                                              comp->GetType());
    2994             :             }
    2995           4 :             break;
    2996             :         }
    2997             :     }
    2998          73 : }
    2999             : 
    3000             : /************************************************************************/
    3001             : /*                            FreeNCStrings()                           */
    3002             : /************************************************************************/
    3003             : 
    3004         150 : static void FreeNCStrings(GByte *pBuffer, const GDALExtendedDataType &dt)
    3005             : {
    3006         150 :     switch (dt.GetClass())
    3007             :     {
    3008           0 :         case GEDTC_STRING:
    3009             :         {
    3010             :             char *pszStr;
    3011             :             // cppcheck-suppress pointerSize
    3012           0 :             memcpy(&pszStr, pBuffer, sizeof(char *));
    3013           0 :             if (pszStr)
    3014             :             {
    3015           0 :                 nc_free_string(1, &pszStr);
    3016             :             }
    3017           0 :             break;
    3018             :         }
    3019             : 
    3020         150 :         case GEDTC_NUMERIC:
    3021             :         {
    3022         150 :             break;
    3023             :         }
    3024             : 
    3025           0 :         case GEDTC_COMPOUND:
    3026             :         {
    3027           0 :             const auto &comps = dt.GetComponents();
    3028           0 :             for (const auto &comp : comps)
    3029             :             {
    3030           0 :                 FreeNCStrings(pBuffer + comp->GetOffset(), comp->GetType());
    3031             :             }
    3032           0 :             break;
    3033             :         }
    3034             :     }
    3035         150 : }
    3036             : 
    3037             : /************************************************************************/
    3038             : /*                          IReadWriteGeneric()                         */
    3039             : /************************************************************************/
    3040             : 
    3041             : namespace
    3042             : {
    3043             : template <typename T> struct GetGByteType
    3044             : {
    3045             : };
    3046             : 
    3047             : template <> struct GetGByteType<void *>
    3048             : {
    3049             :     typedef GByte *type;
    3050             : };
    3051             : 
    3052             : template <> struct GetGByteType<const void *>
    3053             : {
    3054             :     typedef const GByte *type;
    3055             : };
    3056             : }  // namespace
    3057             : 
    3058             : template <typename BufferType, typename NCGetPutVar1FuncType,
    3059             :           typename ReadOrWriteOneElementType>
    3060          15 : bool netCDFVariable::IReadWriteGeneric(
    3061             :     const size_t *arrayStartIdx, const size_t *count, const GInt64 *arrayStep,
    3062             :     const GPtrDiff_t *bufferStride, const GDALExtendedDataType &bufferDataType,
    3063             :     BufferType buffer, NCGetPutVar1FuncType NCGetPutVar1Func,
    3064             :     ReadOrWriteOneElementType ReadOrWriteOneElement) const
    3065             : {
    3066          15 :     CPLAssert(m_nDims > 0);
    3067          30 :     std::vector<size_t> array_idx(m_nDims);
    3068          30 :     std::vector<size_t> stack_count_iters(m_nDims - 1);
    3069             :     typedef typename GetGByteType<BufferType>::type GBytePtrType;
    3070          30 :     std::vector<GBytePtrType> stack_ptr(m_nDims);
    3071          30 :     std::vector<GPtrDiff_t> ptr_inc;
    3072          15 :     ptr_inc.reserve(m_nDims);
    3073          15 :     const auto &eArrayEDT = GetDataType();
    3074          15 :     const bool bSameDT = m_bPerfectDataTypeMatch && eArrayEDT == bufferDataType;
    3075          15 :     const auto nBufferDTSize = bufferDataType.GetSize();
    3076          42 :     for (int i = 0; i < m_nDims; i++)
    3077             :     {
    3078          27 :         ptr_inc.push_back(bufferStride[i] * nBufferDTSize);
    3079             :     }
    3080          15 :     const auto nDimsMinus1 = m_nDims - 1;
    3081          15 :     stack_ptr[0] = static_cast<GBytePtrType>(buffer);
    3082             : 
    3083        4579 :     auto lambdaLastDim = [&](GBytePtrType ptr)
    3084             :     {
    3085          60 :         array_idx[nDimsMinus1] = arrayStartIdx[nDimsMinus1];
    3086          60 :         size_t nIters = count[nDimsMinus1];
    3087         520 :         while (true)
    3088             :         {
    3089         580 :             if (bSameDT)
    3090             :             {
    3091             :                 int ret =
    3092           8 :                     NCGetPutVar1Func(m_gid, m_varid, array_idx.data(), ptr);
    3093           8 :                 NCDF_ERR(ret);
    3094           8 :                 if (ret != NC_NOERR)
    3095           0 :                     return false;
    3096             :             }
    3097             :             else
    3098             :             {
    3099        1144 :                 if (!(this->*ReadOrWriteOneElement)(eArrayEDT, bufferDataType,
    3100         572 :                                                     array_idx.data(), ptr))
    3101           0 :                     return false;
    3102             :             }
    3103         580 :             if ((--nIters) == 0)
    3104          60 :                 break;
    3105         520 :             ptr += ptr_inc[nDimsMinus1];
    3106             :             // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
    3107             :             // thus automatic conversion from negative to big unsigned might
    3108             :             // occur
    3109         520 :             array_idx[nDimsMinus1] = CPLUnsanitizedAdd<size_t>(
    3110             :                 array_idx[nDimsMinus1],
    3111         520 :                 static_cast<GPtrDiff_t>(arrayStep[nDimsMinus1]));
    3112             :         }
    3113          60 :         return true;
    3114             :     };
    3115             : 
    3116          15 :     if (m_nDims == 1)
    3117             :     {
    3118           6 :         return lambdaLastDim(stack_ptr[0]);
    3119             :     }
    3120           9 :     else if (m_nDims == 2)
    3121             :     {
    3122           7 :         auto nIters = count[0];
    3123           7 :         array_idx[0] = arrayStartIdx[0];
    3124             :         while (true)
    3125             :         {
    3126          36 :             if (!lambdaLastDim(stack_ptr[0]))
    3127           0 :                 return false;
    3128          36 :             if ((--nIters) == 0)
    3129           7 :                 break;
    3130          29 :             stack_ptr[0] += ptr_inc[0];
    3131             :             // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
    3132             :             // thus automatic conversion from negative to big unsigned might
    3133             :             // occur
    3134          29 :             array_idx[0] = CPLUnsanitizedAdd<size_t>(
    3135             :                 array_idx[0], static_cast<GPtrDiff_t>(arrayStep[0]));
    3136             :         }
    3137             :     }
    3138           2 :     else if (m_nDims == 3)
    3139             :     {
    3140           1 :         stack_count_iters[0] = count[0];
    3141           1 :         array_idx[0] = arrayStartIdx[0];
    3142           2 :         while (true)
    3143             :         {
    3144           3 :             auto nIters = count[1];
    3145           3 :             array_idx[1] = arrayStartIdx[1];
    3146           3 :             stack_ptr[1] = stack_ptr[0];
    3147             :             while (true)
    3148             :             {
    3149           6 :                 if (!lambdaLastDim(stack_ptr[1]))
    3150           0 :                     return false;
    3151           6 :                 if ((--nIters) == 0)
    3152           3 :                     break;
    3153           3 :                 stack_ptr[1] += ptr_inc[1];
    3154             :                 // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
    3155             :                 // and thus automatic conversion from negative to big unsigned
    3156             :                 // might occur
    3157           3 :                 array_idx[1] = CPLUnsanitizedAdd<size_t>(
    3158           3 :                     array_idx[1], static_cast<GPtrDiff_t>(arrayStep[1]));
    3159             :             }
    3160           3 :             if ((--stack_count_iters[0]) == 0)
    3161           1 :                 break;
    3162           2 :             stack_ptr[0] += ptr_inc[0];
    3163             :             // CPLUnsanitizedAdd needed as arrayStep[] might be negative, and
    3164             :             // thus automatic conversion from negative to big unsigned might
    3165             :             // occur
    3166           2 :             array_idx[0] = CPLUnsanitizedAdd<size_t>(
    3167             :                 array_idx[0], static_cast<GPtrDiff_t>(arrayStep[0]));
    3168             :         }
    3169             :     }
    3170             :     else
    3171             :     {
    3172             :         // Implementation valid for nDims >= 3
    3173             : 
    3174           1 :         int dimIdx = 0;
    3175             :         // Non-recursive implementation. Hence the gotos
    3176             :         // It might be possible to rewrite this without gotos, but I find they
    3177             :         // make it clearer to understand the recursive nature of the code
    3178           9 :     lbl_start:
    3179           9 :         if (dimIdx == nDimsMinus1 - 1)
    3180             :         {
    3181           6 :             array_idx[dimIdx] = arrayStartIdx[dimIdx];
    3182           6 :             auto nIters = count[dimIdx];
    3183             :             while (true)
    3184             :             {
    3185          12 :                 if (!(lambdaLastDim(stack_ptr[dimIdx])))
    3186           0 :                     return false;
    3187          12 :                 if ((--nIters) == 0)
    3188           6 :                     break;
    3189           6 :                 stack_ptr[dimIdx] += ptr_inc[dimIdx];
    3190             :                 // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
    3191             :                 // and thus automatic conversion from negative to big unsigned
    3192             :                 // might occur
    3193           6 :                 array_idx[dimIdx] = CPLUnsanitizedAdd<size_t>(
    3194             :                     array_idx[dimIdx],
    3195           6 :                     static_cast<GPtrDiff_t>(arrayStep[dimIdx]));
    3196             :             }
    3197             :             // If there was a test if( dimIdx > 0 ), that would be valid for
    3198             :             // nDims == 2
    3199           6 :             goto lbl_return_to_caller;
    3200             :         }
    3201             :         else
    3202             :         {
    3203           3 :             array_idx[dimIdx] = arrayStartIdx[dimIdx];
    3204           3 :             stack_count_iters[dimIdx] = count[dimIdx];
    3205             :             while (true)
    3206             :             {
    3207             :                 // Simulate a recursive call to the next dimension
    3208             :                 // Implicitly save back count and ptr
    3209           8 :                 dimIdx++;
    3210           8 :                 stack_ptr[dimIdx] = stack_ptr[dimIdx - 1];
    3211           8 :                 goto lbl_start;
    3212           8 :             lbl_return_to_caller:
    3213           8 :                 dimIdx--;
    3214           8 :                 if ((--stack_count_iters[dimIdx]) == 0)
    3215           3 :                     break;
    3216           5 :                 stack_ptr[dimIdx] += ptr_inc[dimIdx];
    3217             :                 // CPLUnsanitizedAdd needed as arrayStep[] might be negative,
    3218             :                 // and thus automatic conversion from negative to big unsigned
    3219             :                 // might occur
    3220           5 :                 array_idx[dimIdx] = CPLUnsanitizedAdd<size_t>(
    3221             :                     array_idx[dimIdx],
    3222           5 :                     static_cast<GPtrDiff_t>(arrayStep[dimIdx]));
    3223             :             }
    3224           3 :             if (dimIdx > 0)
    3225           2 :                 goto lbl_return_to_caller;
    3226             :         }
    3227             :     }
    3228             : 
    3229           9 :     return true;
    3230             : }
    3231             : 
    3232             : /************************************************************************/
    3233             : /*                          CheckNumericDataType()                      */
    3234             : /************************************************************************/
    3235             : 
    3236         975 : static bool CheckNumericDataType(const GDALExtendedDataType &dt)
    3237             : {
    3238         975 :     const auto klass = dt.GetClass();
    3239         975 :     if (klass == GEDTC_NUMERIC)
    3240         965 :         return dt.GetNumericDataType() != GDT_Unknown;
    3241          10 :     if (klass == GEDTC_STRING)
    3242           0 :         return false;
    3243          10 :     CPLAssert(klass == GEDTC_COMPOUND);
    3244          10 :     const auto &comps = dt.GetComponents();
    3245          31 :     for (const auto &comp : comps)
    3246             :     {
    3247          21 :         if (!CheckNumericDataType(comp->GetType()))
    3248           0 :             return false;
    3249             :     }
    3250          10 :     return true;
    3251             : }
    3252             : 
    3253             : /************************************************************************/
    3254             : /*                            IReadWrite()                              */
    3255             : /************************************************************************/
    3256             : 
    3257             : template <typename BufferType, typename NCGetPutVar1FuncType,
    3258             :           typename NCGetPutVaraFuncType, typename NCGetPutVarmFuncType,
    3259             :           typename ReadOrWriteOneElementType>
    3260         482 : bool netCDFVariable::IReadWrite(
    3261             :     const bool bIsRead, const GUInt64 *arrayStartIdx, const size_t *count,
    3262             :     const GInt64 *arrayStep, const GPtrDiff_t *bufferStride,
    3263             :     const GDALExtendedDataType &bufferDataType, BufferType buffer,
    3264             :     NCGetPutVar1FuncType NCGetPutVar1Func,
    3265             :     NCGetPutVaraFuncType NCGetPutVaraFunc,
    3266             :     NCGetPutVarmFuncType NCGetPutVarmFunc,
    3267             :     ReadOrWriteOneElementType ReadOrWriteOneElement) const
    3268             : {
    3269         964 :     CPLMutexHolderD(&hNCMutex);
    3270         482 :     m_poShared->SetDefineMode(false);
    3271             : 
    3272         482 :     const auto &eDT = GetDataType();
    3273         964 :     std::vector<size_t> startp;
    3274         482 :     startp.reserve(m_nDims);
    3275         482 :     bool bUseSlowPath =
    3276         482 :         !m_bPerfectDataTypeMatch &&
    3277           0 :         !(bIsRead && bufferDataType.GetClass() == GEDTC_NUMERIC &&
    3278           0 :           eDT.GetClass() == GEDTC_NUMERIC &&
    3279           0 :           bufferDataType.GetSize() >= eDT.GetSize());
    3280        1405 :     for (int i = 0; i < m_nDims; i++)
    3281             :     {
    3282             : #if SIZEOF_VOIDP == 4
    3283             :         if (arrayStartIdx[i] > std::numeric_limits<size_t>::max())
    3284             :             return false;
    3285             : #endif
    3286         923 :         startp.push_back(static_cast<size_t>(arrayStartIdx[i]));
    3287             : 
    3288             : #if SIZEOF_VOIDP == 4
    3289             :         if (arrayStep[i] < std::numeric_limits<ptrdiff_t>::min() ||
    3290             :             arrayStep[i] > std::numeric_limits<ptrdiff_t>::max())
    3291             :         {
    3292             :             return false;
    3293             :         }
    3294             : #endif
    3295             : 
    3296         923 :         if (count[i] != 1 && arrayStep[i] <= 0)
    3297           3 :             bUseSlowPath = true;  // netCDF rejects negative or NULL strides
    3298             : 
    3299         923 :         if (bufferStride[i] < 0)
    3300           0 :             bUseSlowPath =
    3301             :                 true;  // and it seems to silently cast to size_t imapp
    3302             :     }
    3303             : 
    3304         487 :     if (eDT.GetClass() == GEDTC_STRING &&
    3305         487 :         bufferDataType.GetClass() == GEDTC_STRING && m_nVarType == NC_STRING)
    3306             :     {
    3307           5 :         if (m_nDims == 0)
    3308             :         {
    3309           2 :             return (this->*ReadOrWriteOneElement)(eDT, bufferDataType, nullptr,
    3310           2 :                                                   buffer);
    3311             :         }
    3312             : 
    3313           3 :         return IReadWriteGeneric(startp.data(), count, arrayStep, bufferStride,
    3314             :                                  bufferDataType, buffer, NCGetPutVar1Func,
    3315           3 :                                  ReadOrWriteOneElement);
    3316             :     }
    3317             : 
    3318         477 :     if (!CheckNumericDataType(eDT))
    3319           0 :         return false;
    3320         477 :     if (!CheckNumericDataType(bufferDataType))
    3321           0 :         return false;
    3322             : 
    3323         477 :     if (m_nDims == 0)
    3324             :     {
    3325          14 :         return (this->*ReadOrWriteOneElement)(eDT, bufferDataType, nullptr,
    3326          14 :                                               buffer);
    3327             :     }
    3328             : 
    3329         923 :     if (!bUseSlowPath &&
    3330         910 :         ((GDALDataTypeIsComplex(bufferDataType.GetNumericDataType()) ||
    3331         450 :           bufferDataType.GetClass() == GEDTC_COMPOUND) &&
    3332          15 :          bufferDataType == eDT))
    3333             :     {
    3334             :         // nc_get_varm() not supported for non-atomic types.
    3335          14 :         ptrdiff_t nExpectedBufferStride = 1;
    3336          40 :         for (int i = m_nDims; i != 0;)
    3337             :         {
    3338          26 :             --i;
    3339          26 :             if (count[i] != 1 &&
    3340          17 :                 (arrayStep[i] != 1 || bufferStride[i] != nExpectedBufferStride))
    3341             :             {
    3342           0 :                 bUseSlowPath = true;
    3343           0 :                 break;
    3344             :             }
    3345          26 :             nExpectedBufferStride *= count[i];
    3346             :         }
    3347          14 :         if (!bUseSlowPath)
    3348             :         {
    3349             :             int ret =
    3350          14 :                 NCGetPutVaraFunc(m_gid, m_varid, startp.data(), count, buffer);
    3351          14 :             NCDF_ERR(ret);
    3352          14 :             return ret == NC_NOERR;
    3353             :         }
    3354             :     }
    3355             : 
    3356         891 :     if (bUseSlowPath || bufferDataType.GetClass() == GEDTC_COMPOUND ||
    3357         445 :         eDT.GetClass() == GEDTC_COMPOUND ||
    3358         445 :         (!bIsRead &&
    3359        1287 :          bufferDataType.GetNumericDataType() != eDT.GetNumericDataType()) ||
    3360         392 :         (bIsRead && bufferDataType.GetSize() < eDT.GetSize()))
    3361             :     {
    3362           8 :         return IReadWriteGeneric(startp.data(), count, arrayStep, bufferStride,
    3363             :                                  bufferDataType, buffer, NCGetPutVar1Func,
    3364           8 :                                  ReadOrWriteOneElement);
    3365             :     }
    3366             : 
    3367         441 :     bUseSlowPath = false;
    3368         441 :     ptrdiff_t nExpectedBufferStride = 1;
    3369        1301 :     for (int i = m_nDims; i != 0;)
    3370             :     {
    3371         871 :         --i;
    3372         871 :         if (count[i] != 1 &&
    3373         517 :             (arrayStep[i] != 1 || bufferStride[i] != nExpectedBufferStride))
    3374             :         {
    3375          11 :             bUseSlowPath = true;
    3376          11 :             break;
    3377             :         }
    3378         860 :         nExpectedBufferStride *= count[i];
    3379             :     }
    3380         441 :     if (!bUseSlowPath)
    3381             :     {
    3382             :         // nc_get_varm() is terribly inefficient, so use nc_get_vara()
    3383             :         // when possible.
    3384             :         int ret =
    3385         430 :             NCGetPutVaraFunc(m_gid, m_varid, startp.data(), count, buffer);
    3386         430 :         if (ret != NC_NOERR)
    3387             :         {
    3388           1 :             NCDF_ERR(ret);
    3389           1 :             return false;
    3390             :         }
    3391         807 :         if (bIsRead &&
    3392         378 :             (!m_bPerfectDataTypeMatch ||
    3393         378 :              bufferDataType.GetNumericDataType() != eDT.GetNumericDataType()))
    3394             :         {
    3395             :             // If the buffer data type is "larger" or of the same size as the
    3396             :             // native data type, we can do a in-place conversion
    3397          16 :             GByte *pabyBuffer =
    3398             :                 static_cast<GByte *>(const_cast<void *>(buffer));
    3399          16 :             CPLAssert(bufferDataType.GetSize() >= eDT.GetSize());
    3400          16 :             const auto nDTSize = eDT.GetSize();
    3401          16 :             const auto nBufferDTSize = bufferDataType.GetSize();
    3402          16 :             if (!m_bPerfectDataTypeMatch &&
    3403           0 :                 (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE))
    3404             :             {
    3405             :                 // native NC type translates into GDAL data type of larger size
    3406           0 :                 for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
    3407             :                 {
    3408             :                     GByte abySrc[sizeof(
    3409             :                         double)];  // 2 is enough here, but sizeof(double) make
    3410             :                                    // MSVC happy
    3411           0 :                     abySrc[0] = *(pabyBuffer + i);
    3412           0 :                     ConvertNCToGDAL(&abySrc[0]);
    3413           0 :                     GDALExtendedDataType::CopyValue(
    3414           0 :                         &abySrc[0], eDT, pabyBuffer + i * nBufferDTSize,
    3415             :                         bufferDataType);
    3416           0 :                 }
    3417             :             }
    3418          16 :             else if (!m_bPerfectDataTypeMatch)
    3419             :             {
    3420             :                 // native NC type translates into GDAL data type of same size
    3421           0 :                 CPLAssert(m_nVarType == NC_INT64 || m_nVarType == NC_UINT64);
    3422           0 :                 for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
    3423             :                 {
    3424           0 :                     ConvertNCToGDAL(pabyBuffer + i * nDTSize);
    3425           0 :                     GDALExtendedDataType::CopyValue(
    3426           0 :                         pabyBuffer + i * nDTSize, eDT,
    3427           0 :                         pabyBuffer + i * nBufferDTSize, bufferDataType);
    3428             :                 }
    3429             :             }
    3430             :             else
    3431             :             {
    3432        2196 :                 for (ptrdiff_t i = nExpectedBufferStride - 1; i >= 0; --i)
    3433             :                 {
    3434        2180 :                     GDALExtendedDataType::CopyValue(
    3435        2180 :                         pabyBuffer + i * nDTSize, eDT,
    3436        2180 :                         pabyBuffer + i * nBufferDTSize, bufferDataType);
    3437             :                 }
    3438             :             }
    3439             :         }
    3440         429 :         return true;
    3441             :     }
    3442             :     else
    3443             :     {
    3444          11 :         if (bufferDataType.GetNumericDataType() != eDT.GetNumericDataType())
    3445             :         {
    3446           4 :             return IReadWriteGeneric(startp.data(), count, arrayStep,
    3447             :                                      bufferStride, bufferDataType, buffer,
    3448           4 :                                      NCGetPutVar1Func, ReadOrWriteOneElement);
    3449             :         }
    3450          14 :         std::vector<ptrdiff_t> stridep;
    3451           7 :         stridep.reserve(m_nDims);
    3452           7 :         std::vector<ptrdiff_t> imapp;
    3453           7 :         imapp.reserve(m_nDims);
    3454          25 :         for (int i = 0; i < m_nDims; i++)
    3455             :         {
    3456          18 :             stridep.push_back(
    3457          18 :                 static_cast<ptrdiff_t>(count[i] == 1 ? 1 : arrayStep[i]));
    3458          18 :             imapp.push_back(static_cast<ptrdiff_t>(bufferStride[i]));
    3459             :         }
    3460             : 
    3461           7 :         if (!m_poShared->GetImappIsInElements())
    3462             :         {
    3463             :             const size_t nMul =
    3464           0 :                 GetNCTypeSize(eDT, m_bPerfectDataTypeMatch, m_nVarType);
    3465           0 :             for (int i = 0; i < m_nDims; ++i)
    3466             :             {
    3467           0 :                 imapp[i] = static_cast<ptrdiff_t>(imapp[i] * nMul);
    3468             :             }
    3469             :         }
    3470           7 :         int ret = NCGetPutVarmFunc(m_gid, m_varid, startp.data(), count,
    3471           7 :                                    stridep.data(), imapp.data(), buffer);
    3472           7 :         NCDF_ERR(ret);
    3473           7 :         return ret == NC_NOERR;
    3474             :     }
    3475             : }
    3476             : 
    3477             : /************************************************************************/
    3478             : /*                          ConvertNCToGDAL()                           */
    3479             : /************************************************************************/
    3480             : 
    3481         579 : void netCDFVariable::ConvertNCToGDAL(GByte *buffer) const
    3482             : {
    3483         579 :     if (!m_bPerfectDataTypeMatch)
    3484             :     {
    3485           0 :         if (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE)
    3486             :         {
    3487           0 :             short s = reinterpret_cast<signed char *>(buffer)[0];
    3488           0 :             memcpy(buffer, &s, sizeof(s));
    3489             :         }
    3490           0 :         else if (m_nVarType == NC_INT64)
    3491             :         {
    3492           0 :             double v =
    3493           0 :                 static_cast<double>(reinterpret_cast<GInt64 *>(buffer)[0]);
    3494           0 :             memcpy(buffer, &v, sizeof(v));
    3495             :         }
    3496           0 :         else if (m_nVarType == NC_UINT64)
    3497             :         {
    3498           0 :             double v =
    3499           0 :                 static_cast<double>(reinterpret_cast<GUInt64 *>(buffer)[0]);
    3500           0 :             memcpy(buffer, &v, sizeof(v));
    3501             :         }
    3502             :     }
    3503         579 : }
    3504             : 
    3505             : /************************************************************************/
    3506             : /*                           ReadOneElement()                           */
    3507             : /************************************************************************/
    3508             : 
    3509         580 : bool netCDFVariable::ReadOneElement(const GDALExtendedDataType &src_datatype,
    3510             :                                     const GDALExtendedDataType &bufferDataType,
    3511             :                                     const size_t *array_idx,
    3512             :                                     void *pDstBuffer) const
    3513             : {
    3514         580 :     if (src_datatype.GetClass() == GEDTC_STRING)
    3515             :     {
    3516           1 :         char *pszStr = nullptr;
    3517           1 :         int ret = nc_get_var1_string(m_gid, m_varid, array_idx, &pszStr);
    3518           1 :         NCDF_ERR(ret);
    3519           1 :         if (ret != NC_NOERR)
    3520           0 :             return false;
    3521           1 :         GDALExtendedDataType::CopyValue(&pszStr, src_datatype, pDstBuffer,
    3522             :                                         bufferDataType);
    3523           1 :         nc_free_string(1, &pszStr);
    3524           1 :         return true;
    3525             :     }
    3526             : 
    3527             :     std::vector<GByte> abySrc(std::max(
    3528           0 :         src_datatype.GetSize(),
    3529        1158 :         GetNCTypeSize(src_datatype, m_bPerfectDataTypeMatch, m_nVarType)));
    3530             : 
    3531         579 :     int ret = nc_get_var1(m_gid, m_varid, array_idx, &abySrc[0]);
    3532         579 :     NCDF_ERR(ret);
    3533         579 :     if (ret != NC_NOERR)
    3534           0 :         return false;
    3535             : 
    3536         579 :     ConvertNCToGDAL(&abySrc[0]);
    3537             : 
    3538         579 :     GDALExtendedDataType::CopyValue(&abySrc[0], src_datatype, pDstBuffer,
    3539             :                                     bufferDataType);
    3540         579 :     return true;
    3541             : }
    3542             : 
    3543             : /************************************************************************/
    3544             : /*                                   IRead()                            */
    3545             : /************************************************************************/
    3546             : 
    3547         510 : bool netCDFVariable::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
    3548             :                            const GInt64 *arrayStep,
    3549             :                            const GPtrDiff_t *bufferStride,
    3550             :                            const GDALExtendedDataType &bufferDataType,
    3551             :                            void *pDstBuffer) const
    3552             : {
    3553         510 :     if (m_nDims == 2 && m_nVarType == NC_CHAR && GetDimensions().size() == 1)
    3554             :     {
    3555           8 :         CPLMutexHolderD(&hNCMutex);
    3556           4 :         m_poShared->SetDefineMode(false);
    3557             : 
    3558           4 :         if (bufferDataType.GetClass() != GEDTC_STRING)
    3559           0 :             return false;
    3560           4 :         char **ppszDstBuffer = static_cast<char **>(pDstBuffer);
    3561           4 :         size_t array_idx[2] = {static_cast<size_t>(arrayStartIdx[0]), 0};
    3562           4 :         size_t array_count[2] = {1, m_nTextLength};
    3563           4 :         std::string osTmp(m_nTextLength, 0);
    3564           4 :         char *pszTmp = &osTmp[0];
    3565           4 :         bool ret = true;
    3566          12 :         for (size_t i = 0; ret && i < count[0]; i++)
    3567             :         {
    3568             :             int ncErr =
    3569           8 :                 nc_get_vara(m_gid, m_varid, array_idx, array_count, pszTmp);
    3570           8 :             NCDF_ERR(ncErr);
    3571           8 :             ret = ncErr == NC_NOERR;
    3572           8 :             if (ret)
    3573             :             {
    3574           8 :                 *ppszDstBuffer = CPLStrdup(pszTmp);
    3575           8 :                 array_idx[0] = static_cast<size_t>(array_idx[0] + arrayStep[0]);
    3576           8 :                 ppszDstBuffer += bufferStride[0];
    3577             :             }
    3578             :         }
    3579           4 :         return ret;
    3580             :     }
    3581             : 
    3582         506 :     if (m_poCachedArray)
    3583             :     {
    3584           5 :         const auto nDims = GetDimensionCount();
    3585           5 :         std::vector<GUInt64> modifiedArrayStartIdx(nDims);
    3586           5 :         bool canUseCache = true;
    3587          13 :         for (size_t i = 0; i < nDims; i++)
    3588             :         {
    3589          17 :             if (arrayStartIdx[i] >= m_cachedArrayStartIdx[i] &&
    3590           8 :                 arrayStartIdx[i] + (count[i] - 1) * arrayStep[i] <=
    3591           8 :                     m_cachedArrayStartIdx[i] + m_cachedCount[i] - 1)
    3592             :             {
    3593           8 :                 modifiedArrayStartIdx[i] =
    3594           8 :                     arrayStartIdx[i] - m_cachedArrayStartIdx[i];
    3595             :             }
    3596             :             else
    3597             :             {
    3598           1 :                 canUseCache = false;
    3599           1 :                 break;
    3600             :             }
    3601             :         }
    3602           5 :         if (canUseCache)
    3603             :         {
    3604           4 :             return m_poCachedArray->Read(modifiedArrayStartIdx.data(), count,
    3605             :                                          arrayStep, bufferStride,
    3606           4 :                                          bufferDataType, pDstBuffer);
    3607             :         }
    3608             :     }
    3609             : 
    3610         502 :     if (IsTransposedRequest(count, bufferStride))
    3611             :     {
    3612          84 :         return ReadForTransposedRequest(arrayStartIdx, count, arrayStep,
    3613             :                                         bufferStride, bufferDataType,
    3614          84 :                                         pDstBuffer);
    3615             :     }
    3616             : 
    3617         418 :     return IReadWrite(true, arrayStartIdx, count, arrayStep, bufferStride,
    3618             :                       bufferDataType, pDstBuffer, nc_get_var1, nc_get_vara,
    3619         418 :                       nc_get_varm, &netCDFVariable::ReadOneElement);
    3620             : }
    3621             : 
    3622             : /************************************************************************/
    3623             : /*                             IAdviseRead()                            */
    3624             : /************************************************************************/
    3625             : 
    3626           4 : bool netCDFVariable::IAdviseRead(const GUInt64 *arrayStartIdx,
    3627             :                                  const size_t *count,
    3628             :                                  CSLConstList /* papszOptions */) const
    3629             : {
    3630           4 :     const auto nDims = GetDimensionCount();
    3631           4 :     if (nDims == 0)
    3632           0 :         return true;
    3633           4 :     const auto &eDT = GetDataType();
    3634           4 :     if (eDT.GetClass() != GEDTC_NUMERIC)
    3635           0 :         return false;
    3636             : 
    3637           4 :     m_poCachedArray.reset();
    3638             : 
    3639           4 :     size_t nElts = 1;
    3640          12 :     for (size_t i = 0; i < nDims; i++)
    3641           8 :         nElts *= count[i];
    3642             : 
    3643           4 :     void *pData = VSI_MALLOC2_VERBOSE(nElts, eDT.GetSize());
    3644           4 :     if (pData == nullptr)
    3645           0 :         return false;
    3646             : 
    3647           4 :     if (!Read(arrayStartIdx, count, nullptr, nullptr, eDT, pData))
    3648             :     {
    3649           0 :         VSIFree(pData);
    3650           0 :         return false;
    3651             :     }
    3652             : 
    3653             :     auto poDS = std::unique_ptr<GDALDataset>(
    3654           8 :         MEMDataset::CreateMultiDimensional("", nullptr, nullptr));
    3655           8 :     auto poGroup = poDS->GetRootGroup();
    3656             : 
    3657           4 :     std::vector<std::shared_ptr<GDALDimension>> apoMemDims;
    3658           4 :     const auto &poDims = GetDimensions();
    3659          12 :     for (size_t i = 0; i < nDims; i++)
    3660             :     {
    3661             :         apoMemDims.emplace_back(
    3662          24 :             poGroup->CreateDimension(poDims[i]->GetName(), std::string(),
    3663          24 :                                      std::string(), count[i], nullptr));
    3664             :     }
    3665             :     m_poCachedArray =
    3666           4 :         poGroup->CreateMDArray(GetName(), apoMemDims, eDT, nullptr);
    3667           4 :     m_poCachedArray->Write(std::vector<GUInt64>(nDims).data(), count, nullptr,
    3668             :                            nullptr, eDT, pData);
    3669           4 :     m_cachedArrayStartIdx.resize(nDims);
    3670           4 :     memcpy(&m_cachedArrayStartIdx[0], arrayStartIdx, nDims * sizeof(GUInt64));
    3671           4 :     m_cachedCount.resize(nDims);
    3672           4 :     memcpy(&m_cachedCount[0], count, nDims * sizeof(size_t));
    3673           4 :     VSIFree(pData);
    3674           4 :     return true;
    3675             : }
    3676             : 
    3677             : /************************************************************************/
    3678             : /*                          ConvertGDALToNC()                           */
    3679             : /************************************************************************/
    3680             : 
    3681          21 : void netCDFVariable::ConvertGDALToNC(GByte *buffer) const
    3682             : {
    3683          21 :     if (!m_bPerfectDataTypeMatch)
    3684             :     {
    3685           0 :         if (m_nVarType == NC_CHAR || m_nVarType == NC_BYTE)
    3686             :         {
    3687           0 :             const auto c =
    3688           0 :                 static_cast<signed char>(reinterpret_cast<short *>(buffer)[0]);
    3689           0 :             memcpy(buffer, &c, sizeof(c));
    3690             :         }
    3691           0 :         else if (m_nVarType == NC_INT64)
    3692             :         {
    3693           0 :             const auto v =
    3694           0 :                 static_cast<GInt64>(reinterpret_cast<double *>(buffer)[0]);
    3695           0 :             memcpy(buffer, &v, sizeof(v));
    3696             :         }
    3697           0 :         else if (m_nVarType == NC_UINT64)
    3698             :         {
    3699           0 :             const auto v =
    3700           0 :                 static_cast<GUInt64>(reinterpret_cast<double *>(buffer)[0]);
    3701           0 :             memcpy(buffer, &v, sizeof(v));
    3702             :         }
    3703             :     }
    3704          21 : }
    3705             : 
    3706             : /************************************************************************/
    3707             : /*                          WriteOneElement()                           */
    3708             : /************************************************************************/
    3709             : 
    3710           8 : bool netCDFVariable::WriteOneElement(const GDALExtendedDataType &dst_datatype,
    3711             :                                      const GDALExtendedDataType &bufferDataType,
    3712             :                                      const size_t *array_idx,
    3713             :                                      const void *pSrcBuffer) const
    3714             : {
    3715           8 :     if (dst_datatype.GetClass() == GEDTC_STRING)
    3716             :     {
    3717           1 :         const char *pszStr = (static_cast<const char *const *>(pSrcBuffer))[0];
    3718           1 :         int ret = nc_put_var1_string(m_gid, m_varid, array_idx, &pszStr);
    3719           1 :         NCDF_ERR(ret);
    3720           1 :         return ret == NC_NOERR;
    3721             :     }
    3722             : 
    3723           7 :     std::vector<GByte> abyTmp(dst_datatype.GetSize());
    3724           7 :     GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType, &abyTmp[0],
    3725             :                                     dst_datatype);
    3726             : 
    3727           7 :     ConvertGDALToNC(&abyTmp[0]);
    3728             : 
    3729           7 :     int ret = nc_put_var1(m_gid, m_varid, array_idx, &abyTmp[0]);
    3730           7 :     NCDF_ERR(ret);
    3731           7 :     return ret == NC_NOERR;
    3732             : }
    3733             : 
    3734             : /************************************************************************/
    3735             : /*                                IWrite()                              */
    3736             : /************************************************************************/
    3737             : 
    3738          67 : bool netCDFVariable::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
    3739             :                             const GInt64 *arrayStep,
    3740             :                             const GPtrDiff_t *bufferStride,
    3741             :                             const GDALExtendedDataType &bufferDataType,
    3742             :                             const void *pSrcBuffer)
    3743             : {
    3744          67 :     m_bHasWrittenData = true;
    3745             : 
    3746          67 :     m_poCachedArray.reset();
    3747             : 
    3748          67 :     if (m_nDims == 2 && m_nVarType == NC_CHAR && GetDimensions().size() == 1)
    3749             :     {
    3750           6 :         CPLMutexHolderD(&hNCMutex);
    3751           3 :         m_poShared->SetDefineMode(false);
    3752             : 
    3753           3 :         if (bufferDataType.GetClass() != GEDTC_STRING)
    3754           0 :             return false;
    3755           3 :         const char *const *ppszSrcBuffer =
    3756             :             static_cast<const char *const *>(pSrcBuffer);
    3757           3 :         size_t array_idx[2] = {static_cast<size_t>(arrayStartIdx[0]), 0};
    3758           3 :         size_t array_count[2] = {1, m_nTextLength};
    3759           6 :         std::string osTmp(m_nTextLength, 0);
    3760           8 :         for (size_t i = 0; i < count[0]; i++)
    3761             :         {
    3762           5 :             const char *pszStr = *ppszSrcBuffer;
    3763           5 :             memset(&osTmp[0], 0, m_nTextLength);
    3764           5 :             if (pszStr)
    3765             :             {
    3766           5 :                 size_t nLen = strlen(pszStr);
    3767           5 :                 memcpy(&osTmp[0], pszStr, std::min(m_nTextLength, nLen));
    3768             :             }
    3769             :             int ret =
    3770           5 :                 nc_put_vara(m_gid, m_varid, array_idx, array_count, &osTmp[0]);
    3771           5 :             NCDF_ERR(ret);
    3772           5 :             if (ret != NC_NOERR)
    3773           0 :                 return false;
    3774           5 :             array_idx[0] = static_cast<size_t>(array_idx[0] + arrayStep[0]);
    3775           5 :             ppszSrcBuffer += bufferStride[0];
    3776             :         }
    3777           3 :         return true;
    3778             :     }
    3779             : 
    3780          64 :     return IReadWrite(false, arrayStartIdx, count, arrayStep, bufferStride,
    3781             :                       bufferDataType, pSrcBuffer, nc_put_var1, nc_put_vara,
    3782          64 :                       nc_put_varm, &netCDFVariable::WriteOneElement);
    3783             : }
    3784             : 
    3785             : /************************************************************************/
    3786             : /*                          GetRawNoDataValue()                         */
    3787             : /************************************************************************/
    3788             : 
    3789         193 : const void *netCDFVariable::GetRawNoDataValue() const
    3790             : {
    3791         193 :     const auto &dt = GetDataType();
    3792         193 :     if (dt.GetClass() != GEDTC_NUMERIC)
    3793           2 :         return nullptr;
    3794             : 
    3795         191 :     if (m_bGetRawNoDataValueHasRun)
    3796             :     {
    3797          94 :         return m_abyNoData.empty() ? nullptr : m_abyNoData.data();
    3798             :     }
    3799             : 
    3800          97 :     m_bGetRawNoDataValueHasRun = true;
    3801             : 
    3802          97 :     const char *pszAttrName = NCDF_FillValue;
    3803         291 :     auto poAttr = GetAttribute(pszAttrName);
    3804          97 :     if (!poAttr)
    3805             :     {
    3806          67 :         pszAttrName = "missing_value";
    3807          67 :         poAttr = GetAttribute(pszAttrName);
    3808             :     }
    3809          97 :     if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
    3810             :     {
    3811          33 :         auto oRawResult = poAttr->ReadAsRaw();
    3812          33 :         if (oRawResult.data())
    3813             :         {
    3814             :             // Round-trip attribute value to target data type and back
    3815             :             // to attribute data type to ensure there is no loss
    3816             :             // Normally _FillValue data type should be the same
    3817             :             // as the array one, but this is not always the case.
    3818             :             // For example NASA GEDI L2B products have Float64
    3819             :             // _FillValue for Float32 variables.
    3820          33 :             m_abyNoData.resize(dt.GetSize());
    3821          33 :             GDALExtendedDataType::CopyValue(oRawResult.data(),
    3822          33 :                                             poAttr->GetDataType(),
    3823          33 :                                             m_abyNoData.data(), dt);
    3824          66 :             std::vector<GByte> abyTmp(poAttr->GetDataType().GetSize());
    3825          33 :             GDALExtendedDataType::CopyValue(
    3826          33 :                 m_abyNoData.data(), dt, abyTmp.data(), poAttr->GetDataType());
    3827          66 :             std::vector<GByte> abyOri;
    3828          33 :             abyOri.assign(oRawResult.data(),
    3829          33 :                           oRawResult.data() + oRawResult.size());
    3830          33 :             if (abyOri == abyTmp)
    3831          32 :                 return m_abyNoData.data();
    3832           1 :             m_abyNoData.clear();
    3833           1 :             char *pszVal = nullptr;
    3834           1 :             GDALExtendedDataType::CopyValue(
    3835           1 :                 oRawResult.data(), poAttr->GetDataType(), &pszVal,
    3836           2 :                 GDALExtendedDataType::CreateString());
    3837           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    3838             :                      "%s attribute value (%s) is not in the range of the "
    3839             :                      "variable data type",
    3840           1 :                      pszAttrName, pszVal ? pszVal : "(null)");
    3841           1 :             CPLFree(pszVal);
    3842           1 :             return nullptr;
    3843             :         }
    3844             :     }
    3845          64 :     else if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_STRING)
    3846             :     {
    3847           3 :         const char *pszVal = poAttr->ReadAsString();
    3848           3 :         if (pszVal)
    3849             :         {
    3850             :             // Round-trip attribute value to target data type and back
    3851             :             // to attribute data type to ensure there is no loss
    3852           3 :             m_abyNoData.resize(dt.GetSize());
    3853           3 :             GDALExtendedDataType::CopyValue(&pszVal, poAttr->GetDataType(),
    3854           3 :                                             m_abyNoData.data(), dt);
    3855           3 :             char *pszTmpVal = nullptr;
    3856           3 :             GDALExtendedDataType::CopyValue(m_abyNoData.data(), dt, &pszTmpVal,
    3857           3 :                                             poAttr->GetDataType());
    3858           3 :             if (pszTmpVal)
    3859             :             {
    3860           3 :                 const bool bSame = strcmp(pszVal, pszTmpVal) == 0;
    3861           3 :                 CPLFree(pszTmpVal);
    3862           3 :                 if (bSame)
    3863           3 :                     return m_abyNoData.data();
    3864           2 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3865             :                          "%s attribute value ('%s') is not in the range of the "
    3866             :                          "variable data type",
    3867             :                          pszAttrName, pszVal);
    3868           2 :                 m_abyNoData.clear();
    3869           2 :                 return nullptr;
    3870             :             }
    3871             :         }
    3872             :     }
    3873             : 
    3874          73 :     if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
    3875          12 :         (m_nVarType == NC_SHORT || m_nVarType == NC_USHORT ||
    3876           8 :          m_nVarType == NC_INT || m_nVarType == NC_UINT ||
    3877           6 :          m_nVarType == NC_FLOAT || m_nVarType == NC_DOUBLE))
    3878             :     {
    3879           9 :         bool bGotNoData = false;
    3880             :         double dfNoData =
    3881           9 :             NCDFGetDefaultNoDataValue(m_gid, m_varid, m_nVarType, bGotNoData);
    3882           9 :         m_abyNoData.resize(dt.GetSize());
    3883           9 :         GDALCopyWords(&dfNoData, GDT_Float64, 0, &m_abyNoData[0],
    3884             :                       dt.GetNumericDataType(), 0, 1);
    3885             :     }
    3886          55 :     else if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
    3887           3 :              m_nVarType == NC_INT64)
    3888             :     {
    3889           1 :         bool bGotNoData = false;
    3890             :         const auto nNoData =
    3891           1 :             NCDFGetDefaultNoDataValueAsInt64(m_gid, m_varid, bGotNoData);
    3892           1 :         m_abyNoData.resize(dt.GetSize());
    3893           1 :         memcpy(&m_abyNoData[0], &nNoData, sizeof(nNoData));
    3894             :     }
    3895          53 :     else if (m_bUseDefaultFillAsNoData && m_abyNoData.empty() &&
    3896           2 :              m_nVarType == NC_UINT64)
    3897             :     {
    3898           1 :         bool bGotNoData = false;
    3899             :         const auto nNoData =
    3900           1 :             NCDFGetDefaultNoDataValueAsUInt64(m_gid, m_varid, bGotNoData);
    3901           1 :         m_abyNoData.resize(dt.GetSize());
    3902           1 :         memcpy(&m_abyNoData[0], &nNoData, sizeof(nNoData));
    3903             :     }
    3904             : 
    3905          61 :     return m_abyNoData.empty() ? nullptr : m_abyNoData.data();
    3906             : }
    3907             : 
    3908             : /************************************************************************/
    3909             : /*                          SetRawNoDataValue()                         */
    3910             : /************************************************************************/
    3911             : 
    3912          17 : bool netCDFVariable::SetRawNoDataValue(const void *pNoData)
    3913             : {
    3914          17 :     GetDataType();
    3915          17 :     if (m_nVarType == NC_STRING)
    3916           0 :         return false;
    3917             : 
    3918          17 :     m_bGetRawNoDataValueHasRun = false;
    3919          34 :     CPLMutexHolderD(&hNCMutex);
    3920          17 :     m_poShared->SetDefineMode(true);
    3921             :     int ret;
    3922          17 :     if (pNoData == nullptr)
    3923             :     {
    3924           3 :         m_abyNoData.clear();
    3925           3 :         nc_type atttype = NC_NAT;
    3926           3 :         size_t attlen = 0;
    3927           3 :         if (nc_inq_att(m_gid, m_varid, NCDF_FillValue, &atttype, &attlen) ==
    3928             :             NC_NOERR)
    3929           2 :             ret = nc_del_att(m_gid, m_varid, NCDF_FillValue);
    3930             :         else
    3931           1 :             ret = NC_NOERR;
    3932           3 :         if (nc_inq_att(m_gid, m_varid, "missing_value", &atttype, &attlen) ==
    3933             :             NC_NOERR)
    3934             :         {
    3935           0 :             int ret2 = nc_del_att(m_gid, m_varid, "missing_value");
    3936           0 :             if (ret2 != NC_NOERR)
    3937           0 :                 ret = ret2;
    3938             :         }
    3939             :     }
    3940             :     else
    3941             :     {
    3942          14 :         const auto nSize = GetDataType().GetSize();
    3943          14 :         m_abyNoData.resize(nSize);
    3944          14 :         memcpy(&m_abyNoData[0], pNoData, nSize);
    3945             : 
    3946          14 :         std::vector<GByte> abyTmp(nSize);
    3947          14 :         memcpy(&abyTmp[0], pNoData, nSize);
    3948          14 :         ConvertGDALToNC(&abyTmp[0]);
    3949             : 
    3950          14 :         if (!m_bHasWrittenData)
    3951             :         {
    3952          12 :             ret = nc_def_var_fill(m_gid, m_varid, NC_FILL, &abyTmp[0]);
    3953          12 :             NCDF_ERR(ret);
    3954             :         }
    3955             : 
    3956          14 :         nc_type atttype = NC_NAT;
    3957          14 :         size_t attlen = 0;
    3958          14 :         if (nc_inq_att(m_gid, m_varid, "missing_value", &atttype, &attlen) ==
    3959             :             NC_NOERR)
    3960             :         {
    3961           2 :             if (nc_inq_att(m_gid, m_varid, NCDF_FillValue, &atttype, &attlen) ==
    3962             :                 NC_NOERR)
    3963             :             {
    3964           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    3965             :                          "Cannot change nodata when missing_value and "
    3966             :                          "_FillValue both exist");
    3967           1 :                 return false;
    3968             :             }
    3969           1 :             ret = nc_put_att(m_gid, m_varid, "missing_value", m_nVarType, 1,
    3970           1 :                              &abyTmp[0]);
    3971             :         }
    3972             :         else
    3973             :         {
    3974          12 :             ret = nc_put_att(m_gid, m_varid, NCDF_FillValue, m_nVarType, 1,
    3975          12 :                              &abyTmp[0]);
    3976             :         }
    3977             :     }
    3978          16 :     NCDF_ERR(ret);
    3979          16 :     if (ret == NC_NOERR)
    3980          16 :         m_bGetRawNoDataValueHasRun = true;
    3981          16 :     return ret == NC_NOERR;
    3982             : }
    3983             : 
    3984             : /************************************************************************/
    3985             : /*                               SetScale()                             */
    3986             : /************************************************************************/
    3987             : 
    3988           3 : bool netCDFVariable::SetScale(double dfScale, GDALDataType eStorageType)
    3989             : {
    3990           9 :     auto poAttr = GetAttribute(CF_SCALE_FACTOR);
    3991           3 :     if (!poAttr)
    3992             :     {
    3993           4 :         poAttr = CreateAttribute(
    3994             :             CF_SCALE_FACTOR, {},
    3995           4 :             GDALExtendedDataType::Create(
    3996             :                 eStorageType == GDT_Unknown ? GDT_Float64 : eStorageType),
    3997           2 :             nullptr);
    3998             :     }
    3999           3 :     if (!poAttr)
    4000           0 :         return false;
    4001           3 :     return poAttr->Write(dfScale);
    4002             : }
    4003             : 
    4004             : /************************************************************************/
    4005             : /*                              SetOffset()                             */
    4006             : /************************************************************************/
    4007             : 
    4008           3 : bool netCDFVariable::SetOffset(double dfOffset, GDALDataType eStorageType)
    4009             : {
    4010           9 :     auto poAttr = GetAttribute(CF_ADD_OFFSET);
    4011           3 :     if (!poAttr)
    4012             :     {
    4013           4 :         poAttr = CreateAttribute(
    4014             :             CF_ADD_OFFSET, {},
    4015           4 :             GDALExtendedDataType::Create(
    4016             :                 eStorageType == GDT_Unknown ? GDT_Float64 : eStorageType),
    4017           2 :             nullptr);
    4018             :     }
    4019           3 :     if (!poAttr)
    4020           0 :         return false;
    4021           3 :     return poAttr->Write(dfOffset);
    4022             : }
    4023             : 
    4024             : /************************************************************************/
    4025             : /*                               GetScale()                             */
    4026             : /************************************************************************/
    4027             : 
    4028          72 : double netCDFVariable::GetScale(bool *pbHasScale,
    4029             :                                 GDALDataType *peStorageType) const
    4030             : {
    4031         216 :     auto poAttr = GetAttribute(CF_SCALE_FACTOR);
    4032          72 :     if (!poAttr || poAttr->GetDataType().GetClass() != GEDTC_NUMERIC)
    4033             :     {
    4034          67 :         if (pbHasScale)
    4035          66 :             *pbHasScale = false;
    4036          67 :         return 1.0;
    4037             :     }
    4038           5 :     if (pbHasScale)
    4039           5 :         *pbHasScale = true;
    4040           5 :     if (peStorageType)
    4041           2 :         *peStorageType = poAttr->GetDataType().GetNumericDataType();
    4042           5 :     return poAttr->ReadAsDouble();
    4043             : }
    4044             : 
    4045             : /************************************************************************/
    4046             : /*                               GetOffset()                            */
    4047             : /************************************************************************/
    4048             : 
    4049          72 : double netCDFVariable::GetOffset(bool *pbHasOffset,
    4050             :                                  GDALDataType *peStorageType) const
    4051             : {
    4052         216 :     auto poAttr = GetAttribute(CF_ADD_OFFSET);
    4053          72 :     if (!poAttr || poAttr->GetDataType().GetClass() != GEDTC_NUMERIC)
    4054             :     {
    4055          67 :         if (pbHasOffset)
    4056          66 :             *pbHasOffset = false;
    4057          67 :         return 0.0;
    4058             :     }
    4059           5 :     if (pbHasOffset)
    4060           5 :         *pbHasOffset = true;
    4061           5 :     if (peStorageType)
    4062           2 :         *peStorageType = poAttr->GetDataType().GetNumericDataType();
    4063           5 :     return poAttr->ReadAsDouble();
    4064             : }
    4065             : 
    4066             : /************************************************************************/
    4067             : /*                           GetBlockSize()                             */
    4068             : /************************************************************************/
    4069             : 
    4070         117 : std::vector<GUInt64> netCDFVariable::GetBlockSize() const
    4071             : {
    4072         117 :     const auto nDimCount = GetDimensionCount();
    4073         117 :     std::vector<GUInt64> res(nDimCount);
    4074         117 :     if (res.empty())
    4075           0 :         return res;
    4076         117 :     int nStorageType = 0;
    4077             :     // We add 1 to the dimension count, for 2D char variables that we
    4078             :     // expose as a 1D variable.
    4079         234 :     std::vector<size_t> anTemp(1 + nDimCount);
    4080         234 :     CPLMutexHolderD(&hNCMutex);
    4081         117 :     nc_inq_var_chunking(m_gid, m_varid, &nStorageType, &anTemp[0]);
    4082         117 :     if (nStorageType == NC_CHUNKED)
    4083             :     {
    4084          36 :         for (size_t i = 0; i < res.size(); ++i)
    4085          23 :             res[i] = anTemp[i];
    4086             :     }
    4087         117 :     return res;
    4088             : }
    4089             : 
    4090             : /************************************************************************/
    4091             : /*                         GetAttribute()                               */
    4092             : /************************************************************************/
    4093             : 
    4094             : std::shared_ptr<GDALAttribute>
    4095        3156 : netCDFVariable::GetAttribute(const std::string &osName) const
    4096             : {
    4097        6312 :     CPLMutexHolderD(&hNCMutex);
    4098        3156 :     int nAttId = -1;
    4099        3156 :     if (nc_inq_attid(m_gid, m_varid, osName.c_str(), &nAttId) != NC_NOERR)
    4100        2526 :         return nullptr;
    4101        1260 :     return netCDFAttribute::Create(
    4102        1890 :         m_poShared, std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
    4103        1260 :         m_gid, m_varid, osName);
    4104             : }
    4105             : 
    4106             : /************************************************************************/
    4107             : /*                         GetAttributes()                              */
    4108             : /************************************************************************/
    4109             : 
    4110             : std::vector<std::shared_ptr<GDALAttribute>>
    4111         101 : netCDFVariable::GetAttributes(CSLConstList papszOptions) const
    4112             : {
    4113         202 :     CPLMutexHolderD(&hNCMutex);
    4114         101 :     std::vector<std::shared_ptr<GDALAttribute>> res;
    4115         101 :     int nbAttr = 0;
    4116         101 :     NCDF_ERR(nc_inq_varnatts(m_gid, m_varid, &nbAttr));
    4117         101 :     res.reserve(nbAttr);
    4118             :     const bool bShowAll =
    4119         101 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SHOW_ALL", "NO"));
    4120         430 :     for (int i = 0; i < nbAttr; i++)
    4121             :     {
    4122             :         char szAttrName[NC_MAX_NAME + 1];
    4123         329 :         szAttrName[0] = 0;
    4124         329 :         NCDF_ERR(nc_inq_attname(m_gid, m_varid, i, szAttrName));
    4125         329 :         if (bShowAll || (!EQUAL(szAttrName, NCDF_FillValue) &&
    4126         287 :                          !EQUAL(szAttrName, "missing_value") &&
    4127         287 :                          !EQUAL(szAttrName, CF_UNITS) &&
    4128         242 :                          !EQUAL(szAttrName, CF_SCALE_FACTOR) &&
    4129         242 :                          !EQUAL(szAttrName, CF_ADD_OFFSET) &&
    4130         242 :                          !EQUAL(szAttrName, CF_GRD_MAPPING) &&
    4131         209 :                          !(EQUAL(szAttrName, "_Unsigned") &&
    4132          31 :                            (m_nVarType == NC_BYTE || m_nVarType == NC_SHORT))))
    4133             :         {
    4134         752 :             res.emplace_back(netCDFAttribute::Create(
    4135         188 :                 m_poShared,
    4136         376 :                 std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
    4137         376 :                 m_gid, m_varid, szAttrName));
    4138             :         }
    4139             :     }
    4140         202 :     return res;
    4141             : }
    4142             : 
    4143             : /************************************************************************/
    4144             : /*                          CreateAttribute()                           */
    4145             : /************************************************************************/
    4146             : 
    4147          88 : std::shared_ptr<GDALAttribute> netCDFVariable::CreateAttribute(
    4148             :     const std::string &osName, const std::vector<GUInt64> &anDimensions,
    4149             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
    4150             : {
    4151         176 :     return netCDFAttribute::Create(
    4152         264 :         m_poShared, std::dynamic_pointer_cast<netCDFVariable>(m_pSelf.lock()),
    4153         176 :         m_gid, m_varid, osName, anDimensions, oDataType, papszOptions);
    4154             : }
    4155             : 
    4156             : /************************************************************************/
    4157             : /*                         DeleteAttribute()                            */
    4158             : /************************************************************************/
    4159             : 
    4160           2 : bool netCDFVariable::DeleteAttribute(const std::string &osName,
    4161             :                                      CSLConstList /*papszOptions*/)
    4162             : {
    4163           4 :     CPLMutexHolderD(&hNCMutex);
    4164           2 :     m_poShared->SetDefineMode(true);
    4165             : 
    4166           2 :     int ret = nc_del_att(m_gid, m_varid, osName.c_str());
    4167           2 :     NCDF_ERR(ret);
    4168           2 :     if (ret != NC_NOERR)
    4169           1 :         return false;
    4170             : 
    4171           1 :     auto it = m_oMapAttributes.find(osName);
    4172           1 :     if (it != m_oMapAttributes.end())
    4173             :     {
    4174           1 :         it->second->Deleted();
    4175           1 :         m_oMapAttributes.erase(it);
    4176             :     }
    4177             : 
    4178           1 :     return true;
    4179             : }
    4180             : 
    4181             : /************************************************************************/
    4182             : /*                      GetCoordinateVariables()                        */
    4183             : /************************************************************************/
    4184             : 
    4185             : std::vector<std::shared_ptr<GDALMDArray>>
    4186          12 : netCDFVariable::GetCoordinateVariables() const
    4187             : {
    4188          24 :     std::vector<std::shared_ptr<GDALMDArray>> ret;
    4189             : 
    4190          36 :     const auto poCoordinates = GetAttribute("coordinates");
    4191           3 :     if (poCoordinates &&
    4192          15 :         poCoordinates->GetDataType().GetClass() == GEDTC_STRING &&
    4193           3 :         poCoordinates->GetDimensionCount() == 0)
    4194             :     {
    4195           3 :         const char *pszCoordinates = poCoordinates->ReadAsString();
    4196           3 :         if (pszCoordinates)
    4197             :         {
    4198             :             const CPLStringList aosNames(
    4199           6 :                 NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    4200           6 :             CPLMutexHolderD(&hNCMutex);
    4201           9 :             for (int i = 0; i < aosNames.size(); i++)
    4202             :             {
    4203           6 :                 int nVarId = 0;
    4204           6 :                 if (nc_inq_varid(m_gid, aosNames[i], &nVarId) == NC_NOERR)
    4205             :                 {
    4206           6 :                     ret.emplace_back(netCDFVariable::Create(
    4207          12 :                         m_poShared, m_poParent.lock(), m_gid, nVarId,
    4208          12 :                         std::vector<std::shared_ptr<GDALDimension>>(), nullptr,
    4209           6 :                         false));
    4210             :                 }
    4211             :                 else
    4212             :                 {
    4213           0 :                     CPLError(
    4214             :                         CE_Warning, CPLE_AppDefined,
    4215             :                         "Cannot find variable corresponding to coordinate %s",
    4216             :                         aosNames[i]);
    4217             :                 }
    4218             :             }
    4219             :         }
    4220             :     }
    4221             : 
    4222             :     // Special case for NASA EMIT datasets
    4223          24 :     auto apoDims = GetDimensions();
    4224          21 :     if ((apoDims.size() == 3 && apoDims[0]->GetName() == "downtrack" &&
    4225           6 :          apoDims[1]->GetName() == "crosstrack" &&
    4226          33 :          apoDims[2]->GetName() == "bands") ||
    4227          18 :         (apoDims.size() == 2 && apoDims[0]->GetName() == "downtrack" &&
    4228           3 :          apoDims[1]->GetName() == "crosstrack"))
    4229             :     {
    4230           6 :         auto poRootGroup = netCDFGroup::Create(m_poShared, nullptr, m_gid);
    4231           6 :         if (poRootGroup)
    4232             :         {
    4233          12 :             auto poLocationGroup = poRootGroup->OpenGroup("location");
    4234           6 :             if (poLocationGroup)
    4235             :             {
    4236          12 :                 auto poLon = poLocationGroup->OpenMDArray("lon");
    4237          12 :                 auto poLat = poLocationGroup->OpenMDArray("lat");
    4238           6 :                 if (poLon && poLat)
    4239             :                 {
    4240          18 :                     return {std::move(poLon), std::move(poLat)};
    4241             :                 }
    4242             :             }
    4243             :         }
    4244             :     }
    4245             : 
    4246           6 :     return ret;
    4247             : }
    4248             : 
    4249             : /************************************************************************/
    4250             : /*                            Resize()                                  */
    4251             : /************************************************************************/
    4252             : 
    4253           9 : bool netCDFVariable::Resize(const std::vector<GUInt64> &anNewDimSizes,
    4254             :                             CSLConstList /* papszOptions */)
    4255             : {
    4256           9 :     if (!IsWritable())
    4257             :     {
    4258           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4259             :                  "Resize() not supported on read-only file");
    4260           1 :         return false;
    4261             :     }
    4262             : 
    4263           8 :     const auto nDimCount = GetDimensionCount();
    4264           8 :     if (anNewDimSizes.size() != nDimCount)
    4265             :     {
    4266           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
    4267             :                  "Not expected number of values in anNewDimSizes.");
    4268           0 :         return false;
    4269             :     }
    4270             : 
    4271           8 :     auto &dims = GetDimensions();
    4272          16 :     std::vector<size_t> anGrownDimIdx;
    4273          16 :     std::map<GDALDimension *, GUInt64> oMapDimToSize;
    4274          18 :     for (size_t i = 0; i < nDimCount; ++i)
    4275             :     {
    4276          14 :         auto oIter = oMapDimToSize.find(dims[i].get());
    4277          14 :         if (oIter != oMapDimToSize.end() && oIter->second != anNewDimSizes[i])
    4278             :         {
    4279           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    4280             :                      "Cannot resize a dimension referenced several times "
    4281             :                      "to different sizes");
    4282           4 :             return false;
    4283             :         }
    4284          12 :         if (anNewDimSizes[i] != dims[i]->GetSize())
    4285             :         {
    4286           9 :             if (anNewDimSizes[i] < dims[i]->GetSize())
    4287             :             {
    4288           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4289             :                          "Resize() does not support shrinking the array.");
    4290           2 :                 return false;
    4291             :             }
    4292             : 
    4293           7 :             oMapDimToSize[dims[i].get()] = anNewDimSizes[i];
    4294           7 :             anGrownDimIdx.push_back(i);
    4295             :         }
    4296             :         else
    4297             :         {
    4298           3 :             oMapDimToSize[dims[i].get()] = dims[i]->GetSize();
    4299             :         }
    4300             :     }
    4301             : 
    4302           4 :     if (!anGrownDimIdx.empty())
    4303             :     {
    4304           4 :         CPLMutexHolderD(&hNCMutex);
    4305             :         // Query which netCDF dimensions have unlimited size
    4306           4 :         int nUnlimitedDimIds = 0;
    4307           4 :         nc_inq_unlimdims(m_gid, &nUnlimitedDimIds, nullptr);
    4308           4 :         std::vector<int> anUnlimitedDimIds(nUnlimitedDimIds);
    4309           4 :         nc_inq_unlimdims(m_gid, &nUnlimitedDimIds, anUnlimitedDimIds.data());
    4310           4 :         std::set<int> oSetUnlimitedDimId;
    4311           8 :         for (int idx : anUnlimitedDimIds)
    4312           4 :             oSetUnlimitedDimId.insert(idx);
    4313             : 
    4314             :         // Check that dimensions that need to grow are of unlimited size
    4315           8 :         for (size_t dimIdx : anGrownDimIdx)
    4316             :         {
    4317             :             auto netCDFDim =
    4318           5 :                 std::dynamic_pointer_cast<netCDFDimension>(dims[dimIdx]);
    4319           5 :             if (!netCDFDim)
    4320             :             {
    4321           0 :                 CPLAssert(false);
    4322             :             }
    4323           5 :             else if (oSetUnlimitedDimId.find(netCDFDim->GetId()) ==
    4324          10 :                      oSetUnlimitedDimId.end())
    4325             :             {
    4326           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4327             :                          "Resize() cannot grow dimension %d (%s) "
    4328             :                          "as it is not created as UNLIMITED.",
    4329             :                          static_cast<int>(dimIdx),
    4330           1 :                          netCDFDim->GetName().c_str());
    4331           1 :                 return false;
    4332             :             }
    4333             :         }
    4334           9 :         for (size_t i = 0; i < nDimCount; ++i)
    4335             :         {
    4336           6 :             if (anNewDimSizes[i] > dims[i]->GetSize())
    4337             :             {
    4338             :                 auto netCDFDim =
    4339           6 :                     std::dynamic_pointer_cast<netCDFDimension>(dims[i]);
    4340           3 :                 if (!netCDFDim)
    4341             :                 {
    4342           0 :                     CPLAssert(false);
    4343             :                 }
    4344             :                 else
    4345             :                 {
    4346           3 :                     netCDFDim->SetSize(anNewDimSizes[i]);
    4347             :                 }
    4348             :             }
    4349             :         }
    4350             :     }
    4351           3 :     return true;
    4352             : }
    4353             : 
    4354             : /************************************************************************/
    4355             : /*                              Rename()                                */
    4356             : /************************************************************************/
    4357             : 
    4358           4 : bool netCDFVariable::Rename(const std::string &osNewName)
    4359             : {
    4360           4 :     if (m_poShared->IsReadOnly())
    4361             :     {
    4362           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4363             :                  "Rename() not supported on read-only file");
    4364           1 :         return false;
    4365             :     }
    4366           3 :     if (osNewName.empty())
    4367             :     {
    4368           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
    4369           1 :         return false;
    4370             :     }
    4371             : 
    4372           4 :     CPLMutexHolderD(&hNCMutex);
    4373           2 :     m_poShared->SetDefineMode(true);
    4374             : 
    4375           2 :     int ret = nc_rename_var(m_gid, m_varid, osNewName.c_str());
    4376           2 :     NCDF_ERR(ret);
    4377           2 :     if (ret != NC_NOERR)
    4378           1 :         return false;
    4379             : 
    4380           1 :     BaseRename(osNewName);
    4381             : 
    4382           1 :     return true;
    4383             : }
    4384             : 
    4385             : /************************************************************************/
    4386             : /*                       NotifyChildrenOfRenaming()                     */
    4387             : /************************************************************************/
    4388             : 
    4389           3 : void netCDFVariable::NotifyChildrenOfRenaming()
    4390             : {
    4391           6 :     for (const auto &iter : m_oMapAttributes)
    4392           3 :         iter.second->ParentRenamed(m_osFullName);
    4393           3 : }
    4394             : 
    4395             : /************************************************************************/
    4396             : /*                    retrieveAttributeParentName()                     */
    4397             : /************************************************************************/
    4398             : 
    4399        2218 : static CPLString retrieveAttributeParentName(int gid, int varid)
    4400             : {
    4401        4436 :     auto groupName(NCDFGetGroupFullName(gid));
    4402        2218 :     if (varid == NC_GLOBAL)
    4403             :     {
    4404         408 :         if (groupName == "/")
    4405         382 :             return "/_GLOBAL_";
    4406          52 :         return groupName + "/_GLOBAL_";
    4407             :     }
    4408             : 
    4409        3620 :     return groupName + "/" + netCDFVariable::retrieveName(gid, varid);
    4410             : }
    4411             : 
    4412             : /************************************************************************/
    4413             : /*                          netCDFAttribute()                           */
    4414             : /************************************************************************/
    4415             : 
    4416         948 : netCDFAttribute::netCDFAttribute(
    4417             :     const std::shared_ptr<netCDFSharedResources> &poShared, int gid, int varid,
    4418         948 :     const std::string &name)
    4419         948 :     : GDALAbstractMDArray(retrieveAttributeParentName(gid, varid), name),
    4420         948 :       GDALAttribute(retrieveAttributeParentName(gid, varid), name),
    4421        2844 :       m_poShared(poShared), m_gid(gid), m_varid(varid)
    4422             : {
    4423        1896 :     CPLMutexHolderD(&hNCMutex);
    4424         948 :     size_t nLen = 0;
    4425         948 :     NCDF_ERR(nc_inq_atttype(m_gid, m_varid, GetName().c_str(), &m_nAttType));
    4426         948 :     NCDF_ERR(nc_inq_attlen(m_gid, m_varid, GetName().c_str(), &nLen));
    4427         948 :     if (m_nAttType == NC_CHAR)
    4428             :     {
    4429         749 :         m_nTextLength = nLen;
    4430             :     }
    4431         199 :     else if (nLen > 1)
    4432             :     {
    4433         176 :         m_dims.emplace_back(std::make_shared<GDALDimension>(
    4434         264 :             std::string(), "length", std::string(), std::string(), nLen));
    4435             :     }
    4436         948 : }
    4437             : 
    4438             : /************************************************************************/
    4439             : /*                          netCDFAttribute()                           */
    4440             : /************************************************************************/
    4441             : 
    4442         161 : netCDFAttribute::netCDFAttribute(
    4443             :     const std::shared_ptr<netCDFSharedResources> &poShared, int gid, int varid,
    4444             :     const std::string &osName, const std::vector<GUInt64> &anDimensions,
    4445         161 :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
    4446         161 :     : GDALAbstractMDArray(retrieveAttributeParentName(gid, varid), osName),
    4447         161 :       GDALAttribute(retrieveAttributeParentName(gid, varid), osName),
    4448         483 :       m_poShared(poShared), m_gid(gid), m_varid(varid)
    4449             : {
    4450         322 :     CPLMutexHolderD(&hNCMutex);
    4451         161 :     m_bPerfectDataTypeMatch = true;
    4452         161 :     m_nAttType = CreateOrGetType(gid, oDataType);
    4453         161 :     m_dt.reset(new GDALExtendedDataType(oDataType));
    4454         161 :     if (!anDimensions.empty())
    4455             :     {
    4456          20 :         m_dims.emplace_back(std::make_shared<GDALDimension>(
    4457          20 :             std::string(), "length", std::string(), std::string(),
    4458          20 :             anDimensions[0]));
    4459             :     }
    4460             : 
    4461         161 :     const char *pszType = CSLFetchNameValueDef(papszOptions, "NC_TYPE", "");
    4462         293 :     if (oDataType.GetClass() == GEDTC_STRING && anDimensions.empty() &&
    4463         132 :         (EQUAL(pszType, "") || EQUAL(pszType, "NC_CHAR")))
    4464             :     {
    4465         131 :         m_nAttType = NC_CHAR;
    4466             :     }
    4467          40 :     else if (oDataType.GetNumericDataType() == GDT_Byte &&
    4468          10 :              EQUAL(CSLFetchNameValueDef(papszOptions, "NC_TYPE", ""),
    4469             :                    "NC_BYTE"))
    4470             :     {
    4471           1 :         m_nAttType = NC_BYTE;
    4472             :     }
    4473          31 :     else if (oDataType.GetNumericDataType() == GDT_Int16 &&
    4474           2 :              EQUAL(CSLFetchNameValueDef(papszOptions, "NC_TYPE", ""),
    4475             :                    "NC_BYTE"))
    4476             :     {
    4477           2 :         m_bPerfectDataTypeMatch = false;
    4478           2 :         m_nAttType = NC_BYTE;
    4479             :     }
    4480          27 :     else if (oDataType.GetNumericDataType() == GDT_Float64)
    4481             :     {
    4482           9 :         if (EQUAL(pszType, "NC_INT64"))
    4483             :         {
    4484           2 :             m_bPerfectDataTypeMatch = false;
    4485           2 :             m_nAttType = NC_INT64;
    4486             :         }
    4487           7 :         else if (EQUAL(pszType, "NC_UINT64"))
    4488             :         {
    4489           1 :             m_bPerfectDataTypeMatch = false;
    4490           1 :             m_nAttType = NC_UINT64;
    4491             :         }
    4492             :     }
    4493         161 : }
    4494             : 
    4495             : /************************************************************************/
    4496             : /*                         ~netCDFAttribute()                           */
    4497             : /************************************************************************/
    4498             : 
    4499        2218 : netCDFAttribute::~netCDFAttribute()
    4500             : {
    4501        1109 :     if (m_bValid)
    4502             :     {
    4503        1748 :         if (auto poParent = m_poParent.lock())
    4504         641 :             poParent->UnRegisterAttribute(this);
    4505             :     }
    4506        2218 : }
    4507             : 
    4508             : /************************************************************************/
    4509             : /*                              Create()                                */
    4510             : /************************************************************************/
    4511             : 
    4512             : std::shared_ptr<netCDFAttribute>
    4513         948 : netCDFAttribute::Create(const std::shared_ptr<netCDFSharedResources> &poShared,
    4514             :                         const std::shared_ptr<netCDFAttributeHolder> &poParent,
    4515             :                         int gid, int varid, const std::string &name)
    4516             : {
    4517             :     auto attr(std::shared_ptr<netCDFAttribute>(
    4518         948 :         new netCDFAttribute(poShared, gid, varid, name)));
    4519         948 :     attr->SetSelf(attr);
    4520         948 :     attr->m_poParent = poParent;
    4521         948 :     if (poParent)
    4522         544 :         poParent->RegisterAttribute(attr.get());
    4523         948 :     return attr;
    4524             : }
    4525             : 
    4526         164 : std::shared_ptr<netCDFAttribute> netCDFAttribute::Create(
    4527             :     const std::shared_ptr<netCDFSharedResources> &poShared,
    4528             :     const std::shared_ptr<netCDFAttributeHolder> &poParent, int gid, int varid,
    4529             :     const std::string &osName, const std::vector<GUInt64> &anDimensions,
    4530             :     const GDALExtendedDataType &oDataType, CSLConstList papszOptions)
    4531             : {
    4532         164 :     if (poShared->IsReadOnly())
    4533             :     {
    4534           2 :         CPLError(CE_Failure, CPLE_AppDefined,
    4535             :                  "CreateAttribute() not supported on read-only file");
    4536           2 :         return nullptr;
    4537             :     }
    4538         162 :     if (anDimensions.size() > 1)
    4539             :     {
    4540           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    4541             :                  "Only 0 or 1-dimensional attribute are supported");
    4542           1 :         return nullptr;
    4543             :     }
    4544             : 
    4545         161 :     const char *apszOptions[2] = {nullptr, nullptr};
    4546         176 :     if (!poShared->IsNC4() && oDataType.GetClass() == GEDTC_NUMERIC &&
    4547         176 :         oDataType.GetNumericDataType() == GDT_Byte && !papszOptions)
    4548             :     {
    4549             :         // GDT_Byte would map to a NC_UBYTE datatype, which is not available in
    4550             :         // NC3 datasets
    4551           1 :         apszOptions[0] = "NC_TYPE=NC_BYTE";
    4552           1 :         papszOptions = apszOptions;
    4553             :     }
    4554             : 
    4555             :     auto attr(std::shared_ptr<netCDFAttribute>(new netCDFAttribute(
    4556         322 :         poShared, gid, varid, osName, anDimensions, oDataType, papszOptions)));
    4557         161 :     if (attr->m_nAttType == NC_NAT)
    4558           0 :         return nullptr;
    4559         161 :     attr->SetSelf(attr);
    4560         161 :     attr->m_poParent = poParent;
    4561         161 :     if (poParent)
    4562         161 :         poParent->RegisterAttribute(attr.get());
    4563         161 :     return attr;
    4564             : }
    4565             : 
    4566             : /************************************************************************/
    4567             : /*                             GetDataType()                            */
    4568             : /************************************************************************/
    4569             : 
    4570        3595 : const GDALExtendedDataType &netCDFAttribute::GetDataType() const
    4571             : {
    4572        3595 :     if (m_dt)
    4573        2790 :         return *m_dt;
    4574        1610 :     CPLMutexHolderD(&hNCMutex);
    4575             : 
    4576         805 :     if (m_nAttType == NC_CHAR)
    4577             :     {
    4578         678 :         m_dt.reset(
    4579         678 :             new GDALExtendedDataType(GDALExtendedDataType::CreateString()));
    4580             :     }
    4581             :     else
    4582             :     {
    4583         127 :         m_dt.reset(new GDALExtendedDataType(
    4584         127 :             GDALExtendedDataType::Create(GDT_Unknown)));
    4585         127 :         BuildDataType(m_gid, m_varid, m_nAttType, m_dt,
    4586         127 :                       m_bPerfectDataTypeMatch);
    4587             :     }
    4588             : 
    4589         805 :     return *m_dt;
    4590             : }
    4591             : 
    4592             : /************************************************************************/
    4593             : /*                                   IRead()                            */
    4594             : /************************************************************************/
    4595             : 
    4596         823 : bool netCDFAttribute::IRead(const GUInt64 *arrayStartIdx, const size_t *count,
    4597             :                             const GInt64 *arrayStep,
    4598             :                             const GPtrDiff_t *bufferStride,
    4599             :                             const GDALExtendedDataType &bufferDataType,
    4600             :                             void *pDstBuffer) const
    4601             : {
    4602         823 :     if (!CheckValidAndErrorOutIfNot())
    4603           0 :         return false;
    4604        1646 :     CPLMutexHolderD(&hNCMutex);
    4605             : 
    4606         823 :     if (m_nAttType == NC_STRING)
    4607             :     {
    4608           8 :         CPLAssert(GetDataType().GetClass() == GEDTC_STRING);
    4609             :         std::vector<char *> apszStrings(
    4610          16 :             static_cast<size_t>(GetTotalElementsCount()));
    4611           8 :         int ret = nc_get_att_string(m_gid, m_varid, GetName().c_str(),
    4612           8 :                                     &apszStrings[0]);
    4613           8 :         NCDF_ERR(ret);
    4614           8 :         if (ret != NC_NOERR)
    4615           0 :             return false;
    4616           8 :         if (m_dims.empty())
    4617             :         {
    4618           3 :             const char *pszStr = apszStrings[0];
    4619           3 :             GDALExtendedDataType::CopyValue(&pszStr, GetDataType(), pDstBuffer,
    4620             :                                             bufferDataType);
    4621             :         }
    4622             :         else
    4623             :         {
    4624           5 :             GByte *pabyDstBuffer = static_cast<GByte *>(pDstBuffer);
    4625          14 :             for (size_t i = 0; i < count[0]; i++)
    4626             :             {
    4627           9 :                 auto srcIdx =
    4628           9 :                     static_cast<size_t>(arrayStartIdx[0] + arrayStep[0] * i);
    4629           9 :                 const char *pszStr = apszStrings[srcIdx];
    4630           9 :                 GDALExtendedDataType::CopyValue(&pszStr, GetDataType(),
    4631             :                                                 pabyDstBuffer, bufferDataType);
    4632           9 :                 pabyDstBuffer += sizeof(char *) * bufferStride[0];
    4633             :             }
    4634             :         }
    4635           8 :         nc_free_string(apszStrings.size(), &apszStrings[0]);
    4636           8 :         return true;
    4637             :     }
    4638             : 
    4639         815 :     if (m_nAttType == NC_CHAR)
    4640             :     {
    4641         684 :         CPLAssert(GetDataType().GetClass() == GEDTC_STRING);
    4642         684 :         CPLAssert(m_dims.empty());
    4643         684 :         if (bufferDataType != GetDataType())
    4644             :         {
    4645           0 :             std::string osStr;
    4646           0 :             osStr.resize(m_nTextLength);
    4647             :             int ret =
    4648           0 :                 nc_get_att_text(m_gid, m_varid, GetName().c_str(), &osStr[0]);
    4649           0 :             NCDF_ERR(ret);
    4650           0 :             if (ret != NC_NOERR)
    4651           0 :                 return false;
    4652           0 :             const char *pszStr = osStr.c_str();
    4653           0 :             GDALExtendedDataType::CopyValue(&pszStr, GetDataType(), pDstBuffer,
    4654             :                                             bufferDataType);
    4655             :         }
    4656             :         else
    4657             :         {
    4658         684 :             char *pszStr = static_cast<char *>(CPLCalloc(1, m_nTextLength + 1));
    4659             :             int ret =
    4660         684 :                 nc_get_att_text(m_gid, m_varid, GetName().c_str(), pszStr);
    4661         684 :             NCDF_ERR(ret);
    4662         684 :             if (ret != NC_NOERR)
    4663             :             {
    4664           1 :                 CPLFree(pszStr);
    4665           1 :                 return false;
    4666             :             }
    4667         683 :             *static_cast<char **>(pDstBuffer) = pszStr;
    4668             :         }
    4669         683 :         return true;
    4670             :     }
    4671             : 
    4672         131 :     const auto &dt(GetDataType());
    4673         258 :     if (dt.GetClass() == GEDTC_NUMERIC &&
    4674         127 :         dt.GetNumericDataType() == GDT_Unknown)
    4675             :     {
    4676           0 :         return false;
    4677             :     }
    4678             : 
    4679         131 :     CPLAssert(dt.GetClass() != GEDTC_STRING);
    4680         243 :     const bool bFastPath = ((m_dims.size() == 1 && arrayStartIdx[0] == 0 &&
    4681          56 :                              count[0] == m_dims[0]->GetSize() &&
    4682         131 :                              arrayStep[0] == 1 && bufferStride[0] == 1) ||
    4683          76 :                             m_dims.empty()) &&
    4684         327 :                            m_bPerfectDataTypeMatch && bufferDataType == dt &&
    4685          65 :                            dt.GetSize() > 0;
    4686         131 :     if (bFastPath)
    4687             :     {
    4688          65 :         int ret = nc_get_att(m_gid, m_varid, GetName().c_str(), pDstBuffer);
    4689          65 :         NCDF_ERR(ret);
    4690          65 :         if (ret == NC_NOERR)
    4691             :         {
    4692          65 :             ConvertNCStringsToCPLStrings(static_cast<GByte *>(pDstBuffer), dt);
    4693             :         }
    4694          65 :         return ret == NC_NOERR;
    4695             :     }
    4696             : 
    4697             :     const auto nElementSize =
    4698          66 :         GetNCTypeSize(dt, m_bPerfectDataTypeMatch, m_nAttType);
    4699          66 :     if (nElementSize == 0)
    4700           0 :         return false;
    4701          66 :     const auto nOutputDTSize = bufferDataType.GetSize();
    4702          66 :     std::vector<GByte> abyBuffer(static_cast<size_t>(GetTotalElementsCount()) *
    4703         198 :                                  nElementSize);
    4704          66 :     int ret = nc_get_att(m_gid, m_varid, GetName().c_str(), &abyBuffer[0]);
    4705          66 :     NCDF_ERR(ret);
    4706          66 :     if (ret != NC_NOERR)
    4707           0 :         return false;
    4708             : 
    4709             :     GByte *pabySrcBuffer =
    4710          66 :         m_dims.empty()
    4711          66 :             ? abyBuffer.data()
    4712          41 :             : abyBuffer.data() +
    4713          41 :                   static_cast<size_t>(arrayStartIdx[0]) * nElementSize;
    4714          66 :     GByte *pabyDstBuffer = static_cast<GByte *>(pDstBuffer);
    4715         216 :     for (size_t i = 0; i < (m_dims.empty() ? 1 : count[0]); i++)
    4716             :     {
    4717             :         GByte abyTmpBuffer[sizeof(double)];
    4718         150 :         const GByte *pabySrcElement = pabySrcBuffer;
    4719         150 :         if (!m_bPerfectDataTypeMatch)
    4720             :         {
    4721           7 :             if (m_nAttType == NC_BYTE)
    4722             :             {
    4723           3 :                 short s =
    4724           3 :                     reinterpret_cast<const signed char *>(pabySrcBuffer)[0];
    4725           3 :                 memcpy(abyTmpBuffer, &s, sizeof(s));
    4726           3 :                 pabySrcElement = abyTmpBuffer;
    4727             :             }
    4728           4 :             else if (m_nAttType == NC_INT64)
    4729             :             {
    4730           3 :                 double v = static_cast<double>(
    4731           3 :                     reinterpret_cast<const GInt64 *>(pabySrcBuffer)[0]);
    4732           3 :                 memcpy(abyTmpBuffer, &v, sizeof(v));
    4733           3 :                 pabySrcElement = abyTmpBuffer;
    4734             :             }
    4735           1 :             else if (m_nAttType == NC_UINT64)
    4736             :             {
    4737           1 :                 double v = static_cast<double>(
    4738           1 :                     reinterpret_cast<const GUInt64 *>(pabySrcBuffer)[0]);
    4739           1 :                 memcpy(abyTmpBuffer, &v, sizeof(v));
    4740           1 :                 pabySrcElement = abyTmpBuffer;
    4741             :             }
    4742             :             else
    4743             :             {
    4744           0 :                 CPLAssert(false);
    4745             :             }
    4746             :         }
    4747         150 :         GDALExtendedDataType::CopyValue(pabySrcElement, dt, pabyDstBuffer,
    4748             :                                         bufferDataType);
    4749         150 :         FreeNCStrings(pabySrcBuffer, dt);
    4750         150 :         if (!m_dims.empty())
    4751             :         {
    4752         125 :             pabySrcBuffer +=
    4753         125 :                 static_cast<std::ptrdiff_t>(arrayStep[0] * nElementSize);
    4754         125 :             pabyDstBuffer += nOutputDTSize * bufferStride[0];
    4755             :         }
    4756             :     }
    4757             : 
    4758          66 :     return true;
    4759             : }
    4760             : 
    4761             : /************************************************************************/
    4762             : /*                                IWrite()                              */
    4763             : /************************************************************************/
    4764             : 
    4765         166 : bool netCDFAttribute::IWrite(const GUInt64 *arrayStartIdx, const size_t *count,
    4766             :                              const GInt64 *arrayStep,
    4767             :                              const GPtrDiff_t *bufferStride,
    4768             :                              const GDALExtendedDataType &bufferDataType,
    4769             :                              const void *pSrcBuffer)
    4770             : {
    4771         166 :     if (!CheckValidAndErrorOutIfNot())
    4772           0 :         return false;
    4773         332 :     CPLMutexHolderD(&hNCMutex);
    4774             : 
    4775         177 :     if (m_dims.size() == 1 &&
    4776          11 :         (arrayStartIdx[0] != 0 || count[0] != m_dims[0]->GetSize() ||
    4777          11 :          arrayStep[0] != 1))
    4778             :     {
    4779           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    4780             :                  "Only contiguous writing of attribute values supported");
    4781           0 :         return false;
    4782             :     }
    4783             : 
    4784         166 :     m_poShared->SetDefineMode(true);
    4785             : 
    4786         166 :     const auto &dt(GetDataType());
    4787         166 :     if (m_nAttType == NC_STRING)
    4788             :     {
    4789           4 :         CPLAssert(dt.GetClass() == GEDTC_STRING);
    4790           4 :         if (m_dims.empty())
    4791             :         {
    4792           2 :             char *pszStr = nullptr;
    4793             :             const char *pszStrConst;
    4794           2 :             if (bufferDataType != dt)
    4795             :             {
    4796           1 :                 GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType,
    4797             :                                                 &pszStr, dt);
    4798           1 :                 pszStrConst = pszStr;
    4799             :             }
    4800             :             else
    4801             :             {
    4802           1 :                 memcpy(&pszStrConst, pSrcBuffer, sizeof(const char *));
    4803             :             }
    4804           2 :             int ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), 1,
    4805             :                                         &pszStrConst);
    4806           2 :             CPLFree(pszStr);
    4807           2 :             NCDF_ERR(ret);
    4808           2 :             if (ret != NC_NOERR)
    4809           0 :                 return false;
    4810           2 :             return true;
    4811             :         }
    4812             : 
    4813             :         int ret;
    4814           2 :         if (bufferDataType != dt)
    4815             :         {
    4816           2 :             std::vector<char *> apszStrings(count[0]);
    4817           1 :             const auto nInputDTSize = bufferDataType.GetSize();
    4818           1 :             const GByte *pabySrcBuffer = static_cast<const GByte *>(pSrcBuffer);
    4819           3 :             for (size_t i = 0; i < count[0]; i++)
    4820             :             {
    4821           2 :                 GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
    4822           2 :                                                 &apszStrings[i], dt);
    4823           2 :                 pabySrcBuffer += nInputDTSize * bufferStride[0];
    4824             :             }
    4825           1 :             ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), count[0],
    4826           1 :                                     const_cast<const char **>(&apszStrings[0]));
    4827           3 :             for (size_t i = 0; i < count[0]; i++)
    4828             :             {
    4829           2 :                 CPLFree(apszStrings[i]);
    4830             :             }
    4831             :         }
    4832             :         else
    4833             :         {
    4834             :             const char **ppszStr;
    4835           1 :             memcpy(&ppszStr, &pSrcBuffer, sizeof(const char **));
    4836           1 :             ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), count[0],
    4837             :                                     ppszStr);
    4838             :         }
    4839           2 :         NCDF_ERR(ret);
    4840           2 :         if (ret != NC_NOERR)
    4841           0 :             return false;
    4842           2 :         return true;
    4843             :     }
    4844             : 
    4845         162 :     if (m_nAttType == NC_CHAR)
    4846             :     {
    4847         137 :         CPLAssert(dt.GetClass() == GEDTC_STRING);
    4848         137 :         CPLAssert(m_dims.empty());
    4849         137 :         char *pszStr = nullptr;
    4850             :         const char *pszStrConst;
    4851         137 :         if (bufferDataType != dt)
    4852             :         {
    4853           1 :             GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType, &pszStr,
    4854             :                                             dt);
    4855           1 :             pszStrConst = pszStr;
    4856             :         }
    4857             :         else
    4858             :         {
    4859         136 :             memcpy(&pszStrConst, pSrcBuffer, sizeof(const char *));
    4860             :         }
    4861         137 :         m_nTextLength = pszStrConst ? strlen(pszStrConst) : 0;
    4862         137 :         int ret = nc_put_att_text(m_gid, m_varid, GetName().c_str(),
    4863             :                                   m_nTextLength, pszStrConst);
    4864         137 :         CPLFree(pszStr);
    4865         137 :         NCDF_ERR(ret);
    4866         137 :         if (ret != NC_NOERR)
    4867           1 :             return false;
    4868         136 :         return true;
    4869             :     }
    4870             : 
    4871          49 :     if (dt.GetClass() == GEDTC_NUMERIC &&
    4872          24 :         dt.GetNumericDataType() == GDT_Unknown)
    4873             :     {
    4874           0 :         return false;
    4875             :     }
    4876             : 
    4877          25 :     CPLAssert(dt.GetClass() != GEDTC_STRING);
    4878             :     const bool bFastPath =
    4879          50 :         ((m_dims.size() == 1 && bufferStride[0] == 1) || m_dims.empty()) &&
    4880          50 :         m_bPerfectDataTypeMatch && bufferDataType == dt && dt.GetSize() > 0;
    4881          25 :     if (bFastPath)
    4882             :     {
    4883          11 :         int ret = nc_put_att(m_gid, m_varid, GetName().c_str(), m_nAttType,
    4884          11 :                              m_dims.empty() ? 1 : count[0], pSrcBuffer);
    4885          11 :         NCDF_ERR(ret);
    4886          11 :         return ret == NC_NOERR;
    4887             :     }
    4888             : 
    4889             :     const auto nElementSize =
    4890          14 :         GetNCTypeSize(dt, m_bPerfectDataTypeMatch, m_nAttType);
    4891          14 :     if (nElementSize == 0)
    4892           0 :         return false;
    4893          14 :     const auto nInputDTSize = bufferDataType.GetSize();
    4894          14 :     std::vector<GByte> abyBuffer(static_cast<size_t>(GetTotalElementsCount()) *
    4895          28 :                                  nElementSize);
    4896             : 
    4897          14 :     const GByte *pabySrcBuffer = static_cast<const GByte *>(pSrcBuffer);
    4898          14 :     auto pabyDstBuffer = &abyBuffer[0];
    4899          31 :     for (size_t i = 0; i < (m_dims.empty() ? 1 : count[0]); i++)
    4900             :     {
    4901          17 :         if (!m_bPerfectDataTypeMatch)
    4902             :         {
    4903           7 :             if (m_nAttType == NC_BYTE)
    4904             :             {
    4905             :                 short s;
    4906           3 :                 GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
    4907             :                                                 &s, dt);
    4908           3 :                 signed char c = static_cast<signed char>(s);
    4909           3 :                 memcpy(pabyDstBuffer, &c, sizeof(c));
    4910             :             }
    4911           4 :             else if (m_nAttType == NC_INT64)
    4912             :             {
    4913             :                 double d;
    4914           3 :                 GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
    4915             :                                                 &d, dt);
    4916           3 :                 GInt64 v = static_cast<GInt64>(d);
    4917           3 :                 memcpy(pabyDstBuffer, &v, sizeof(v));
    4918             :             }
    4919           1 :             else if (m_nAttType == NC_UINT64)
    4920             :             {
    4921             :                 double d;
    4922           1 :                 GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
    4923             :                                                 &d, dt);
    4924           1 :                 GUInt64 v = static_cast<GUInt64>(d);
    4925           1 :                 memcpy(pabyDstBuffer, &v, sizeof(v));
    4926             :             }
    4927             :             else
    4928             :             {
    4929           0 :                 CPLAssert(false);
    4930             :             }
    4931             :         }
    4932             :         else
    4933             :         {
    4934          10 :             GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType,
    4935             :                                             pabyDstBuffer, dt);
    4936             :         }
    4937             : 
    4938          17 :         if (!m_dims.empty())
    4939             :         {
    4940           7 :             pabySrcBuffer += nInputDTSize * bufferStride[0];
    4941           7 :             pabyDstBuffer += nElementSize;
    4942             :         }
    4943             :     }
    4944             : 
    4945          14 :     int ret = nc_put_att(m_gid, m_varid, GetName().c_str(), m_nAttType,
    4946          14 :                          m_dims.empty() ? 1 : count[0], &abyBuffer[0]);
    4947          14 :     NCDF_ERR(ret);
    4948          14 :     return ret == NC_NOERR;
    4949             : }
    4950             : 
    4951             : /************************************************************************/
    4952             : /*                              Rename()                                */
    4953             : /************************************************************************/
    4954             : 
    4955           8 : bool netCDFAttribute::Rename(const std::string &osNewName)
    4956             : {
    4957           8 :     if (!CheckValidAndErrorOutIfNot())
    4958           2 :         return false;
    4959           6 :     if (m_poShared->IsReadOnly())
    4960             :     {
    4961           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4962             :                  "Rename() not supported on read-only file");
    4963           1 :         return false;
    4964             :     }
    4965           5 :     if (osNewName.empty())
    4966             :     {
    4967           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Empty name not supported");
    4968           1 :         return false;
    4969             :     }
    4970           8 :     CPLMutexHolderD(&hNCMutex);
    4971           4 :     m_poShared->SetDefineMode(true);
    4972             : 
    4973             :     int ret =
    4974           4 :         nc_rename_att(m_gid, m_varid, m_osName.c_str(), osNewName.c_str());
    4975           4 :     NCDF_ERR(ret);
    4976           4 :     if (ret != NC_NOERR)
    4977           2 :         return false;
    4978             : 
    4979           2 :     BaseRename(osNewName);
    4980             : 
    4981           2 :     return true;
    4982             : }
    4983             : 
    4984             : /************************************************************************/
    4985             : /*                           OpenMultiDim()                             */
    4986             : /************************************************************************/
    4987             : 
    4988         183 : GDALDataset *netCDFDataset::OpenMultiDim(GDALOpenInfo *poOpenInfo)
    4989             : {
    4990             : 
    4991         366 :     CPLMutexHolderD(&hNCMutex);
    4992             : 
    4993         183 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    4994             :                                 // GDALDataset own mutex.
    4995         183 :     netCDFDataset *poDS = new netCDFDataset();
    4996         183 :     CPLAcquireMutex(hNCMutex, 1000.0);
    4997             : 
    4998         366 :     std::string osFilename;
    4999             : 
    5000             :     // For example to open DAP datasets
    5001         183 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    5002             :     {
    5003           0 :         osFilename = poOpenInfo->pszFilename + strlen("NETCDF:");
    5004           0 :         if (!osFilename.empty() && osFilename[0] == '"' &&
    5005           0 :             osFilename.back() == '"')
    5006             :         {
    5007           0 :             osFilename = osFilename.substr(1, osFilename.size() - 2);
    5008             :         }
    5009             :     }
    5010             :     else
    5011             :     {
    5012         183 :         osFilename = poOpenInfo->pszFilename;
    5013         183 :         poDS->eFormat =
    5014         183 :             netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
    5015             :     }
    5016             : 
    5017         183 :     poDS->SetDescription(poOpenInfo->pszFilename);
    5018         183 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    5019             : 
    5020             : #ifdef ENABLE_NCDUMP
    5021         183 :     bool bFileToDestroyAtClosing = false;
    5022         183 :     const char *pszHeader =
    5023             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    5024         183 :     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
    5025           1 :         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
    5026             :     {
    5027             :         // By default create a temporary file that will be destroyed,
    5028             :         // unless NETCDF_TMP_FILE is defined. Can be useful to see which
    5029             :         // netCDF file has been generated from a potential fuzzed input.
    5030           1 :         osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
    5031           1 :         if (osFilename.empty())
    5032             :         {
    5033           1 :             bFileToDestroyAtClosing = true;
    5034           1 :             osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
    5035             :         }
    5036           1 :         if (!netCDFDatasetCreateTempFile(NCDF_FORMAT_NC4, osFilename.c_str(),
    5037             :                                          poOpenInfo->fpL))
    5038             :         {
    5039           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    5040             :                                         // deadlock with GDALDataset own mutex.
    5041           0 :             delete poDS;
    5042           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    5043           0 :             return nullptr;
    5044             :         }
    5045           1 :         poDS->eFormat = NCDF_FORMAT_NC4;
    5046             :     }
    5047             : #endif
    5048             : 
    5049             :     // Try opening the dataset.
    5050             : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
    5051             :     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)", osFilename.c_str());
    5052             : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
    5053             :     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", osFilename.c_str());
    5054             : #endif
    5055         183 :     int cdfid = -1;
    5056         183 :     const int nMode =
    5057         183 :         (poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0 ? NC_WRITE : NC_NOWRITE;
    5058         366 :     CPLString osFilenameForNCOpen(osFilename);
    5059             : #ifdef _WIN32
    5060             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    5061             :     {
    5062             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    5063             :         osFilenameForNCOpen = pszTemp;
    5064             :         CPLFree(pszTemp);
    5065             :     }
    5066             : #endif
    5067         183 :     int status2 = -1;
    5068             : 
    5069         366 :     auto poSharedResources(std::make_shared<netCDFSharedResources>(osFilename));
    5070             : #ifdef ENABLE_NCDUMP
    5071         183 :     poSharedResources->m_bFileToDestroyAtClosing = bFileToDestroyAtClosing;
    5072             : #endif
    5073             : 
    5074         187 :     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
    5075           4 :         poOpenInfo->eAccess == GA_ReadOnly)
    5076             :     {
    5077           4 :         vsi_l_offset nLength = 0;
    5078           4 :         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
    5079           4 :         if (poDS->fpVSIMEM)
    5080             :         {
    5081             :             // We assume that the file will not be modified. If it is, then
    5082             :             // pabyBuffer might become invalid.
    5083             :             GByte *pabyBuffer =
    5084           4 :                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
    5085           4 :             if (pabyBuffer)
    5086             :             {
    5087           4 :                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
    5088             :                                       nMode, static_cast<size_t>(nLength),
    5089             :                                       pabyBuffer, &cdfid);
    5090             :             }
    5091             :         }
    5092             :     }
    5093             :     else
    5094             :     {
    5095             : #ifdef ENABLE_UFFD
    5096         179 :         bool bVsiFile = !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
    5097         179 :         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
    5098         179 :         void *pVma = nullptr;
    5099         179 :         uint64_t nVmaSize = 0;
    5100         179 :         cpl_uffd_context *pCtx = nullptr;
    5101             : 
    5102         179 :         if (bVsiFile && bReadOnly && CPLIsUserFaultMappingSupported())
    5103           1 :             pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
    5104             :                                              &nVmaSize);
    5105         179 :         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
    5106             :         {
    5107             :             // netCDF code, at least for netCDF 4.7.0, is confused by filenames
    5108             :             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
    5109             :             // final part
    5110           1 :             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
    5111             :                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);
    5112             :         }
    5113             :         else
    5114         178 :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    5115         179 :         poSharedResources->m_pUffdCtx = pCtx;
    5116             : #else
    5117             :         status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    5118             : #endif
    5119             :     }
    5120         183 :     if (status2 != NC_NOERR)
    5121             :     {
    5122             : #ifdef NCDF_DEBUG
    5123             :         CPLDebug("GDAL_netCDF", "error opening");
    5124             : #endif
    5125           3 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    5126             :                                     // with GDALDataset own mutex.
    5127           3 :         delete poDS;
    5128           3 :         CPLAcquireMutex(hNCMutex, 1000.0);
    5129           3 :         return nullptr;
    5130             :     }
    5131             : #ifdef NCDF_DEBUG
    5132             :     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
    5133             : #endif
    5134             : 
    5135             : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
    5136             :     // Try to destroy the temporary file right now on Unix
    5137         180 :     if (poSharedResources->m_bFileToDestroyAtClosing)
    5138             :     {
    5139           1 :         if (VSIUnlink(poSharedResources->m_osFilename) == 0)
    5140             :         {
    5141           1 :             poSharedResources->m_bFileToDestroyAtClosing = false;
    5142             :         }
    5143             :     }
    5144             : #endif
    5145         180 :     poSharedResources->m_bReadOnly = nMode == NC_NOWRITE;
    5146         180 :     poSharedResources->m_bIsNC4 =
    5147         180 :         poDS->eFormat == NCDF_FORMAT_NC4 || poDS->eFormat == NCDF_FORMAT_NC4C;
    5148         180 :     poSharedResources->m_cdfid = cdfid;
    5149         180 :     poSharedResources->m_fpVSIMEM = poDS->fpVSIMEM;
    5150         180 :     poDS->fpVSIMEM = nullptr;
    5151             : 
    5152             :     // Is this a real netCDF file?
    5153             :     int ndims;
    5154             :     int ngatts;
    5155             :     int nvars;
    5156             :     int unlimdimid;
    5157         180 :     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
    5158         180 :     if (status != NC_NOERR)
    5159             :     {
    5160           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    5161             :                                     // with GDALDataset own mutex.
    5162           0 :         delete poDS;
    5163           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    5164           0 :         return nullptr;
    5165             :     }
    5166             : 
    5167         180 :     poDS->m_poRootGroup = netCDFGroup::Create(poSharedResources, cdfid);
    5168             : 
    5169         180 :     poDS->TryLoadXML();
    5170             : 
    5171         180 :     return poDS;
    5172             : }
    5173             : 
    5174             : /************************************************************************/
    5175             : /*                          GetRootGroup()                              */
    5176             : /************************************************************************/
    5177             : 
    5178         299 : std::shared_ptr<GDALGroup> netCDFDataset::GetRootGroup() const
    5179             : {
    5180         299 :     return m_poRootGroup;
    5181             : }
    5182             : 
    5183             : /************************************************************************/
    5184             : /*                      CreateMultiDimensional()                        */
    5185             : /************************************************************************/
    5186             : 
    5187             : GDALDataset *
    5188          57 : netCDFDataset::CreateMultiDimensional(const char *pszFilename,
    5189             :                                       CSLConstList /* papszRootGroupOptions */,
    5190             :                                       CSLConstList papszOptions)
    5191             : {
    5192         114 :     CPLMutexHolderD(&hNCMutex);
    5193             : 
    5194          57 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    5195             :                                 // GDALDataset own mutex.
    5196          57 :     netCDFDataset *poDS = new netCDFDataset();
    5197          57 :     CPLAcquireMutex(hNCMutex, 1000.0);
    5198          57 :     poDS->eAccess = GA_Update;
    5199          57 :     poDS->osFilename = pszFilename;
    5200             : 
    5201             :     // process options.
    5202          57 :     poDS->papszCreationOptions = CSLDuplicate(papszOptions);
    5203          57 :     if (CSLFetchNameValue(papszOptions, "FORMAT") == nullptr)
    5204             :     {
    5205          56 :         poDS->papszCreationOptions =
    5206          56 :             CSLSetNameValue(poDS->papszCreationOptions, "FORMAT", "NC4");
    5207             :     }
    5208          57 :     poDS->ProcessCreationOptions();
    5209             : 
    5210             :     // Create the dataset.
    5211         114 :     CPLString osFilenameForNCCreate(pszFilename);
    5212             : #ifdef _WIN32
    5213             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    5214             :     {
    5215             :         char *pszTemp =
    5216             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    5217             :         osFilenameForNCCreate = pszTemp;
    5218             :         CPLFree(pszTemp);
    5219             :     }
    5220             : #endif
    5221          57 :     int cdfid = 0;
    5222          57 :     int status = nc_create(osFilenameForNCCreate, poDS->nCreateMode, &cdfid);
    5223          57 :     if (status != NC_NOERR)
    5224             :     {
    5225           2 :         CPLError(CE_Failure, CPLE_OpenFailed,
    5226             :                  "Unable to create netCDF file %s (Error code %d): %s .",
    5227             :                  pszFilename, status, nc_strerror(status));
    5228           2 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    5229             :                                     // with GDALDataset own mutex.
    5230           2 :         delete poDS;
    5231           2 :         CPLAcquireMutex(hNCMutex, 1000.0);
    5232           2 :         return nullptr;
    5233             :     }
    5234             : 
    5235             :     auto poSharedResources(
    5236          55 :         std::make_shared<netCDFSharedResources>(pszFilename));
    5237          55 :     poSharedResources->m_cdfid = cdfid;
    5238          55 :     poSharedResources->m_bReadOnly = false;
    5239          55 :     poSharedResources->m_bDefineMode = true;
    5240          55 :     poSharedResources->m_bIsNC4 =
    5241          55 :         poDS->eFormat == NCDF_FORMAT_NC4 || poDS->eFormat == NCDF_FORMAT_NC4C;
    5242             :     poDS->m_poRootGroup =
    5243          55 :         netCDFGroup::Create(poSharedResources, nullptr, cdfid);
    5244          55 :     const char *pszConventions = CSLFetchNameValueDef(
    5245             :         papszOptions, "CONVENTIONS", NCDF_CONVENTIONS_CF_V1_6);
    5246          55 :     if (!EQUAL(pszConventions, ""))
    5247             :     {
    5248          55 :         auto poAttr = poDS->m_poRootGroup->CreateAttribute(
    5249         165 :             NCDF_CONVENTIONS, {}, GDALExtendedDataType::CreateString());
    5250          55 :         if (poAttr)
    5251          55 :             poAttr->Write(pszConventions);
    5252             :     }
    5253             : 
    5254          55 :     return poDS;
    5255             : }

Generated by: LCOV version 1.14