LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfmultidim.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2207 2417 91.3 %
Date: 2025-01-18 12:42:00 Functions: 135 141 95.7 %

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

Generated by: LCOV version 1.14