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

Generated by: LCOV version 1.14