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

Generated by: LCOV version 1.14