LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfmultidim.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2247 2475 90.8 %
Date: 2025-11-11 21:23:49 Functions: 135 142 95.1 %

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

Generated by: LCOV version 1.14