LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4914 5828 84.3 %
Date: 2024-11-21 22:18:42 Functions: 156 163 95.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  netCDF read/write Driver
       4             :  * Purpose:  GDAL bindings over netCDF library.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *           Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2004, Frank Warmerdam
      10             :  * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
      11             :  * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
      12             :  * Copyright (c) 2021, CLS
      13             :  *
      14             :  * SPDX-License-Identifier: MIT
      15             :  ****************************************************************************/
      16             : 
      17             : #include "cpl_port.h"
      18             : 
      19             : #include <array>
      20             : #include <cassert>
      21             : #include <cctype>
      22             : #include <cerrno>
      23             : #include <climits>
      24             : #include <cmath>
      25             : #include <cstdio>
      26             : #include <cstdlib>
      27             : #include <cstring>
      28             : #include <ctime>
      29             : #include <algorithm>
      30             : #include <limits>
      31             : #include <map>
      32             : #include <set>
      33             : #include <queue>
      34             : #include <string>
      35             : #include <tuple>
      36             : #include <utility>
      37             : #include <vector>
      38             : 
      39             : // Must be included after standard includes, otherwise VS2015 fails when
      40             : // including <ctime>
      41             : #include "netcdfdataset.h"
      42             : #include "netcdfdrivercore.h"
      43             : #include "netcdfsg.h"
      44             : #include "netcdfuffd.h"
      45             : 
      46             : #include "netcdf_mem.h"
      47             : 
      48             : #include "cpl_conv.h"
      49             : #include "cpl_error.h"
      50             : #include "cpl_json.h"
      51             : #include "cpl_minixml.h"
      52             : #include "cpl_multiproc.h"
      53             : #include "cpl_progress.h"
      54             : #include "cpl_time.h"
      55             : #include "gdal.h"
      56             : #include "gdal_frmts.h"
      57             : #include "gdal_priv_templates.hpp"
      58             : #include "ogr_core.h"
      59             : #include "ogr_srs_api.h"
      60             : 
      61             : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
      62             : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
      63             : // this is apparently back to expecting filenames in current codepage...
      64             : // Detect netCDF 4.8 with NC_ENCZARR
      65             : // Detect netCDF 4.9 with NC_NOATTCREORD
      66             : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
      67             : #define NETCDF_USES_UTF8
      68             : #endif
      69             : 
      70             : // Internal function declarations.
      71             : 
      72             : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
      73             : 
      74             : static void
      75             : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
      76             :                    bool bWriteGDALHistory, const char *pszOldHist,
      77             :                    const char *pszFunctionName,
      78             :                    const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
      79             : 
      80             : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
      81             :                            const char *pszOldHist);
      82             : 
      83             : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
      84             :                              size_t *nDestSize);
      85             : 
      86             : // Var / attribute helper functions.
      87             : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
      88             :                           const char *pszValue);
      89             : 
      90             : // Replace this where used.
      91             : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
      92             : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
      93             : 
      94             : // Replace this where used.
      95             : static char **NCDFTokenizeArray(const char *pszValue);
      96             : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
      97             :                          GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
      98             :                          const char *pszMatchPrefix = nullptr);
      99             : 
     100             : // NetCDF-4 groups helper functions.
     101             : // They all work also for NetCDF-3 files which are considered as
     102             : // NetCDF-4 file with only one group.
     103             : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
     104             :                                  int *pnGroupId, int *pnVarId);
     105             : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
     106             : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
     107             :                                int **ppanSubGroupIds);
     108             : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
     109             :                                    bool bNC3Compat = true);
     110             : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
     111             :                                  bool bNC3Compat = true);
     112             : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
     113             : 
     114             : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
     115             :                                      char **ppszFullName,
     116             :                                      bool bMandatory = false);
     117             : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
     118             :                                 const char *pszAtt, int *pnAtt,
     119             :                                 bool bMandatory = false);
     120             : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
     121             : 
     122             : // Uncomment this for more debug output.
     123             : // #define NCDF_DEBUG 1
     124             : 
     125             : CPLMutex *hNCMutex = nullptr;
     126             : 
     127             : // Workaround https://github.com/OSGeo/gdal/issues/6253
     128             : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
     129             : // way. Apparently having the same handle works better (this is OK since
     130             : // we have a global mutex on the netCDF library)
     131             : static std::map<std::string, int> goMapNameToNetCDFId;
     132             : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
     133             : 
     134         646 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
     135             : {
     136        1292 :     std::string osKey(pszFilename);
     137         646 :     osKey += "#####";
     138         646 :     osKey += std::to_string(nMode);
     139         646 :     auto oIter = goMapNameToNetCDFId.find(osKey);
     140         646 :     if (oIter == goMapNameToNetCDFId.end())
     141             :     {
     142         601 :         int ret = nc_open(pszFilename, nMode, pID);
     143         601 :         if (ret != NC_NOERR)
     144           3 :             return ret;
     145         598 :         goMapNameToNetCDFId[osKey] = *pID;
     146         598 :         goMapNetCDFIdToKeyAndCount[*pID] =
     147        1196 :             std::pair<std::string, int>(osKey, 1);
     148         598 :         return ret;
     149             :     }
     150             :     else
     151             :     {
     152          45 :         *pID = oIter->second;
     153          45 :         goMapNetCDFIdToKeyAndCount[oIter->second].second++;
     154          45 :         return NC_NOERR;
     155             :     }
     156             : }
     157             : 
     158         898 : int GDAL_nc_close(int cdfid)
     159             : {
     160         898 :     int ret = NC_NOERR;
     161         898 :     auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
     162         898 :     if (oIter != goMapNetCDFIdToKeyAndCount.end())
     163             :     {
     164         643 :         if (--oIter->second.second == 0)
     165             :         {
     166         598 :             ret = nc_close(cdfid);
     167         598 :             goMapNameToNetCDFId.erase(oIter->second.first);
     168         598 :             goMapNetCDFIdToKeyAndCount.erase(oIter);
     169             :         }
     170             :     }
     171             :     else
     172             :     {
     173             :         // we can go here if file opened with nc_open_mem() or nc_create()
     174         255 :         ret = nc_close(cdfid);
     175             :     }
     176         898 :     return ret;
     177             : }
     178             : 
     179             : /************************************************************************/
     180             : /* ==================================================================== */
     181             : /*                         netCDFRasterBand                             */
     182             : /* ==================================================================== */
     183             : /************************************************************************/
     184             : 
     185             : class netCDFRasterBand final : public GDALPamRasterBand
     186             : {
     187             :     friend class netCDFDataset;
     188             : 
     189             :     nc_type nc_datatype;
     190             :     int cdfid;
     191             :     int nZId;
     192             :     int nZDim;
     193             :     int nLevel;
     194             :     int nBandXPos;
     195             :     int nBandYPos;
     196             :     int *panBandZPos;
     197             :     int *panBandZLev;
     198             :     bool m_bNoDataSet = false;
     199             :     double m_dfNoDataValue = 0;
     200             :     bool m_bNoDataSetAsInt64 = false;
     201             :     int64_t m_nNodataValueInt64 = 0;
     202             :     bool m_bNoDataSetAsUInt64 = false;
     203             :     uint64_t m_nNodataValueUInt64 = 0;
     204             :     bool bValidRangeValid = false;
     205             :     double adfValidRange[2]{0, 0};
     206             :     bool m_bHaveScale = false;
     207             :     bool m_bHaveOffset = false;
     208             :     double m_dfScale = 1;
     209             :     double m_dfOffset = 0;
     210             :     CPLString m_osUnitType{};
     211             :     bool bSignedData;
     212             :     bool bCheckLongitude;
     213             :     bool m_bCreateMetadataFromOtherVarsDone = false;
     214             : 
     215             :     void CreateMetadataFromAttributes();
     216             :     void CreateMetadataFromOtherVars();
     217             : 
     218             :     template <class T>
     219             :     void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     220             :                    size_t nTmpBlockYSize, bool bCheckIsNan = false);
     221             :     template <class T>
     222             :     void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     223             :                       size_t nTmpBlockYSize, bool bCheckIsNan = false);
     224             :     void SetBlockSize();
     225             : 
     226             :     bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
     227             : 
     228             :     void SetNoDataValueNoUpdate(double dfNoData);
     229             :     void SetNoDataValueNoUpdate(int64_t nNoData);
     230             :     void SetNoDataValueNoUpdate(uint64_t nNoData);
     231             : 
     232             :     void SetOffsetNoUpdate(double dfVal);
     233             :     void SetScaleNoUpdate(double dfVal);
     234             :     void SetUnitTypeNoUpdate(const char *pszNewValue);
     235             : 
     236             :   protected:
     237             :     CPLXMLNode *SerializeToXML(const char *pszUnused) override;
     238             : 
     239             :   public:
     240             :     struct CONSTRUCTOR_OPEN
     241             :     {
     242             :     };
     243             : 
     244             :     struct CONSTRUCTOR_CREATE
     245             :     {
     246             :     };
     247             : 
     248             :     netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
     249             :                      int nGroupId, int nZId, int nZDim, int nLevel,
     250             :                      const int *panBandZLen, const int *panBandPos, int nBand);
     251             :     netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
     252             :                      GDALDataType eType, int nBand, bool bSigned = true,
     253             :                      const char *pszBandName = nullptr,
     254             :                      const char *pszLongName = nullptr, int nZId = -1,
     255             :                      int nZDim = 2, int nLevel = 0,
     256             :                      const int *panBandZLev = nullptr,
     257             :                      const int *panBandZPos = nullptr,
     258             :                      const int *paDimIds = nullptr);
     259             :     virtual ~netCDFRasterBand();
     260             : 
     261             :     virtual double GetNoDataValue(int *) override;
     262             :     virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
     263             :     virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
     264             :     virtual CPLErr SetNoDataValue(double) override;
     265             :     virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
     266             :     virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
     267             :     // virtual CPLErr DeleteNoDataValue();
     268             :     virtual double GetOffset(int *) override;
     269             :     virtual CPLErr SetOffset(double) override;
     270             :     virtual double GetScale(int *) override;
     271             :     virtual CPLErr SetScale(double) override;
     272             :     virtual const char *GetUnitType() override;
     273             :     virtual CPLErr SetUnitType(const char *) override;
     274             :     virtual CPLErr IReadBlock(int, int, void *) override;
     275             :     virtual CPLErr IWriteBlock(int, int, void *) override;
     276             : 
     277             :     char **GetMetadata(const char *pszDomain = "") override;
     278             :     const char *GetMetadataItem(const char *pszName,
     279             :                                 const char *pszDomain = "") override;
     280             : 
     281             :     virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     282             :                                    const char *pszDomain = "") override;
     283             :     virtual CPLErr SetMetadata(char **papszMD,
     284             :                                const char *pszDomain = "") override;
     285             : };
     286             : 
     287             : /************************************************************************/
     288             : /*                          netCDFRasterBand()                          */
     289             : /************************************************************************/
     290             : 
     291         457 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
     292             :                                    netCDFDataset *poNCDFDS, int nGroupId,
     293             :                                    int nZIdIn, int nZDimIn, int nLevelIn,
     294             :                                    const int *panBandZLevIn,
     295         457 :                                    const int *panBandZPosIn, int nBandIn)
     296             :     : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
     297         457 :       nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
     298         457 :       nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
     299             :       panBandZLev(nullptr),
     300             :       bSignedData(true),  // Default signed, except for Byte.
     301         914 :       bCheckLongitude(false)
     302             : {
     303         457 :     poDS = poNCDFDS;
     304         457 :     nBand = nBandIn;
     305             : 
     306             :     // Take care of all other dimensions.
     307         457 :     if (nZDim > 2)
     308             :     {
     309         159 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     310         159 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     311             : 
     312         443 :         for (int i = 0; i < nZDim - 2; i++)
     313             :         {
     314         284 :             panBandZPos[i] = panBandZPosIn[i + 2];
     315         284 :             panBandZLev[i] = panBandZLevIn[i];
     316             :         }
     317             :     }
     318             : 
     319         457 :     nRasterXSize = poDS->GetRasterXSize();
     320         457 :     nRasterYSize = poDS->GetRasterYSize();
     321         457 :     nBlockXSize = poDS->GetRasterXSize();
     322         457 :     nBlockYSize = 1;
     323             : 
     324             :     // Get the type of the "z" variable, our target raster array.
     325         457 :     if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
     326         457 :                    nullptr) != NC_NOERR)
     327             :     {
     328           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
     329           0 :         return;
     330             :     }
     331             : 
     332         457 :     if (NCDFIsUserDefinedType(cdfid, nc_datatype))
     333             :     {
     334             :         // First enquire and check that the number of fields is 2
     335             :         size_t nfields, compoundsize;
     336           5 :         if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
     337           5 :                             &nfields) != NC_NOERR)
     338             :         {
     339           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     340             :                      "Error in nc_inq_compound() on 'z'.");
     341           0 :             return;
     342             :         }
     343             : 
     344           5 :         if (nfields != 2)
     345             :         {
     346           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     347             :                      "Unsupported data type encountered in nc_inq_compound() "
     348             :                      "on 'z'.");
     349           0 :             return;
     350             :         }
     351             : 
     352             :         // Now check that that two types are the same in the struct.
     353             :         nc_type field_type1, field_type2;
     354             :         int field_dims1, field_dims2;
     355           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     356             :                                   &field_type1, &field_dims1,
     357           5 :                                   nullptr) != NC_NOERR)
     358             :         {
     359           0 :             CPLError(
     360             :                 CE_Failure, CPLE_AppDefined,
     361             :                 "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
     362           0 :             return;
     363             :         }
     364             : 
     365           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     366             :                                   &field_type2, &field_dims2,
     367           5 :                                   nullptr) != NC_NOERR)
     368             :         {
     369           0 :             CPLError(
     370             :                 CE_Failure, CPLE_AppDefined,
     371             :                 "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
     372           0 :             return;
     373             :         }
     374             : 
     375           5 :         if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
     376           5 :             (field_dims1 != 0))
     377             :         {
     378           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     379             :                      "Error in interpreting compound data type on 'z'.");
     380           0 :             return;
     381             :         }
     382             : 
     383           5 :         if (field_type1 == NC_SHORT)
     384           0 :             eDataType = GDT_CInt16;
     385           5 :         else if (field_type1 == NC_INT)
     386           0 :             eDataType = GDT_CInt32;
     387           5 :         else if (field_type1 == NC_FLOAT)
     388           4 :             eDataType = GDT_CFloat32;
     389           1 :         else if (field_type1 == NC_DOUBLE)
     390           1 :             eDataType = GDT_CFloat64;
     391             :         else
     392             :         {
     393           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     394             :                      "Unsupported netCDF compound data type encountered.");
     395           0 :             return;
     396             :         }
     397             :     }
     398             :     else
     399             :     {
     400         452 :         if (nc_datatype == NC_BYTE)
     401         140 :             eDataType = GDT_Byte;
     402         312 :         else if (nc_datatype == NC_CHAR)
     403           0 :             eDataType = GDT_Byte;
     404         312 :         else if (nc_datatype == NC_SHORT)
     405          41 :             eDataType = GDT_Int16;
     406         271 :         else if (nc_datatype == NC_INT)
     407          89 :             eDataType = GDT_Int32;
     408         182 :         else if (nc_datatype == NC_FLOAT)
     409         106 :             eDataType = GDT_Float32;
     410          76 :         else if (nc_datatype == NC_DOUBLE)
     411          40 :             eDataType = GDT_Float64;
     412          36 :         else if (nc_datatype == NC_UBYTE)
     413          14 :             eDataType = GDT_Byte;
     414          22 :         else if (nc_datatype == NC_USHORT)
     415           4 :             eDataType = GDT_UInt16;
     416          18 :         else if (nc_datatype == NC_UINT)
     417           3 :             eDataType = GDT_UInt32;
     418          15 :         else if (nc_datatype == NC_INT64)
     419           8 :             eDataType = GDT_Int64;
     420           7 :         else if (nc_datatype == NC_UINT64)
     421           7 :             eDataType = GDT_UInt64;
     422             :         else
     423             :         {
     424           0 :             if (nBand == 1)
     425           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     426             :                          "Unsupported netCDF datatype (%d), treat as Float32.",
     427           0 :                          static_cast<int>(nc_datatype));
     428           0 :             eDataType = GDT_Float32;
     429           0 :             nc_datatype = NC_FLOAT;
     430             :         }
     431             :     }
     432             : 
     433             :     // Find and set No Data for this variable.
     434         457 :     nc_type atttype = NC_NAT;
     435         457 :     size_t attlen = 0;
     436         457 :     const char *pszNoValueName = nullptr;
     437             : 
     438             :     // Find attribute name, either _FillValue or missing_value.
     439         457 :     int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
     440         457 :     if (status == NC_NOERR)
     441             :     {
     442         240 :         pszNoValueName = NCDF_FillValue;
     443             :     }
     444             :     else
     445             :     {
     446         217 :         status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
     447         217 :         if (status == NC_NOERR)
     448             :         {
     449          12 :             pszNoValueName = "missing_value";
     450             :         }
     451             :     }
     452             : 
     453             :     // Fetch missing value.
     454         457 :     double dfNoData = 0.0;
     455         457 :     bool bGotNoData = false;
     456         457 :     int64_t nNoDataAsInt64 = 0;
     457         457 :     bool bGotNoDataAsInt64 = false;
     458         457 :     uint64_t nNoDataAsUInt64 = 0;
     459         457 :     bool bGotNoDataAsUInt64 = false;
     460         457 :     if (status == NC_NOERR)
     461             :     {
     462         252 :         nc_type nAttrType = NC_NAT;
     463         252 :         size_t nAttrLen = 0;
     464         252 :         status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
     465         252 :         if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
     466             :         {
     467             :             long long v;
     468           7 :             nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
     469           7 :             bGotNoData = true;
     470           7 :             bGotNoDataAsInt64 = true;
     471           7 :             nNoDataAsInt64 = static_cast<int64_t>(v);
     472             :         }
     473         245 :         else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
     474             :         {
     475             :             unsigned long long v;
     476           7 :             nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
     477           7 :             bGotNoData = true;
     478           7 :             bGotNoDataAsUInt64 = true;
     479           7 :             nNoDataAsUInt64 = static_cast<uint64_t>(v);
     480             :         }
     481         238 :         else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
     482             :         {
     483         237 :             bGotNoData = true;
     484             :         }
     485             :     }
     486             : 
     487             :     // If NoData was not found, use the default value, but for non-Byte types
     488             :     // as it is not recommended:
     489             :     // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
     490         457 :     nc_type vartype = NC_NAT;
     491         457 :     if (!bGotNoData)
     492             :     {
     493         206 :         nc_inq_vartype(cdfid, nZId, &vartype);
     494         206 :         if (vartype == NC_INT64)
     495             :         {
     496             :             nNoDataAsInt64 =
     497           1 :                 NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
     498           1 :             bGotNoDataAsInt64 = bGotNoData;
     499             :         }
     500         205 :         else if (vartype == NC_UINT64)
     501             :         {
     502             :             nNoDataAsUInt64 =
     503           0 :                 NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
     504           0 :             bGotNoDataAsUInt64 = bGotNoData;
     505             :         }
     506         205 :         else if (vartype != NC_CHAR && vartype != NC_BYTE &&
     507          88 :                  vartype != NC_UBYTE)
     508             :         {
     509          80 :             dfNoData =
     510          80 :                 NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
     511          80 :             if (bGotNoData)
     512             :             {
     513          69 :                 CPLDebug("GDAL_netCDF",
     514             :                          "did not get nodata value for variable #%d, using "
     515             :                          "default %f",
     516             :                          nZId, dfNoData);
     517             :             }
     518             :         }
     519             :     }
     520             : 
     521         457 :     bool bHasUnderscoreUnsignedAttr = false;
     522         457 :     bool bUnderscoreUnsignedAttrVal = false;
     523             :     {
     524         457 :         char *pszTemp = nullptr;
     525         457 :         if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
     526             :         {
     527         132 :             if (EQUAL(pszTemp, "true"))
     528             :             {
     529         124 :                 bHasUnderscoreUnsignedAttr = true;
     530         124 :                 bUnderscoreUnsignedAttrVal = true;
     531             :             }
     532           8 :             else if (EQUAL(pszTemp, "false"))
     533             :             {
     534           8 :                 bHasUnderscoreUnsignedAttr = true;
     535           8 :                 bUnderscoreUnsignedAttrVal = false;
     536             :             }
     537         132 :             CPLFree(pszTemp);
     538             :         }
     539             :     }
     540             : 
     541             :     // Look for valid_range or valid_min/valid_max.
     542             : 
     543             :     // First look for valid_range.
     544         457 :     if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
     545             :     {
     546         455 :         char *pszValidRange = nullptr;
     547         455 :         if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
     548         125 :                 CE_None &&
     549         580 :             pszValidRange[0] == '{' &&
     550         125 :             pszValidRange[strlen(pszValidRange) - 1] == '}')
     551             :         {
     552             :             const std::string osValidRange =
     553         375 :                 std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
     554             :             const CPLStringList aosValidRange(
     555         250 :                 CSLTokenizeString2(osValidRange.c_str(), ",", 0));
     556         125 :             if (aosValidRange.size() == 2 &&
     557         250 :                 CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
     558         125 :                 CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
     559             :             {
     560         125 :                 bValidRangeValid = true;
     561         125 :                 adfValidRange[0] = CPLAtof(aosValidRange[0]);
     562         125 :                 adfValidRange[1] = CPLAtof(aosValidRange[1]);
     563             :             }
     564             :         }
     565         455 :         CPLFree(pszValidRange);
     566             : 
     567             :         // If not found look for valid_min and valid_max.
     568         455 :         if (!bValidRangeValid)
     569             :         {
     570         330 :             double dfMin = 0;
     571         330 :             double dfMax = 0;
     572         345 :             if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
     573          15 :                 NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
     574             :             {
     575           8 :                 adfValidRange[0] = dfMin;
     576           8 :                 adfValidRange[1] = dfMax;
     577           8 :                 bValidRangeValid = true;
     578             :             }
     579             :         }
     580             : 
     581         455 :         if (bValidRangeValid &&
     582         133 :             (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
     583          17 :             nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
     584             :             bUnderscoreUnsignedAttrVal)
     585             :         {
     586           2 :             if (adfValidRange[0] < 0)
     587           0 :                 adfValidRange[0] += 65536;
     588           2 :             if (adfValidRange[1] < 0)
     589           2 :                 adfValidRange[1] += 65536;
     590           2 :             if (adfValidRange[0] <= adfValidRange[1])
     591             :             {
     592             :                 // Updating metadata item
     593           2 :                 GDALPamRasterBand::SetMetadataItem(
     594             :                     "valid_range",
     595           2 :                     CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
     596           2 :                                static_cast<int>(adfValidRange[1])));
     597             :             }
     598             :         }
     599             : 
     600         455 :         if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
     601             :         {
     602           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     603             :                      "netCDFDataset::valid_range: min > max:\n"
     604             :                      "  min: %lf\n  max: %lf\n",
     605             :                      adfValidRange[0], adfValidRange[1]);
     606           0 :             bValidRangeValid = false;
     607           0 :             adfValidRange[0] = 0.0;
     608           0 :             adfValidRange[1] = 0.0;
     609             :         }
     610             :     }
     611             : 
     612             :     // Special For Byte Bands: check for signed/unsigned byte.
     613         457 :     if (nc_datatype == NC_BYTE)
     614             :     {
     615             :         // netcdf uses signed byte by default, but GDAL uses unsigned by default
     616             :         // This may cause unexpected results, but is needed for back-compat.
     617         140 :         if (poNCDFDS->bIsGdalFile)
     618         119 :             bSignedData = false;
     619             :         else
     620          21 :             bSignedData = true;
     621             : 
     622             :         // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
     623             :         // But in case a NC3 file was converted automatically and has hints
     624             :         // that it is unsigned, take them into account
     625         140 :         if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     626             :         {
     627           3 :             bSignedData = true;
     628             :         }
     629             : 
     630             :         // If we got valid_range, test for signed/unsigned range.
     631             :         // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
     632         140 :         if (bValidRangeValid)
     633             :         {
     634             :             // If we got valid_range={0,255}, treat as unsigned.
     635         121 :             if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
     636             :             {
     637         113 :                 bSignedData = false;
     638             :                 // Reset valid_range.
     639         113 :                 bValidRangeValid = false;
     640             :             }
     641             :             // If we got valid_range={-128,127}, treat as signed.
     642           8 :             else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
     643             :             {
     644           8 :                 bSignedData = true;
     645             :                 // Reset valid_range.
     646           8 :                 bValidRangeValid = false;
     647             :             }
     648             :         }
     649             :         // Else test for _Unsigned.
     650             :         // https://docs.unidata.ucar.edu/nug/current/best_practices.html
     651             :         else
     652             :         {
     653          19 :             if (bHasUnderscoreUnsignedAttr)
     654           7 :                 bSignedData = !bUnderscoreUnsignedAttrVal;
     655             :         }
     656             : 
     657         140 :         if (bSignedData)
     658             :         {
     659          20 :             eDataType = GDT_Int8;
     660             :         }
     661         120 :         else if (dfNoData < 0)
     662             :         {
     663             :             // Fix nodata value as it was stored signed.
     664           6 :             dfNoData += 256;
     665           6 :             if (pszNoValueName)
     666             :             {
     667             :                 // Updating metadata item
     668           6 :                 GDALPamRasterBand::SetMetadataItem(
     669             :                     pszNoValueName,
     670             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     671             :             }
     672             :         }
     673             :     }
     674         317 :     else if (nc_datatype == NC_SHORT)
     675             :     {
     676          41 :         if (bHasUnderscoreUnsignedAttr)
     677             :         {
     678           4 :             bSignedData = !bUnderscoreUnsignedAttrVal;
     679           4 :             if (!bSignedData)
     680           4 :                 eDataType = GDT_UInt16;
     681             :         }
     682             : 
     683             :         // Fix nodata value as it was stored signed.
     684          41 :         if (!bSignedData && dfNoData < 0)
     685             :         {
     686           4 :             dfNoData += 65536;
     687           4 :             if (pszNoValueName)
     688             :             {
     689             :                 // Updating metadata item
     690           4 :                 GDALPamRasterBand::SetMetadataItem(
     691             :                     pszNoValueName,
     692             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     693             :             }
     694             :         }
     695             :     }
     696             : 
     697         276 :     else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
     698         258 :              nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
     699             :     {
     700          28 :         bSignedData = false;
     701             :     }
     702             : 
     703         457 :     CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
     704         457 :              nc_datatype, eDataType, static_cast<int>(bSignedData));
     705             : 
     706         457 :     if (bGotNoData)
     707             :     {
     708             :         // Set nodata value.
     709         321 :         if (bGotNoDataAsInt64)
     710             :         {
     711           8 :             if (eDataType == GDT_Int64)
     712             :             {
     713           8 :                 SetNoDataValueNoUpdate(nNoDataAsInt64);
     714             :             }
     715           0 :             else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
     716             :             {
     717           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
     718             :             }
     719             :             else
     720             :             {
     721           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
     722             :             }
     723             :         }
     724         313 :         else if (bGotNoDataAsUInt64)
     725             :         {
     726           7 :             if (eDataType == GDT_UInt64)
     727             :             {
     728           7 :                 SetNoDataValueNoUpdate(nNoDataAsUInt64);
     729             :             }
     730           0 :             else if (eDataType == GDT_Int64 &&
     731             :                      nNoDataAsUInt64 <=
     732           0 :                          static_cast<uint64_t>(
     733           0 :                              std::numeric_limits<int64_t>::max()))
     734             :             {
     735           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
     736             :             }
     737             :             else
     738             :             {
     739           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
     740             :             }
     741             :         }
     742             :         else
     743             :         {
     744             : #ifdef NCDF_DEBUG
     745             :             CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
     746             : #endif
     747         306 :             if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
     748             :             {
     749           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
     750             :             }
     751         306 :             else if (eDataType == GDT_UInt64 &&
     752           0 :                      GDALIsValueExactAs<uint64_t>(dfNoData))
     753             :             {
     754           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
     755             :             }
     756             :             else
     757             :             {
     758         306 :                 SetNoDataValueNoUpdate(dfNoData);
     759             :             }
     760             :         }
     761             :     }
     762             : 
     763         457 :     CreateMetadataFromAttributes();
     764             : 
     765             :     // Attempt to fetch the scale_factor and add_offset attributes for the
     766             :     // variable and set them.  If these values are not available, set
     767             :     // offset to 0 and scale to 1.
     768         457 :     if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
     769             :     {
     770          16 :         double dfOffset = 0;
     771          16 :         status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
     772          16 :         CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
     773             :                  status);
     774          16 :         SetOffsetNoUpdate(dfOffset);
     775             :     }
     776             : 
     777         457 :     bool bHasScale = false;
     778         457 :     if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
     779             :     {
     780          20 :         bHasScale = true;
     781          20 :         double dfScale = 1;
     782          20 :         status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
     783          20 :         CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
     784             :                  status);
     785          20 :         SetScaleNoUpdate(dfScale);
     786             :     }
     787             : 
     788          12 :     if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
     789           4 :         eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
     790           4 :         (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
     791         469 :          std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
     792           1 :         CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
     793             :             nullptr)
     794             :     {
     795           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     796             :                  "validity range = %f, %f contains floating-point values, "
     797             :                  "whereas data type is integer. valid_range is thus likely "
     798             :                  "wrong%s. Ignoring it.",
     799             :                  adfValidRange[0], adfValidRange[1],
     800             :                  bHasScale ? " (likely scaled using scale_factor/add_factor "
     801             :                              "whereas it should be using the packed data type)"
     802             :                            : "");
     803           1 :         bValidRangeValid = false;
     804           1 :         adfValidRange[0] = 0.0;
     805           1 :         adfValidRange[1] = 0.0;
     806             :     }
     807             : 
     808             :     // Should we check for longitude values > 360?
     809         457 :     bCheckLongitude =
     810         914 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
     811         457 :         NCDFIsVarLongitude(cdfid, nZId, nullptr);
     812             : 
     813             :     // Attempt to fetch the units attribute for the variable and set it.
     814         457 :     SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
     815             : 
     816         457 :     SetBlockSize();
     817             : }
     818             : 
     819         636 : void netCDFRasterBand::SetBlockSize()
     820             : {
     821             :     // Check for variable chunking (netcdf-4 only).
     822             :     // GDAL block size should be set to hdf5 chunk size.
     823         636 :     int nTmpFormat = 0;
     824         636 :     int status = nc_inq_format(cdfid, &nTmpFormat);
     825         636 :     NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
     826         636 :     if ((status == NC_NOERR) &&
     827         544 :         (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
     828             :     {
     829         106 :         size_t chunksize[MAX_NC_DIMS] = {};
     830             :         // Check for chunksize and set it as the blocksize (optimizes read).
     831         106 :         status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
     832         106 :         if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
     833             :         {
     834          11 :             nBlockXSize = (int)chunksize[nZDim - 1];
     835          11 :             if (nZDim >= 2)
     836          11 :                 nBlockYSize = (int)chunksize[nZDim - 2];
     837             :             else
     838           0 :                 nBlockYSize = 1;
     839             :         }
     840             :     }
     841             : 
     842             :     // Deal with bottom-up datasets and nBlockYSize != 1.
     843         636 :     auto poGDS = static_cast<netCDFDataset *>(poDS);
     844         636 :     if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
     845             :     {
     846           5 :         if (poGDS->eAccess == GA_ReadOnly)
     847             :         {
     848             :             // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
     849             :             // width of the raster
     850           5 :             size_t nChunks =
     851           5 :                 static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
     852           5 :             if ((nRasterYSize % nBlockYSize) != 0)
     853           1 :                 nChunks *= 2;
     854             :             const size_t nChunkSize =
     855           5 :                 static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
     856           5 :                 nBlockXSize * nBlockYSize;
     857           5 :             constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
     858           5 :             nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
     859           5 :             if (nChunks)
     860             :             {
     861           5 :                 poGDS->poChunkCache.reset(
     862           5 :                     new netCDFDataset::ChunkCacheType(nChunks));
     863             :             }
     864             :         }
     865             :         else
     866             :         {
     867           0 :             nBlockYSize = 1;
     868             :         }
     869             :     }
     870         636 : }
     871             : 
     872             : // Constructor in create mode.
     873             : // If nZId and following variables are not passed, the band will have 2
     874             : // dimensions.
     875             : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
     876         179 : netCDFRasterBand::netCDFRasterBand(
     877             :     const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
     878             :     const GDALDataType eTypeIn, int nBandIn, bool bSigned,
     879             :     const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
     880             :     int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
     881         179 :     const int *paDimIds)
     882         179 :     : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
     883             :       nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
     884             :       panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
     885         179 :       bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
     886             : {
     887         179 :     poDS = poNCDFDS;
     888         179 :     nBand = nBandIn;
     889             : 
     890         179 :     nRasterXSize = poDS->GetRasterXSize();
     891         179 :     nRasterYSize = poDS->GetRasterYSize();
     892         179 :     nBlockXSize = poDS->GetRasterXSize();
     893         179 :     nBlockYSize = 1;
     894             : 
     895         179 :     if (poDS->GetAccess() != GA_Update)
     896             :     {
     897           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     898             :                  "Dataset is not in update mode, "
     899             :                  "wrong netCDFRasterBand constructor");
     900           0 :         return;
     901             :     }
     902             : 
     903             :     // Take care of all other dimensions.
     904         179 :     if (nZDim > 2 && paDimIds != nullptr)
     905             :     {
     906          27 :         nBandXPos = panBandZPosIn[0];
     907          27 :         nBandYPos = panBandZPosIn[1];
     908             : 
     909          27 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     910          27 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     911             : 
     912          76 :         for (int i = 0; i < nZDim - 2; i++)
     913             :         {
     914          49 :             panBandZPos[i] = panBandZPosIn[i + 2];
     915          49 :             panBandZLev[i] = panBandZLevIn[i];
     916             :         }
     917             :     }
     918             : 
     919             :     // Get the type of the "z" variable, our target raster array.
     920         179 :     eDataType = eTypeIn;
     921             : 
     922         179 :     switch (eDataType)
     923             :     {
     924          74 :         case GDT_Byte:
     925          74 :             nc_datatype = NC_BYTE;
     926             :             // NC_UBYTE (unsigned byte) is only available for NC4.
     927          74 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     928           3 :                 nc_datatype = NC_UBYTE;
     929          74 :             break;
     930           7 :         case GDT_Int8:
     931           7 :             nc_datatype = NC_BYTE;
     932           7 :             break;
     933          11 :         case GDT_Int16:
     934          11 :             nc_datatype = NC_SHORT;
     935          11 :             break;
     936          24 :         case GDT_Int32:
     937          24 :             nc_datatype = NC_INT;
     938          24 :             break;
     939          13 :         case GDT_Float32:
     940          13 :             nc_datatype = NC_FLOAT;
     941          13 :             break;
     942           8 :         case GDT_Float64:
     943           8 :             nc_datatype = NC_DOUBLE;
     944           8 :             break;
     945           7 :         case GDT_Int64:
     946           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     947             :             {
     948           7 :                 nc_datatype = NC_INT64;
     949             :             }
     950             :             else
     951             :             {
     952           0 :                 if (nBand == 1)
     953           0 :                     CPLError(
     954             :                         CE_Warning, CPLE_AppDefined,
     955             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     956             :                         "Int64");
     957           0 :                 nc_datatype = NC_DOUBLE;
     958           0 :                 eDataType = GDT_Float64;
     959             :             }
     960           7 :             break;
     961           7 :         case GDT_UInt64:
     962           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     963             :             {
     964           7 :                 nc_datatype = NC_UINT64;
     965             :             }
     966             :             else
     967             :             {
     968           0 :                 if (nBand == 1)
     969           0 :                     CPLError(
     970             :                         CE_Warning, CPLE_AppDefined,
     971             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     972             :                         "UInt64");
     973           0 :                 nc_datatype = NC_DOUBLE;
     974           0 :                 eDataType = GDT_Float64;
     975             :             }
     976           7 :             break;
     977           6 :         case GDT_UInt16:
     978           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     979             :             {
     980           6 :                 nc_datatype = NC_USHORT;
     981           6 :                 break;
     982             :             }
     983             :             [[fallthrough]];
     984             :         case GDT_UInt32:
     985           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     986             :             {
     987           6 :                 nc_datatype = NC_UINT;
     988           6 :                 break;
     989             :             }
     990             :             [[fallthrough]];
     991             :         default:
     992          16 :             if (nBand == 1)
     993           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
     994             :                          "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
     995           8 :                          static_cast<int>(eDataType));
     996          16 :             nc_datatype = NC_FLOAT;
     997          16 :             eDataType = GDT_Float32;
     998          16 :             break;
     999             :     }
    1000             : 
    1001             :     // Define the variable if necessary (if nZId == -1).
    1002         179 :     bool bDefineVar = false;
    1003             : 
    1004         179 :     if (nZId == -1)
    1005             :     {
    1006         157 :         bDefineVar = true;
    1007             : 
    1008             :         // Make sure we are in define mode.
    1009         157 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1010             : 
    1011             :         char szTempPrivate[256 + 1];
    1012         157 :         const char *pszTemp = nullptr;
    1013         157 :         if (!pszBandName || EQUAL(pszBandName, ""))
    1014             :         {
    1015         135 :             snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
    1016         135 :             pszTemp = szTempPrivate;
    1017             :         }
    1018             :         else
    1019             :         {
    1020          22 :             pszTemp = pszBandName;
    1021             :         }
    1022             : 
    1023             :         int status;
    1024         157 :         if (nZDim > 2 && paDimIds != nullptr)
    1025             :         {
    1026           5 :             status =
    1027           5 :                 nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
    1028             :         }
    1029             :         else
    1030             :         {
    1031         152 :             int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
    1032             :             status =
    1033         152 :                 nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
    1034             :         }
    1035         157 :         NCDF_ERR(status);
    1036         157 :         CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
    1037             :                  nc_datatype, nZId);
    1038             : 
    1039         157 :         if (!pszLongName || EQUAL(pszLongName, ""))
    1040             :         {
    1041         150 :             snprintf(szTempPrivate, sizeof(szTempPrivate),
    1042             :                      "GDAL Band Number %d", nBand);
    1043         150 :             pszTemp = szTempPrivate;
    1044             :         }
    1045             :         else
    1046             :         {
    1047           7 :             pszTemp = pszLongName;
    1048             :         }
    1049             :         status =
    1050         157 :             nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
    1051         157 :         NCDF_ERR(status);
    1052             : 
    1053         157 :         poNCDFDS->DefVarDeflate(nZId, true);
    1054             :     }
    1055             : 
    1056             :     // For Byte data add signed/unsigned info.
    1057         179 :     if (eDataType == GDT_Byte || eDataType == GDT_Int8)
    1058             :     {
    1059          81 :         if (bDefineVar)
    1060             :         {
    1061             :             // Only add attributes if creating variable.
    1062             :             // For unsigned NC_BYTE (except NC4 format),
    1063             :             // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
    1064          73 :             if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
    1065             :             {
    1066          70 :                 CPLDebug("GDAL_netCDF",
    1067             :                          "adding valid_range attributes for Byte Band");
    1068          70 :                 short l_adfValidRange[2] = {0, 0};
    1069             :                 int status;
    1070          70 :                 if (bSignedData || eDataType == GDT_Int8)
    1071             :                 {
    1072           7 :                     l_adfValidRange[0] = -128;
    1073           7 :                     l_adfValidRange[1] = 127;
    1074           7 :                     status =
    1075           7 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
    1076             :                 }
    1077             :                 else
    1078             :                 {
    1079          63 :                     l_adfValidRange[0] = 0;
    1080          63 :                     l_adfValidRange[1] = 255;
    1081             :                     status =
    1082          63 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
    1083             :                 }
    1084          70 :                 NCDF_ERR(status);
    1085          70 :                 status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
    1086             :                                           2, l_adfValidRange);
    1087          70 :                 NCDF_ERR(status);
    1088             :             }
    1089             :         }
    1090             :     }
    1091             : 
    1092         179 :     if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
    1093         101 :         nc_datatype != NC_UBYTE)
    1094             :     {
    1095             :         // Set default nodata.
    1096          98 :         bool bIgnored = false;
    1097             :         double dfNoData =
    1098          98 :             NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
    1099             : #ifdef NCDF_DEBUG
    1100             :         CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
    1101             : #endif
    1102          98 :         netCDFRasterBand::SetNoDataValue(dfNoData);
    1103             :     }
    1104             : 
    1105         179 :     SetBlockSize();
    1106             : }
    1107             : 
    1108             : /************************************************************************/
    1109             : /*                         ~netCDFRasterBand()                          */
    1110             : /************************************************************************/
    1111             : 
    1112        1272 : netCDFRasterBand::~netCDFRasterBand()
    1113             : {
    1114         636 :     netCDFRasterBand::FlushCache(true);
    1115         636 :     CPLFree(panBandZPos);
    1116         636 :     CPLFree(panBandZLev);
    1117        1272 : }
    1118             : 
    1119             : /************************************************************************/
    1120             : /*                          GetMetadata()                               */
    1121             : /************************************************************************/
    1122             : 
    1123          47 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
    1124             : {
    1125          47 :     if (!m_bCreateMetadataFromOtherVarsDone)
    1126          47 :         CreateMetadataFromOtherVars();
    1127          47 :     return GDALPamRasterBand::GetMetadata(pszDomain);
    1128             : }
    1129             : 
    1130             : /************************************************************************/
    1131             : /*                        GetMetadataItem()                             */
    1132             : /************************************************************************/
    1133             : 
    1134         522 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
    1135             :                                               const char *pszDomain)
    1136             : {
    1137         522 :     if (!m_bCreateMetadataFromOtherVarsDone &&
    1138         506 :         STARTS_WITH(pszName, "NETCDF_DIM_") &&
    1139           1 :         (!pszDomain || pszDomain[0] == 0))
    1140           1 :         CreateMetadataFromOtherVars();
    1141         522 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
    1142             : }
    1143             : 
    1144             : /************************************************************************/
    1145             : /*                        SetMetadataItem()                             */
    1146             : /************************************************************************/
    1147             : 
    1148           6 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
    1149             :                                          const char *pszValue,
    1150             :                                          const char *pszDomain)
    1151             : {
    1152           7 :     if (GetAccess() == GA_Update &&
    1153           7 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    1154             :     {
    1155             :         // Same logic as in CopyMetadata()
    1156             : 
    1157           1 :         const char *const papszIgnoreBand[] = {
    1158             :             CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    1159             :             NCDF_FillValue, "coordinates",   nullptr};
    1160             :         // Do not copy varname, stats, NETCDF_DIM_*, nodata
    1161             :         // and items in papszIgnoreBand.
    1162           3 :         if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
    1163           1 :             STARTS_WITH(pszName, "STATISTICS_") ||
    1164           1 :             STARTS_WITH(pszName, "NETCDF_DIM_") ||
    1165           1 :             STARTS_WITH(pszName, "missing_value") ||
    1166           3 :             STARTS_WITH(pszName, "_FillValue") ||
    1167           1 :             CSLFindString(papszIgnoreBand, pszName) != -1)
    1168             :         {
    1169             :             // do nothing
    1170             :         }
    1171             :         else
    1172             :         {
    1173           1 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1174             : 
    1175           1 :             if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
    1176           1 :                 return CE_Failure;
    1177             :         }
    1178             :     }
    1179             : 
    1180           5 :     return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    1181             : }
    1182             : 
    1183             : /************************************************************************/
    1184             : /*                          SetMetadata()                               */
    1185             : /************************************************************************/
    1186             : 
    1187           1 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
    1188             : {
    1189           2 :     if (GetAccess() == GA_Update &&
    1190           1 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    1191             :     {
    1192             :         // We don't handle metadata item removal for now
    1193           2 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    1194             :              ++papszIter)
    1195             :         {
    1196           1 :             char *pszName = nullptr;
    1197           1 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    1198           1 :             if (pszName && pszValue)
    1199           1 :                 SetMetadataItem(pszName, pszValue);
    1200           1 :             CPLFree(pszName);
    1201             :         }
    1202             :     }
    1203           1 :     return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
    1204             : }
    1205             : 
    1206             : /************************************************************************/
    1207             : /*                             GetOffset()                              */
    1208             : /************************************************************************/
    1209          49 : double netCDFRasterBand::GetOffset(int *pbSuccess)
    1210             : {
    1211          49 :     if (pbSuccess != nullptr)
    1212          44 :         *pbSuccess = static_cast<int>(m_bHaveOffset);
    1213             : 
    1214          49 :     return m_dfOffset;
    1215             : }
    1216             : 
    1217             : /************************************************************************/
    1218             : /*                             SetOffset()                              */
    1219             : /************************************************************************/
    1220           1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
    1221             : {
    1222           2 :     CPLMutexHolderD(&hNCMutex);
    1223             : 
    1224             :     // Write value if in update mode.
    1225           1 :     if (poDS->GetAccess() == GA_Update)
    1226             :     {
    1227             :         // Make sure we are in define mode.
    1228           1 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1229             : 
    1230           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
    1231             :                                              NC_DOUBLE, 1, &dfNewOffset);
    1232             : 
    1233           1 :         NCDF_ERR(status);
    1234           1 :         if (status == NC_NOERR)
    1235             :         {
    1236           1 :             SetOffsetNoUpdate(dfNewOffset);
    1237           1 :             return CE_None;
    1238             :         }
    1239             : 
    1240           0 :         return CE_Failure;
    1241             :     }
    1242             : 
    1243           0 :     SetOffsetNoUpdate(dfNewOffset);
    1244           0 :     return CE_None;
    1245             : }
    1246             : 
    1247             : /************************************************************************/
    1248             : /*                         SetOffsetNoUpdate()                          */
    1249             : /************************************************************************/
    1250          17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
    1251             : {
    1252          17 :     m_dfOffset = dfVal;
    1253          17 :     m_bHaveOffset = true;
    1254          17 : }
    1255             : 
    1256             : /************************************************************************/
    1257             : /*                              GetScale()                              */
    1258             : /************************************************************************/
    1259          49 : double netCDFRasterBand::GetScale(int *pbSuccess)
    1260             : {
    1261          49 :     if (pbSuccess != nullptr)
    1262          44 :         *pbSuccess = static_cast<int>(m_bHaveScale);
    1263             : 
    1264          49 :     return m_dfScale;
    1265             : }
    1266             : 
    1267             : /************************************************************************/
    1268             : /*                              SetScale()                              */
    1269             : /************************************************************************/
    1270           1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
    1271             : {
    1272           2 :     CPLMutexHolderD(&hNCMutex);
    1273             : 
    1274             :     // Write value if in update mode.
    1275           1 :     if (poDS->GetAccess() == GA_Update)
    1276             :     {
    1277             :         // Make sure we are in define mode.
    1278           1 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1279             : 
    1280           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
    1281             :                                              NC_DOUBLE, 1, &dfNewScale);
    1282             : 
    1283           1 :         NCDF_ERR(status);
    1284           1 :         if (status == NC_NOERR)
    1285             :         {
    1286           1 :             SetScaleNoUpdate(dfNewScale);
    1287           1 :             return CE_None;
    1288             :         }
    1289             : 
    1290           0 :         return CE_Failure;
    1291             :     }
    1292             : 
    1293           0 :     SetScaleNoUpdate(dfNewScale);
    1294           0 :     return CE_None;
    1295             : }
    1296             : 
    1297             : /************************************************************************/
    1298             : /*                         SetScaleNoUpdate()                           */
    1299             : /************************************************************************/
    1300          21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
    1301             : {
    1302          21 :     m_dfScale = dfVal;
    1303          21 :     m_bHaveScale = true;
    1304          21 : }
    1305             : 
    1306             : /************************************************************************/
    1307             : /*                            GetUnitType()                             */
    1308             : /************************************************************************/
    1309             : 
    1310          21 : const char *netCDFRasterBand::GetUnitType()
    1311             : 
    1312             : {
    1313          21 :     if (!m_osUnitType.empty())
    1314           6 :         return m_osUnitType;
    1315             : 
    1316          15 :     return GDALRasterBand::GetUnitType();
    1317             : }
    1318             : 
    1319             : /************************************************************************/
    1320             : /*                           SetUnitType()                              */
    1321             : /************************************************************************/
    1322             : 
    1323           1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
    1324             : 
    1325             : {
    1326           2 :     CPLMutexHolderD(&hNCMutex);
    1327             : 
    1328           2 :     const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1329             : 
    1330           1 :     if (!osUnitType.empty())
    1331             :     {
    1332             :         // Write value if in update mode.
    1333           1 :         if (poDS->GetAccess() == GA_Update)
    1334             :         {
    1335             :             // Make sure we are in define mode.
    1336           1 :             static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
    1337             : 
    1338           1 :             const int status = nc_put_att_text(
    1339             :                 cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
    1340             : 
    1341           1 :             NCDF_ERR(status);
    1342           1 :             if (status == NC_NOERR)
    1343             :             {
    1344           1 :                 SetUnitTypeNoUpdate(pszNewValue);
    1345           1 :                 return CE_None;
    1346             :             }
    1347             : 
    1348           0 :             return CE_Failure;
    1349             :         }
    1350             :     }
    1351             : 
    1352           0 :     SetUnitTypeNoUpdate(pszNewValue);
    1353             : 
    1354           0 :     return CE_None;
    1355             : }
    1356             : 
    1357             : /************************************************************************/
    1358             : /*                       SetUnitTypeNoUpdate()                          */
    1359             : /************************************************************************/
    1360             : 
    1361         458 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
    1362             : {
    1363         458 :     m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1364         458 : }
    1365             : 
    1366             : /************************************************************************/
    1367             : /*                           GetNoDataValue()                           */
    1368             : /************************************************************************/
    1369             : 
    1370         153 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
    1371             : 
    1372             : {
    1373         153 :     if (m_bNoDataSetAsInt64)
    1374             :     {
    1375           0 :         if (pbSuccess)
    1376           0 :             *pbSuccess = TRUE;
    1377           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
    1378             :     }
    1379             : 
    1380         153 :     if (m_bNoDataSetAsUInt64)
    1381             :     {
    1382           0 :         if (pbSuccess)
    1383           0 :             *pbSuccess = TRUE;
    1384           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
    1385             :     }
    1386             : 
    1387         153 :     if (m_bNoDataSet)
    1388             :     {
    1389         118 :         if (pbSuccess)
    1390         102 :             *pbSuccess = TRUE;
    1391         118 :         return m_dfNoDataValue;
    1392             :     }
    1393             : 
    1394          35 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
    1395             : }
    1396             : 
    1397             : /************************************************************************/
    1398             : /*                        GetNoDataValueAsInt64()                       */
    1399             : /************************************************************************/
    1400             : 
    1401           4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
    1402             : 
    1403             : {
    1404           4 :     if (m_bNoDataSetAsInt64)
    1405             :     {
    1406           4 :         if (pbSuccess)
    1407           4 :             *pbSuccess = TRUE;
    1408             : 
    1409           4 :         return m_nNodataValueInt64;
    1410             :     }
    1411             : 
    1412           0 :     return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
    1413             : }
    1414             : 
    1415             : /************************************************************************/
    1416             : /*                        GetNoDataValueAsUInt64()                      */
    1417             : /************************************************************************/
    1418             : 
    1419           4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
    1420             : 
    1421             : {
    1422           4 :     if (m_bNoDataSetAsUInt64)
    1423             :     {
    1424           4 :         if (pbSuccess)
    1425           4 :             *pbSuccess = TRUE;
    1426             : 
    1427           4 :         return m_nNodataValueUInt64;
    1428             :     }
    1429             : 
    1430           0 :     return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
    1431             : }
    1432             : 
    1433             : /************************************************************************/
    1434             : /*                           SetNoDataValue()                           */
    1435             : /************************************************************************/
    1436             : 
    1437         134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
    1438             : 
    1439             : {
    1440         268 :     CPLMutexHolderD(&hNCMutex);
    1441             : 
    1442             :     // If already set to new value, don't do anything.
    1443         134 :     if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
    1444          19 :         return CE_None;
    1445             : 
    1446             :     // Write value if in update mode.
    1447         115 :     if (poDS->GetAccess() == GA_Update)
    1448             :     {
    1449             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1450             :         // but it is ok if variable has not been written to, so only print
    1451             :         // debug. See bug #4484.
    1452         125 :         if (m_bNoDataSet &&
    1453          10 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1454             :         {
    1455           0 :             CPLDebug("GDAL_netCDF",
    1456             :                      "Setting NoDataValue to %.17g (previously set to %.17g) "
    1457             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1458             :                      dfNoData, m_dfNoDataValue, cdfid, nBand);
    1459             :         }
    1460             : #ifdef NCDF_DEBUG
    1461             :         else
    1462             :         {
    1463             :             CPLDebug("GDAL_netCDF",
    1464             :                      "Setting NoDataValue to %.17g (id #%d, band #%d)",
    1465             :                      dfNoData, cdfid, nBand);
    1466             :         }
    1467             : #endif
    1468             :         // Make sure we are in define mode.
    1469         115 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1470             : 
    1471             :         int status;
    1472         115 :         if (eDataType == GDT_Byte)
    1473             :         {
    1474           6 :             if (bSignedData)
    1475             :             {
    1476           0 :                 signed char cNoDataValue = static_cast<signed char>(dfNoData);
    1477           0 :                 status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
    1478             :                                           nc_datatype, 1, &cNoDataValue);
    1479             :             }
    1480             :             else
    1481             :             {
    1482           6 :                 const unsigned char ucNoDataValue =
    1483           6 :                     static_cast<unsigned char>(dfNoData);
    1484           6 :                 status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
    1485             :                                           nc_datatype, 1, &ucNoDataValue);
    1486             :             }
    1487             :         }
    1488         109 :         else if (eDataType == GDT_Int16)
    1489             :         {
    1490          14 :             short nsNoDataValue = static_cast<short>(dfNoData);
    1491          14 :             status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1492             :                                       1, &nsNoDataValue);
    1493             :         }
    1494          95 :         else if (eDataType == GDT_Int32)
    1495             :         {
    1496          27 :             int nNoDataValue = static_cast<int>(dfNoData);
    1497          27 :             status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
    1498             :                                     &nNoDataValue);
    1499             :         }
    1500          68 :         else if (eDataType == GDT_Float32)
    1501             :         {
    1502          31 :             float fNoDataValue = static_cast<float>(dfNoData);
    1503          31 :             status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1504             :                                       1, &fNoDataValue);
    1505             :         }
    1506          37 :         else if (eDataType == GDT_UInt16 &&
    1507           6 :                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
    1508             :                      NCDF_FORMAT_NC4)
    1509             :         {
    1510           6 :             unsigned short usNoDataValue =
    1511           6 :                 static_cast<unsigned short>(dfNoData);
    1512           6 :             status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1513           6 :                                        1, &usNoDataValue);
    1514             :         }
    1515          31 :         else if (eDataType == GDT_UInt32 &&
    1516           7 :                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
    1517             :                      NCDF_FORMAT_NC4)
    1518             :         {
    1519           7 :             unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
    1520           7 :             status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1521           7 :                                      1, &unNoDataValue);
    1522             :         }
    1523             :         else
    1524             :         {
    1525          24 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1526             :                                        1, &dfNoData);
    1527             :         }
    1528             : 
    1529         115 :         NCDF_ERR(status);
    1530             : 
    1531             :         // Update status if write worked.
    1532         115 :         if (status == NC_NOERR)
    1533             :         {
    1534         115 :             SetNoDataValueNoUpdate(dfNoData);
    1535         115 :             return CE_None;
    1536             :         }
    1537             : 
    1538           0 :         return CE_Failure;
    1539             :     }
    1540             : 
    1541           0 :     SetNoDataValueNoUpdate(dfNoData);
    1542           0 :     return CE_None;
    1543             : }
    1544             : 
    1545             : /************************************************************************/
    1546             : /*                       SetNoDataValueNoUpdate()                       */
    1547             : /************************************************************************/
    1548             : 
    1549         421 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
    1550             : {
    1551         421 :     m_dfNoDataValue = dfNoData;
    1552         421 :     m_bNoDataSet = true;
    1553         421 :     m_bNoDataSetAsInt64 = false;
    1554         421 :     m_bNoDataSetAsUInt64 = false;
    1555         421 : }
    1556             : 
    1557             : /************************************************************************/
    1558             : /*                        SetNoDataValueAsInt64()                       */
    1559             : /************************************************************************/
    1560             : 
    1561           3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    1562             : 
    1563             : {
    1564           6 :     CPLMutexHolderD(&hNCMutex);
    1565             : 
    1566             :     // If already set to new value, don't do anything.
    1567           3 :     if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
    1568           0 :         return CE_None;
    1569             : 
    1570             :     // Write value if in update mode.
    1571           3 :     if (poDS->GetAccess() == GA_Update)
    1572             :     {
    1573             :         // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
    1574             :         // but it is ok if variable has not been written to, so only print
    1575             :         // debug. See bug #4484.
    1576           3 :         if (m_bNoDataSetAsInt64 &&
    1577           0 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1578             :         {
    1579           0 :             CPLDebug("GDAL_netCDF",
    1580             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1581             :                      " (previously set to " CPL_FRMT_GIB ") "
    1582             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1583             :                      static_cast<GIntBig>(nNoData),
    1584           0 :                      static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
    1585             :         }
    1586             : #ifdef NCDF_DEBUG
    1587             :         else
    1588             :         {
    1589             :             CPLDebug("GDAL_netCDF",
    1590             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1591             :                      " (id #%d, band #%d)",
    1592             :                      static_cast<GIntBig>(nNoData), cdfid, nBand);
    1593             :         }
    1594             : #endif
    1595             :         // Make sure we are in define mode.
    1596           3 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1597             : 
    1598             :         int status;
    1599           3 :         if (eDataType == GDT_Int64 &&
    1600           3 :             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1601             :         {
    1602           3 :             long long tmp = static_cast<long long>(nNoData);
    1603           3 :             status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
    1604           3 :                                          nc_datatype, 1, &tmp);
    1605             :         }
    1606             :         else
    1607             :         {
    1608           0 :             double dfNoData = static_cast<double>(nNoData);
    1609           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1610             :                                        1, &dfNoData);
    1611             :         }
    1612             : 
    1613           3 :         NCDF_ERR(status);
    1614             : 
    1615             :         // Update status if write worked.
    1616           3 :         if (status == NC_NOERR)
    1617             :         {
    1618           3 :             SetNoDataValueNoUpdate(nNoData);
    1619           3 :             return CE_None;
    1620             :         }
    1621             : 
    1622           0 :         return CE_Failure;
    1623             :     }
    1624             : 
    1625           0 :     SetNoDataValueNoUpdate(nNoData);
    1626           0 :     return CE_None;
    1627             : }
    1628             : 
    1629             : /************************************************************************/
    1630             : /*                       SetNoDataValueNoUpdate()                       */
    1631             : /************************************************************************/
    1632             : 
    1633          11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
    1634             : {
    1635          11 :     m_nNodataValueInt64 = nNoData;
    1636          11 :     m_bNoDataSet = false;
    1637          11 :     m_bNoDataSetAsInt64 = true;
    1638          11 :     m_bNoDataSetAsUInt64 = false;
    1639          11 : }
    1640             : 
    1641             : /************************************************************************/
    1642             : /*                        SetNoDataValueAsUInt64()                      */
    1643             : /************************************************************************/
    1644             : 
    1645           3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    1646             : 
    1647             : {
    1648           6 :     CPLMutexHolderD(&hNCMutex);
    1649             : 
    1650             :     // If already set to new value, don't do anything.
    1651           3 :     if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
    1652           0 :         return CE_None;
    1653             : 
    1654             :     // Write value if in update mode.
    1655           3 :     if (poDS->GetAccess() == GA_Update)
    1656             :     {
    1657             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1658             :         // but it is ok if variable has not been written to, so only print
    1659             :         // debug. See bug #4484.
    1660           3 :         if (m_bNoDataSetAsUInt64 &&
    1661           0 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1662             :         {
    1663           0 :             CPLDebug("GDAL_netCDF",
    1664             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1665             :                      " (previously set to " CPL_FRMT_GUIB ") "
    1666             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1667             :                      static_cast<GUIntBig>(nNoData),
    1668           0 :                      static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
    1669             :         }
    1670             : #ifdef NCDF_DEBUG
    1671             :         else
    1672             :         {
    1673             :             CPLDebug("GDAL_netCDF",
    1674             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1675             :                      " (id #%d, band #%d)",
    1676             :                      static_cast<GUIntBig>(nNoData), cdfid, nBand);
    1677             :         }
    1678             : #endif
    1679             :         // Make sure we are in define mode.
    1680           3 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1681             : 
    1682             :         int status;
    1683           3 :         if (eDataType == GDT_UInt64 &&
    1684           3 :             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1685             :         {
    1686           3 :             unsigned long long tmp = static_cast<long long>(nNoData);
    1687           3 :             status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
    1688           3 :                                           nc_datatype, 1, &tmp);
    1689             :         }
    1690             :         else
    1691             :         {
    1692           0 :             double dfNoData = static_cast<double>(nNoData);
    1693           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1694             :                                        1, &dfNoData);
    1695             :         }
    1696             : 
    1697           3 :         NCDF_ERR(status);
    1698             : 
    1699             :         // Update status if write worked.
    1700           3 :         if (status == NC_NOERR)
    1701             :         {
    1702           3 :             SetNoDataValueNoUpdate(nNoData);
    1703           3 :             return CE_None;
    1704             :         }
    1705             : 
    1706           0 :         return CE_Failure;
    1707             :     }
    1708             : 
    1709           0 :     SetNoDataValueNoUpdate(nNoData);
    1710           0 :     return CE_None;
    1711             : }
    1712             : 
    1713             : /************************************************************************/
    1714             : /*                       SetNoDataValueNoUpdate()                       */
    1715             : /************************************************************************/
    1716             : 
    1717          10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
    1718             : {
    1719          10 :     m_nNodataValueUInt64 = nNoData;
    1720          10 :     m_bNoDataSet = false;
    1721          10 :     m_bNoDataSetAsInt64 = false;
    1722          10 :     m_bNoDataSetAsUInt64 = true;
    1723          10 : }
    1724             : 
    1725             : /************************************************************************/
    1726             : /*                        DeleteNoDataValue()                           */
    1727             : /************************************************************************/
    1728             : 
    1729             : #ifdef notdef
    1730             : CPLErr netCDFRasterBand::DeleteNoDataValue()
    1731             : 
    1732             : {
    1733             :     CPLMutexHolderD(&hNCMutex);
    1734             : 
    1735             :     if (!bNoDataSet)
    1736             :         return CE_None;
    1737             : 
    1738             :     // Write value if in update mode.
    1739             :     if (poDS->GetAccess() == GA_Update)
    1740             :     {
    1741             :         // Make sure we are in define mode.
    1742             :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1743             : 
    1744             :         status = nc_del_att(cdfid, nZId, NCDF_FillValue);
    1745             : 
    1746             :         NCDF_ERR(status);
    1747             : 
    1748             :         // Update status if write worked.
    1749             :         if (status == NC_NOERR)
    1750             :         {
    1751             :             dfNoDataValue = 0.0;
    1752             :             bNoDataSet = false;
    1753             :             return CE_None;
    1754             :         }
    1755             : 
    1756             :         return CE_Failure;
    1757             :     }
    1758             : 
    1759             :     dfNoDataValue = 0.0;
    1760             :     bNoDataSet = false;
    1761             :     return CE_None;
    1762             : }
    1763             : #endif
    1764             : 
    1765             : /************************************************************************/
    1766             : /*                           SerializeToXML()                           */
    1767             : /************************************************************************/
    1768             : 
    1769           4 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
    1770             : {
    1771             :     // Overridden from GDALPamDataset to add only band histogram
    1772             :     // and statistics. See bug #4244.
    1773           4 :     if (psPam == nullptr)
    1774           0 :         return nullptr;
    1775             : 
    1776             :     // Setup root node and attributes.
    1777             :     CPLXMLNode *psTree =
    1778           4 :         CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
    1779             : 
    1780           4 :     if (GetBand() > 0)
    1781             :     {
    1782           8 :         CPLString oFmt;
    1783           4 :         CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
    1784             :     }
    1785             : 
    1786             :     // Histograms.
    1787           4 :     if (psPam->psSavedHistograms != nullptr)
    1788           1 :         CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
    1789             : 
    1790             :     // Metadata (statistics only).
    1791           4 :     GDALMultiDomainMetadata oMDMDStats;
    1792           4 :     const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
    1793             :                                   "STATISTICS_MEAN", "STATISTICS_STDDEV",
    1794             :                                   nullptr};
    1795          20 :     for (int i = 0; i < CSLCount(papszMDStats); i++)
    1796             :     {
    1797          16 :         const char *pszMDI = GetMetadataItem(papszMDStats[i]);
    1798          16 :         if (pszMDI)
    1799           4 :             oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
    1800             :     }
    1801           4 :     CPLXMLNode *psMD = oMDMDStats.Serialize();
    1802             : 
    1803           4 :     if (psMD != nullptr)
    1804             :     {
    1805           1 :         if (psMD->psChild == nullptr)
    1806           0 :             CPLDestroyXMLNode(psMD);
    1807             :         else
    1808           1 :             CPLAddXMLChild(psTree, psMD);
    1809             :     }
    1810             : 
    1811             :     // We don't want to return anything if we had no metadata to attach.
    1812           4 :     if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
    1813             :     {
    1814           2 :         CPLDestroyXMLNode(psTree);
    1815           2 :         psTree = nullptr;
    1816             :     }
    1817             : 
    1818           4 :     return psTree;
    1819             : }
    1820             : 
    1821             : /************************************************************************/
    1822             : /*               Get1DVariableIndexedByDimension()                      */
    1823             : /************************************************************************/
    1824             : 
    1825          73 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
    1826             :                                            const char *pszDimName,
    1827             :                                            bool bVerboseError, int *pnGroupID)
    1828             : {
    1829          73 :     *pnGroupID = -1;
    1830          73 :     int nVarID = -1;
    1831             :     // First try to find a variable whose name is identical to the dimension
    1832             :     // name, and check that it is indeed indexed by this dimension
    1833          73 :     if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
    1834             :     {
    1835          61 :         int nDimCountOfVariable = 0;
    1836          61 :         nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
    1837          61 :         if (nDimCountOfVariable == 1)
    1838             :         {
    1839          61 :             int nDimIdOfVariable = -1;
    1840          61 :             nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
    1841          61 :             if (nDimIdOfVariable == nDimId)
    1842             :             {
    1843          61 :                 return nVarID;
    1844             :             }
    1845             :         }
    1846             :     }
    1847             : 
    1848             :     // Otherwise iterate over the variables to find potential candidates
    1849             :     // TODO: should be modified to search also in other groups using the same
    1850             :     //       logic than in NCDFResolveVar(), but maybe not needed if it's a
    1851             :     //       very rare case? and I think this is not CF compliant.
    1852          12 :     int nvars = 0;
    1853          12 :     CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
    1854             : 
    1855          12 :     int nCountCandidateVars = 0;
    1856          12 :     int nCandidateVarID = -1;
    1857          53 :     for (int k = 0; k < nvars; k++)
    1858             :     {
    1859          41 :         int nDimCountOfVariable = 0;
    1860          41 :         nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
    1861          41 :         if (nDimCountOfVariable == 1)
    1862             :         {
    1863          23 :             int nDimIdOfVariable = -1;
    1864          23 :             nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
    1865          23 :             if (nDimIdOfVariable == nDimId)
    1866             :             {
    1867           5 :                 nCountCandidateVars++;
    1868           5 :                 nCandidateVarID = k;
    1869             :             }
    1870             :         }
    1871             :     }
    1872          12 :     if (nCountCandidateVars > 1)
    1873             :     {
    1874           1 :         if (bVerboseError)
    1875             :         {
    1876           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1877             :                      "Several 1D variables are indexed by dimension %s",
    1878             :                      pszDimName);
    1879             :         }
    1880           1 :         *pnGroupID = -1;
    1881           1 :         return -1;
    1882             :     }
    1883          11 :     else if (nCandidateVarID < 0)
    1884             :     {
    1885           8 :         if (bVerboseError)
    1886             :         {
    1887           8 :             CPLError(CE_Warning, CPLE_AppDefined,
    1888             :                      "No 1D variable is indexed by dimension %s", pszDimName);
    1889             :         }
    1890             :     }
    1891          11 :     *pnGroupID = cdfid;
    1892          11 :     return nCandidateVarID;
    1893             : }
    1894             : 
    1895             : /************************************************************************/
    1896             : /*                      CreateMetadataFromAttributes()                  */
    1897             : /************************************************************************/
    1898             : 
    1899         457 : void netCDFRasterBand::CreateMetadataFromAttributes()
    1900             : {
    1901         457 :     char szVarName[NC_MAX_NAME + 1] = {};
    1902         457 :     int status = nc_inq_varname(cdfid, nZId, szVarName);
    1903         457 :     NCDF_ERR(status);
    1904             : 
    1905         457 :     GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
    1906             : 
    1907             :     // Get attribute metadata.
    1908         457 :     int nAtt = 0;
    1909         457 :     NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
    1910             : 
    1911        1911 :     for (int i = 0; i < nAtt; i++)
    1912             :     {
    1913        1454 :         char szMetaName[NC_MAX_NAME + 1] = {};
    1914        1454 :         status = nc_inq_attname(cdfid, nZId, i, szMetaName);
    1915        1454 :         if (status != NC_NOERR)
    1916          12 :             continue;
    1917             : 
    1918        1454 :         if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
    1919             :         {
    1920          12 :             continue;
    1921             :         }
    1922             : 
    1923        1442 :         char *pszMetaValue = nullptr;
    1924        1442 :         if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
    1925             :         {
    1926        1442 :             GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
    1927             :         }
    1928             :         else
    1929             :         {
    1930           0 :             CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
    1931             :         }
    1932             : 
    1933        1442 :         if (pszMetaValue)
    1934             :         {
    1935        1442 :             CPLFree(pszMetaValue);
    1936        1442 :             pszMetaValue = nullptr;
    1937             :         }
    1938             :     }
    1939         457 : }
    1940             : 
    1941             : /************************************************************************/
    1942             : /*                      CreateMetadataFromOtherVars()                   */
    1943             : /************************************************************************/
    1944             : 
    1945          48 : void netCDFRasterBand::CreateMetadataFromOtherVars()
    1946             : 
    1947             : {
    1948          48 :     CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
    1949          48 :     m_bCreateMetadataFromOtherVarsDone = true;
    1950             : 
    1951          48 :     netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
    1952          48 :     const int nPamFlagsBackup = l_poDS->nPamFlags;
    1953             : 
    1954             :     // Compute all dimensions from Band number and save in Metadata.
    1955          48 :     int nd = 0;
    1956          48 :     nc_inq_varndims(cdfid, nZId, &nd);
    1957             :     // Compute multidimention band position.
    1958             :     //
    1959             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    1960             :     // if Data[2,3,4,x,y]
    1961             :     //
    1962             :     //  BandPos0 = (nBand) / (3*4)
    1963             :     //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)
    1964             :     //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)
    1965             : 
    1966          48 :     int Sum = 1;
    1967          48 :     if (nd == 3)
    1968             :     {
    1969           5 :         Sum *= panBandZLev[0];
    1970             :     }
    1971             : 
    1972             :     // Loop over non-spatial dimensions.
    1973          48 :     int Taken = 0;
    1974             : 
    1975          88 :     for (int i = 0; i < nd - 2; i++)
    1976             :     {
    1977             :         int result;
    1978          40 :         if (i != nd - 2 - 1)
    1979             :         {
    1980          18 :             Sum = 1;
    1981          37 :             for (int j = i + 1; j < nd - 2; j++)
    1982             :             {
    1983          19 :                 Sum *= panBandZLev[j];
    1984             :             }
    1985          18 :             result = static_cast<int>((nLevel - Taken) / Sum);
    1986             :         }
    1987             :         else
    1988             :         {
    1989          22 :             result = static_cast<int>((nLevel - Taken) % Sum);
    1990             :         }
    1991             : 
    1992          40 :         char szName[NC_MAX_NAME + 1] = {};
    1993          40 :         snprintf(szName, sizeof(szName), "%s",
    1994          40 :                  l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
    1995             : 
    1996             :         char szMetaName[NC_MAX_NAME + 1 + 32];
    1997          40 :         snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
    1998             : 
    1999          40 :         const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
    2000          40 :         const int nVarID = l_poDS->m_anExtraDimVarIds[i];
    2001          40 :         if (nVarID < 0)
    2002             :         {
    2003           2 :             GDALPamRasterBand::SetMetadataItem(szMetaName,
    2004             :                                                CPLSPrintf("%d", result + 1));
    2005             :         }
    2006             :         else
    2007             :         {
    2008             :             // TODO: Make sure all the status checks make sense.
    2009             : 
    2010          38 :             nc_type nVarType = NC_NAT;
    2011          38 :             /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
    2012             : 
    2013          38 :             int nDims = 0;
    2014          38 :             /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
    2015             : 
    2016          38 :             char szMetaTemp[256] = {};
    2017          38 :             if (nDims == 1)
    2018             :             {
    2019          38 :                 size_t count[1] = {1};
    2020          38 :                 size_t start[1] = {static_cast<size_t>(result)};
    2021             : 
    2022          38 :                 switch (nVarType)
    2023             :                 {
    2024           0 :                     case NC_BYTE:
    2025             :                         // TODO: Check for signed/unsigned byte.
    2026             :                         signed char cData;
    2027           0 :                         /* status = */ nc_get_vara_schar(nGroupID, nVarID,
    2028             :                                                          start, count, &cData);
    2029           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
    2030           0 :                         break;
    2031           0 :                     case NC_SHORT:
    2032             :                         short sData;
    2033           0 :                         /* status = */ nc_get_vara_short(nGroupID, nVarID,
    2034             :                                                          start, count, &sData);
    2035           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
    2036           0 :                         break;
    2037          19 :                     case NC_INT:
    2038             :                     {
    2039             :                         int nData;
    2040          19 :                         /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
    2041             :                                                        count, &nData);
    2042          19 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
    2043          19 :                         break;
    2044             :                     }
    2045           0 :                     case NC_FLOAT:
    2046             :                         float fData;
    2047           0 :                         /* status = */ nc_get_vara_float(nGroupID, nVarID,
    2048             :                                                          start, count, &fData);
    2049           0 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
    2050             :                                     fData);
    2051           0 :                         break;
    2052          18 :                     case NC_DOUBLE:
    2053             :                         double dfData;
    2054          18 :                         /* status = */ nc_get_vara_double(
    2055             :                             nGroupID, nVarID, start, count, &dfData);
    2056          18 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
    2057             :                                     dfData);
    2058          18 :                         break;
    2059           0 :                     case NC_UBYTE:
    2060             :                         unsigned char ucData;
    2061           0 :                         /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
    2062             :                                                          start, count, &ucData);
    2063           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
    2064           0 :                         break;
    2065           0 :                     case NC_USHORT:
    2066             :                         unsigned short usData;
    2067           0 :                         /* status = */ nc_get_vara_ushort(
    2068             :                             nGroupID, nVarID, start, count, &usData);
    2069           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
    2070           0 :                         break;
    2071           0 :                     case NC_UINT:
    2072             :                     {
    2073             :                         unsigned int unData;
    2074           0 :                         /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
    2075             :                                                         count, &unData);
    2076           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
    2077           0 :                         break;
    2078             :                     }
    2079           1 :                     case NC_INT64:
    2080             :                     {
    2081             :                         long long nData;
    2082           1 :                         /* status = */ nc_get_vara_longlong(
    2083             :                             nGroupID, nVarID, start, count, &nData);
    2084           1 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
    2085             :                                  nData);
    2086           1 :                         break;
    2087             :                     }
    2088           0 :                     case NC_UINT64:
    2089             :                     {
    2090             :                         unsigned long long unData;
    2091           0 :                         /* status = */ nc_get_vara_ulonglong(
    2092             :                             nGroupID, nVarID, start, count, &unData);
    2093           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
    2094             :                                  unData);
    2095           0 :                         break;
    2096             :                     }
    2097           0 :                     default:
    2098           0 :                         CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
    2099             :                                  szMetaTemp, nVarType);
    2100           0 :                         break;
    2101             :                 }
    2102             :             }
    2103             :             else
    2104             :             {
    2105           0 :                 snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
    2106             :             }
    2107             : 
    2108             :             // Save dimension value.
    2109             :             // NOTE: removed #original_units as not part of CF-1.
    2110             : 
    2111          38 :             GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
    2112             :         }
    2113             : 
    2114             :         // Avoid int32 overflow. Perhaps something more sensible to do here ?
    2115          40 :         if (result > 0 && Sum > INT_MAX / result)
    2116           0 :             break;
    2117          40 :         if (Taken > INT_MAX - result * Sum)
    2118           0 :             break;
    2119             : 
    2120          40 :         Taken += result * Sum;
    2121             :     }  // End loop non-spatial dimensions.
    2122             : 
    2123          48 :     l_poDS->nPamFlags = nPamFlagsBackup;
    2124          48 : }
    2125             : 
    2126             : /************************************************************************/
    2127             : /*                             CheckData()                              */
    2128             : /************************************************************************/
    2129             : template <class T>
    2130        5730 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
    2131             :                                  size_t nTmpBlockXSize, size_t nTmpBlockYSize,
    2132             :                                  bool bCheckIsNan)
    2133             : {
    2134        5730 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2135             : 
    2136             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2137             :     // the data this is because partial blocks are not arranged the same way in
    2138             :     // netcdf and gdal.
    2139        5730 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2140             :     {
    2141           6 :         T *ptrWrite = static_cast<T *>(pImage);
    2142           6 :         T *ptrRead = static_cast<T *>(pImageNC);
    2143          29 :         for (size_t j = 0; j < nTmpBlockYSize;
    2144          23 :              j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
    2145             :         {
    2146          23 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
    2147             :         }
    2148             :     }
    2149             : 
    2150             :     // Is valid data checking needed or requested?
    2151        5730 :     if (bValidRangeValid || bCheckIsNan)
    2152             :     {
    2153        1265 :         T *ptrImage = static_cast<T *>(pImage);
    2154        2584 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2155             :         {
    2156             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2157        1319 :             size_t k = j * nBlockXSize;
    2158       96938 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2159             :             {
    2160             :                 // Check for nodata and nan.
    2161       95619 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2162        6301 :                     continue;
    2163       89318 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2164             :                 {
    2165        5737 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2166        5737 :                     continue;
    2167             :                 }
    2168             :                 // Check for valid_range.
    2169       83581 :                 if (bValidRangeValid)
    2170             :                 {
    2171       40986 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2172       40986 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2173       40983 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2174       40983 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2175             :                     {
    2176           4 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2177             :                     }
    2178             :                 }
    2179             :             }
    2180             :         }
    2181             :     }
    2182             : 
    2183             :     // If minimum longitude is > 180, subtract 360 from all.
    2184             :     // If not, disable checking for further calls (check just once).
    2185             :     // Only check first and last block elements since lon must be monotonic.
    2186        5730 :     const bool bIsSigned = std::numeric_limits<T>::is_signed;
    2187        5419 :     if (bCheckLongitude && bIsSigned &&
    2188           9 :         !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
    2189           8 :         !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
    2190        2714 :                     m_dfNoDataValue) &&
    2191           8 :         std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
    2192             :     {
    2193           0 :         T *ptrImage = static_cast<T *>(pImage);
    2194           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2195             :         {
    2196           0 :             size_t k = j * nBlockXSize;
    2197           0 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2198             :             {
    2199           0 :                 if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2200           0 :                     ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
    2201             :             }
    2202             :         }
    2203             :     }
    2204             :     else
    2205             :     {
    2206        5730 :         bCheckLongitude = false;
    2207             :     }
    2208        5730 : }
    2209             : 
    2210             : /************************************************************************/
    2211             : /*                             CheckDataCpx()                              */
    2212             : /************************************************************************/
    2213             : template <class T>
    2214          25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
    2215             :                                     size_t nTmpBlockXSize,
    2216             :                                     size_t nTmpBlockYSize, bool bCheckIsNan)
    2217             : {
    2218          25 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2219             : 
    2220             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2221             :     // the data this is because partial blocks are not arranged the same way in
    2222             :     // netcdf and gdal.
    2223          25 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2224             :     {
    2225           0 :         T *ptrWrite = static_cast<T *>(pImage);
    2226           0 :         T *ptrRead = static_cast<T *>(pImageNC);
    2227           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++,
    2228           0 :                     ptrWrite += (2 * nBlockXSize),
    2229           0 :                     ptrRead += (2 * nTmpBlockXSize))
    2230             :         {
    2231           0 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
    2232             :         }
    2233             :     }
    2234             : 
    2235             :     // Is valid data checking needed or requested?
    2236          25 :     if (bValidRangeValid || bCheckIsNan)
    2237             :     {
    2238           0 :         T *ptrImage = static_cast<T *>(pImage);
    2239           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2240             :         {
    2241             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2242           0 :             size_t k = 2 * j * nBlockXSize;
    2243           0 :             for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
    2244             :             {
    2245             :                 // Check for nodata and nan.
    2246           0 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2247           0 :                     continue;
    2248           0 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2249             :                 {
    2250           0 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2251           0 :                     continue;
    2252             :                 }
    2253             :                 // Check for valid_range.
    2254           0 :                 if (bValidRangeValid)
    2255             :                 {
    2256           0 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2257           0 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2258           0 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2259           0 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2260             :                     {
    2261           0 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2262             :                     }
    2263             :                 }
    2264             :             }
    2265             :         }
    2266             :     }
    2267          25 : }
    2268             : 
    2269             : /************************************************************************/
    2270             : /*                         FetchNetcdfChunk()                           */
    2271             : /************************************************************************/
    2272             : 
    2273        5755 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
    2274             :                                         void *pImage)
    2275             : {
    2276        5755 :     size_t start[MAX_NC_DIMS] = {};
    2277        5755 :     size_t edge[MAX_NC_DIMS] = {};
    2278             : 
    2279        5755 :     start[nBandXPos] = xstart;
    2280        5755 :     edge[nBandXPos] = nBlockXSize;
    2281        5755 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2282           6 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2283        5755 :     if (nBandYPos >= 0)
    2284             :     {
    2285        5751 :         start[nBandYPos] = ystart;
    2286        5751 :         edge[nBandYPos] = nBlockYSize;
    2287        5751 :         if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2288           4 :             edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2289             :     }
    2290        5755 :     const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
    2291             : 
    2292             : #ifdef NCDF_DEBUG
    2293             :     CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
    2294             :              start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
    2295             :              edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
    2296             : #endif
    2297             : 
    2298        5755 :     int nd = 0;
    2299        5755 :     nc_inq_varndims(cdfid, nZId, &nd);
    2300        5755 :     if (nd == 3)
    2301             :     {
    2302        1078 :         start[panBandZPos[0]] = nLevel;  // z
    2303        1078 :         edge[panBandZPos[0]] = 1;
    2304             :     }
    2305             : 
    2306             :     // Compute multidimention band position.
    2307             :     //
    2308             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2309             :     // if Data[2,3,4,x,y]
    2310             :     //
    2311             :     //  BandPos0 = (nBand) / (3*4)
    2312             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2313             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2314        5755 :     if (nd > 3)
    2315             :     {
    2316         160 :         int Sum = -1;
    2317         160 :         int Taken = 0;
    2318         480 :         for (int i = 0; i < nd - 2; i++)
    2319             :         {
    2320         320 :             if (i != nd - 2 - 1)
    2321             :             {
    2322         160 :                 Sum = 1;
    2323         320 :                 for (int j = i + 1; j < nd - 2; j++)
    2324             :                 {
    2325         160 :                     Sum *= panBandZLev[j];
    2326             :                 }
    2327         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2328         160 :                 edge[panBandZPos[i]] = 1;
    2329             :             }
    2330             :             else
    2331             :             {
    2332         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2333         160 :                 edge[panBandZPos[i]] = 1;
    2334             :             }
    2335         320 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2336             :         }
    2337             :     }
    2338             : 
    2339             :     // Make sure we are in data mode.
    2340        5755 :     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2341             : 
    2342             :     // If this block is not a full block in the x axis, we need to
    2343             :     // re-arrange the data because partial blocks are not arranged the
    2344             :     // same way in netcdf and gdal, so we first we read the netcdf data at
    2345             :     // the end of the gdal block buffer then re-arrange rows in CheckData().
    2346        5755 :     void *pImageNC = pImage;
    2347        5755 :     if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
    2348             :     {
    2349           6 :         pImageNC = static_cast<GByte *>(pImage) +
    2350           6 :                    ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
    2351          12 :                      edge[nBandXPos] * nYChunkSize) *
    2352           6 :                     (GDALGetDataTypeSize(eDataType) / 8));
    2353             :     }
    2354             : 
    2355             :     // Read data according to type.
    2356             :     int status;
    2357        5755 :     if (eDataType == GDT_Byte)
    2358             :     {
    2359        3003 :         if (bSignedData)
    2360             :         {
    2361           0 :             status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2362             :                                        static_cast<signed char *>(pImageNC));
    2363           0 :             if (status == NC_NOERR)
    2364           0 :                 CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2365             :                                        nYChunkSize, false);
    2366             :         }
    2367             :         else
    2368             :         {
    2369        3003 :             status = nc_get_vara_uchar(cdfid, nZId, start, edge,
    2370             :                                        static_cast<unsigned char *>(pImageNC));
    2371        3003 :             if (status == NC_NOERR)
    2372        3003 :                 CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
    2373             :                                          nYChunkSize, false);
    2374             :         }
    2375             :     }
    2376        2752 :     else if (eDataType == GDT_Int8)
    2377             :     {
    2378          60 :         status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2379             :                                    static_cast<signed char *>(pImageNC));
    2380          60 :         if (status == NC_NOERR)
    2381          60 :             CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2382             :                                    nYChunkSize, false);
    2383             :     }
    2384        2692 :     else if (nc_datatype == NC_SHORT)
    2385             :     {
    2386         465 :         status = nc_get_vara_short(cdfid, nZId, start, edge,
    2387             :                                    static_cast<short *>(pImageNC));
    2388         465 :         if (status == NC_NOERR)
    2389             :         {
    2390         465 :             if (eDataType == GDT_Int16)
    2391             :             {
    2392         462 :                 CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
    2393             :                                   nYChunkSize, false);
    2394             :             }
    2395             :             else
    2396             :             {
    2397           3 :                 CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
    2398             :                                    nYChunkSize, false);
    2399             :             }
    2400             :         }
    2401             :     }
    2402        2227 :     else if (eDataType == GDT_Int32)
    2403             :     {
    2404             : #if SIZEOF_UNSIGNED_LONG == 4
    2405             :         status = nc_get_vara_long(cdfid, nZId, start, edge,
    2406             :                                   static_cast<long *>(pImageNC));
    2407             :         if (status == NC_NOERR)
    2408             :             CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2409             :                             false);
    2410             : #else
    2411         912 :         status = nc_get_vara_int(cdfid, nZId, start, edge,
    2412             :                                  static_cast<int *>(pImageNC));
    2413         912 :         if (status == NC_NOERR)
    2414         912 :             CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2415             :                            false);
    2416             : #endif
    2417             :     }
    2418        1315 :     else if (eDataType == GDT_Float32)
    2419             :     {
    2420        1178 :         status = nc_get_vara_float(cdfid, nZId, start, edge,
    2421             :                                    static_cast<float *>(pImageNC));
    2422        1178 :         if (status == NC_NOERR)
    2423        1178 :             CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2424             :                              true);
    2425             :     }
    2426         137 :     else if (eDataType == GDT_Float64)
    2427             :     {
    2428          86 :         status = nc_get_vara_double(cdfid, nZId, start, edge,
    2429             :                                     static_cast<double *>(pImageNC));
    2430          86 :         if (status == NC_NOERR)
    2431          86 :             CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2432             :                               true);
    2433             :     }
    2434          51 :     else if (eDataType == GDT_UInt16)
    2435             :     {
    2436           6 :         status = nc_get_vara_ushort(cdfid, nZId, start, edge,
    2437             :                                     static_cast<unsigned short *>(pImageNC));
    2438           6 :         if (status == NC_NOERR)
    2439           6 :             CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
    2440             :                                       nYChunkSize, false);
    2441             :     }
    2442          45 :     else if (eDataType == GDT_UInt32)
    2443             :     {
    2444           6 :         status = nc_get_vara_uint(cdfid, nZId, start, edge,
    2445             :                                   static_cast<unsigned int *>(pImageNC));
    2446           6 :         if (status == NC_NOERR)
    2447           6 :             CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
    2448             :                                     nYChunkSize, false);
    2449             :     }
    2450          39 :     else if (eDataType == GDT_Int64)
    2451             :     {
    2452           7 :         status = nc_get_vara_longlong(cdfid, nZId, start, edge,
    2453             :                                       static_cast<long long *>(pImageNC));
    2454           7 :         if (status == NC_NOERR)
    2455           7 :             CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
    2456             :                                     nYChunkSize, false);
    2457             :     }
    2458          32 :     else if (eDataType == GDT_UInt64)
    2459             :     {
    2460             :         status =
    2461           7 :             nc_get_vara_ulonglong(cdfid, nZId, start, edge,
    2462             :                                   static_cast<unsigned long long *>(pImageNC));
    2463           7 :         if (status == NC_NOERR)
    2464           7 :             CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
    2465             :                                      nYChunkSize, false);
    2466             :     }
    2467          25 :     else if (eDataType == GDT_CInt16)
    2468             :     {
    2469           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2470           0 :         if (status == NC_NOERR)
    2471           0 :             CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2472             :                                 false);
    2473             :     }
    2474          25 :     else if (eDataType == GDT_CInt32)
    2475             :     {
    2476           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2477           0 :         if (status == NC_NOERR)
    2478           0 :             CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2479             :                               false);
    2480             :     }
    2481          25 :     else if (eDataType == GDT_CFloat32)
    2482             :     {
    2483          20 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2484          20 :         if (status == NC_NOERR)
    2485          20 :             CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2486             :                                 false);
    2487             :     }
    2488           5 :     else if (eDataType == GDT_CFloat64)
    2489             :     {
    2490           5 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2491           5 :         if (status == NC_NOERR)
    2492           5 :             CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2493             :                                  false);
    2494             :     }
    2495             : 
    2496             :     else
    2497           0 :         status = NC_EBADTYPE;
    2498             : 
    2499        5755 :     if (status != NC_NOERR)
    2500             :     {
    2501           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2502             :                  "netCDF chunk fetch failed: #%d (%s)", status,
    2503             :                  nc_strerror(status));
    2504           0 :         return false;
    2505             :     }
    2506        5755 :     return true;
    2507             : }
    2508             : 
    2509             : /************************************************************************/
    2510             : /*                             IReadBlock()                             */
    2511             : /************************************************************************/
    2512             : 
    2513        5755 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2514             :                                     void *pImage)
    2515             : 
    2516             : {
    2517       11510 :     CPLMutexHolderD(&hNCMutex);
    2518             : 
    2519             :     // Locate X, Y and Z position in the array.
    2520             : 
    2521        5755 :     size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2522        5755 :     size_t ystart = 0;
    2523             : 
    2524             :     // Check y order.
    2525        5755 :     if (nBandYPos >= 0)
    2526             :     {
    2527        5751 :         auto poGDS = static_cast<netCDFDataset *>(poDS);
    2528        5751 :         if (poGDS->bBottomUp)
    2529             :         {
    2530        4836 :             if (nBlockYSize == 1)
    2531             :             {
    2532        4823 :                 ystart = nRasterYSize - 1 - nBlockYOff;
    2533             :             }
    2534             :             else
    2535             :             {
    2536             :                 // in GDAL space
    2537          13 :                 ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2538             :                 const size_t yend =
    2539          26 :                     std::min(ystart + nBlockYSize - 1,
    2540          13 :                              static_cast<size_t>(nRasterYSize - 1));
    2541             :                 // in netCDF space
    2542          13 :                 const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
    2543          13 :                 const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
    2544          13 :                 const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
    2545          13 :                 const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
    2546             : 
    2547             :                 const auto firstKey = netCDFDataset::ChunkKey(
    2548          13 :                     nBlockXOff, nFirstChunkBlock, nBand);
    2549             :                 const auto secondKey =
    2550          13 :                     netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
    2551             : 
    2552             :                 // Retrieve data from the one or 2 needed netCDF chunks
    2553          13 :                 std::shared_ptr<std::vector<GByte>> firstChunk;
    2554          13 :                 std::shared_ptr<std::vector<GByte>> secondChunk;
    2555          13 :                 if (poGDS->poChunkCache)
    2556             :                 {
    2557          13 :                     poGDS->poChunkCache->tryGet(firstKey, firstChunk);
    2558          13 :                     if (firstKey != secondKey)
    2559           6 :                         poGDS->poChunkCache->tryGet(secondKey, secondChunk);
    2560             :                 }
    2561             :                 const size_t nChunkLineSize =
    2562          13 :                     static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
    2563          13 :                     nBlockXSize;
    2564          13 :                 const size_t nChunkSize = nChunkLineSize * nBlockYSize;
    2565          13 :                 if (!firstChunk)
    2566             :                 {
    2567          11 :                     firstChunk.reset(new std::vector<GByte>(nChunkSize));
    2568          11 :                     if (!FetchNetcdfChunk(xstart,
    2569          11 :                                           nFirstChunkBlock * nBlockYSize,
    2570          11 :                                           firstChunk.get()->data()))
    2571           0 :                         return CE_Failure;
    2572          11 :                     if (poGDS->poChunkCache)
    2573          11 :                         poGDS->poChunkCache->insert(firstKey, firstChunk);
    2574             :                 }
    2575          13 :                 if (!secondChunk && firstKey != secondKey)
    2576             :                 {
    2577           2 :                     secondChunk.reset(new std::vector<GByte>(nChunkSize));
    2578           2 :                     if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
    2579           2 :                                           secondChunk.get()->data()))
    2580           0 :                         return CE_Failure;
    2581           2 :                     if (poGDS->poChunkCache)
    2582           2 :                         poGDS->poChunkCache->insert(secondKey, secondChunk);
    2583             :                 }
    2584             : 
    2585             :                 // Assemble netCDF chunks into GDAL block
    2586          13 :                 GByte *pabyImage = static_cast<GByte *>(pImage);
    2587          13 :                 const size_t nFirstChunkBlockLine =
    2588          13 :                     nFirstChunkBlock * nBlockYSize;
    2589          13 :                 const size_t nLastChunkBlockLine =
    2590          13 :                     nLastChunkBlock * nBlockYSize;
    2591         146 :                 for (size_t iLine = ystart; iLine <= yend; iLine++)
    2592             :                 {
    2593         133 :                     const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
    2594         133 :                     const size_t nChunkY = nLineFromBottom / nBlockYSize;
    2595         133 :                     if (nChunkY == nFirstChunkBlock)
    2596             :                     {
    2597         121 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2598         121 :                                firstChunk.get()->data() +
    2599         121 :                                    (nLineFromBottom - nFirstChunkBlockLine) *
    2600             :                                        nChunkLineSize,
    2601             :                                nChunkLineSize);
    2602             :                     }
    2603             :                     else
    2604             :                     {
    2605          12 :                         CPLAssert(nChunkY == nLastChunkBlock);
    2606          12 :                         assert(secondChunk);
    2607          12 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2608          12 :                                secondChunk.get()->data() +
    2609          12 :                                    (nLineFromBottom - nLastChunkBlockLine) *
    2610             :                                        nChunkLineSize,
    2611             :                                nChunkLineSize);
    2612             :                     }
    2613             :                 }
    2614          13 :                 return CE_None;
    2615             :             }
    2616             :         }
    2617             :         else
    2618             :         {
    2619         915 :             ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2620             :         }
    2621             :     }
    2622             : 
    2623        5742 :     return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
    2624             : }
    2625             : 
    2626             : /************************************************************************/
    2627             : /*                             IWriteBlock()                            */
    2628             : /************************************************************************/
    2629             : 
    2630        2652 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
    2631             :                                      void *pImage)
    2632             : {
    2633        5304 :     CPLMutexHolderD(&hNCMutex);
    2634             : 
    2635             : #ifdef NCDF_DEBUG
    2636             :     if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
    2637             :         CPLDebug("GDAL_netCDF",
    2638             :                  "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
    2639             :                  nBlockXOff, nBlockYOff, nBand);
    2640             : #endif
    2641             : 
    2642        2652 :     int nd = 0;
    2643        2652 :     nc_inq_varndims(cdfid, nZId, &nd);
    2644             : 
    2645             :     // Locate X, Y and Z position in the array.
    2646             : 
    2647             :     size_t start[MAX_NC_DIMS];
    2648        2652 :     memset(start, 0, sizeof(start));
    2649        2652 :     start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2650             : 
    2651             :     // check y order.
    2652        2652 :     if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
    2653             :     {
    2654        2628 :         if (nBlockYSize == 1)
    2655             :         {
    2656        2628 :             start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
    2657             :         }
    2658             :         else
    2659             :         {
    2660           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2661             :                      "nBlockYSize = %d, only 1 supported when "
    2662             :                      "writing bottom-up dataset",
    2663             :                      nBlockYSize);
    2664           0 :             return CE_Failure;
    2665             :         }
    2666             :     }
    2667             :     else
    2668             :     {
    2669          24 :         start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y
    2670             :     }
    2671             : 
    2672        2652 :     size_t edge[MAX_NC_DIMS] = {};
    2673             : 
    2674        2652 :     edge[nBandXPos] = nBlockXSize;
    2675        2652 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2676           0 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2677        2652 :     edge[nBandYPos] = nBlockYSize;
    2678        2652 :     if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2679           0 :         edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2680             : 
    2681        2652 :     if (nd == 3)
    2682             :     {
    2683         610 :         start[panBandZPos[0]] = nLevel;  // z
    2684         610 :         edge[panBandZPos[0]] = 1;
    2685             :     }
    2686             : 
    2687             :     // Compute multidimention band position.
    2688             :     //
    2689             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2690             :     // if Data[2,3,4,x,y]
    2691             :     //
    2692             :     //  BandPos0 = (nBand) / (3*4)
    2693             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2694             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2695        2652 :     if (nd > 3)
    2696             :     {
    2697         178 :         int Sum = -1;
    2698         178 :         int Taken = 0;
    2699         534 :         for (int i = 0; i < nd - 2; i++)
    2700             :         {
    2701         356 :             if (i != nd - 2 - 1)
    2702             :             {
    2703         178 :                 Sum = 1;
    2704         356 :                 for (int j = i + 1; j < nd - 2; j++)
    2705             :                 {
    2706         178 :                     Sum *= panBandZLev[j];
    2707             :                 }
    2708         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2709         178 :                 edge[panBandZPos[i]] = 1;
    2710             :             }
    2711             :             else
    2712             :             {
    2713         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2714         178 :                 edge[panBandZPos[i]] = 1;
    2715             :             }
    2716         356 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2717             :         }
    2718             :     }
    2719             : 
    2720             :     // Make sure we are in data mode.
    2721        2652 :     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2722             : 
    2723             :     // Copy data according to type.
    2724        2652 :     int status = 0;
    2725        2652 :     if (eDataType == GDT_Byte)
    2726             :     {
    2727        2093 :         if (bSignedData)
    2728           0 :             status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2729             :                                        static_cast<signed char *>(pImage));
    2730             :         else
    2731        2093 :             status = nc_put_vara_uchar(cdfid, nZId, start, edge,
    2732             :                                        static_cast<unsigned char *>(pImage));
    2733             :     }
    2734         559 :     else if (eDataType == GDT_Int8)
    2735             :     {
    2736          40 :         status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2737             :                                    static_cast<signed char *>(pImage));
    2738             :     }
    2739         519 :     else if (nc_datatype == NC_SHORT)
    2740             :     {
    2741         101 :         status = nc_put_vara_short(cdfid, nZId, start, edge,
    2742             :                                    static_cast<short *>(pImage));
    2743             :     }
    2744         418 :     else if (eDataType == GDT_Int32)
    2745             :     {
    2746         210 :         status = nc_put_vara_int(cdfid, nZId, start, edge,
    2747             :                                  static_cast<int *>(pImage));
    2748             :     }
    2749         208 :     else if (eDataType == GDT_Float32)
    2750             :     {
    2751         128 :         status = nc_put_vara_float(cdfid, nZId, start, edge,
    2752             :                                    static_cast<float *>(pImage));
    2753             :     }
    2754          80 :     else if (eDataType == GDT_Float64)
    2755             :     {
    2756          50 :         status = nc_put_vara_double(cdfid, nZId, start, edge,
    2757             :                                     static_cast<double *>(pImage));
    2758             :     }
    2759          30 :     else if (eDataType == GDT_UInt16 &&
    2760          12 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2761             :     {
    2762          12 :         status = nc_put_vara_ushort(cdfid, nZId, start, edge,
    2763             :                                     static_cast<unsigned short *>(pImage));
    2764             :     }
    2765          18 :     else if (eDataType == GDT_UInt32 &&
    2766          12 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2767             :     {
    2768          12 :         status = nc_put_vara_uint(cdfid, nZId, start, edge,
    2769             :                                   static_cast<unsigned int *>(pImage));
    2770             :     }
    2771           6 :     else if (eDataType == GDT_UInt64 &&
    2772           3 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2773             :     {
    2774           3 :         status =
    2775           3 :             nc_put_vara_ulonglong(cdfid, nZId, start, edge,
    2776             :                                   static_cast<unsigned long long *>(pImage));
    2777             :     }
    2778           3 :     else if (eDataType == GDT_Int64 &&
    2779           3 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2780             :     {
    2781           3 :         status = nc_put_vara_longlong(cdfid, nZId, start, edge,
    2782             :                                       static_cast<long long *>(pImage));
    2783             :     }
    2784             :     else
    2785             :     {
    2786           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2787             :                  "The NetCDF driver does not support GDAL data type %d",
    2788           0 :                  eDataType);
    2789           0 :         status = NC_EBADTYPE;
    2790             :     }
    2791        2652 :     NCDF_ERR(status);
    2792             : 
    2793        2652 :     if (status != NC_NOERR)
    2794             :     {
    2795           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2796             :                  "netCDF scanline write failed: %s", nc_strerror(status));
    2797           0 :         return CE_Failure;
    2798             :     }
    2799             : 
    2800        2652 :     return CE_None;
    2801             : }
    2802             : 
    2803             : /************************************************************************/
    2804             : /* ==================================================================== */
    2805             : /*                              netCDFDataset                           */
    2806             : /* ==================================================================== */
    2807             : /************************************************************************/
    2808             : 
    2809             : /************************************************************************/
    2810             : /*                           netCDFDataset()                            */
    2811             : /************************************************************************/
    2812             : 
    2813         966 : netCDFDataset::netCDFDataset()
    2814             :     :
    2815             : // Basic dataset vars.
    2816             : #ifdef ENABLE_NCDUMP
    2817             :       bFileToDestroyAtClosing(false),
    2818             : #endif
    2819             :       cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
    2820             :       papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
    2821             :       bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
    2822             :       pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
    2823         966 :       eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
    2824         966 :       GeometryScribe(vcdf, this->generateLogName()),
    2825         966 :       FieldScribe(vcdf, this->generateLogName()),
    2826        1932 :       bufManager(CPLGetUsablePhysicalRAM() / 5),
    2827             : 
    2828             :       // projection/GT.
    2829             :       nXDimID(-1), nYDimID(-1), bIsProjected(false),
    2830             :       bIsGeographic(false),  // Can be not projected, and also not geographic
    2831             :       // State vars.
    2832             :       bDefineMode(true), bAddedGridMappingRef(false),
    2833             : 
    2834             :       // Create vars.
    2835             :       papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
    2836             :       nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
    2837        2898 :       bSignedData(true)
    2838             : {
    2839         966 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2840             : 
    2841             :     // Projection/GT.
    2842         966 :     m_adfGeoTransform[0] = 0.0;
    2843         966 :     m_adfGeoTransform[1] = 1.0;
    2844         966 :     m_adfGeoTransform[2] = 0.0;
    2845         966 :     m_adfGeoTransform[3] = 0.0;
    2846         966 :     m_adfGeoTransform[4] = 0.0;
    2847         966 :     m_adfGeoTransform[5] = 1.0;
    2848             : 
    2849             :     // Set buffers
    2850         966 :     bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
    2851         966 :     bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
    2852         966 : }
    2853             : 
    2854             : /************************************************************************/
    2855             : /*                           ~netCDFDataset()                           */
    2856             : /************************************************************************/
    2857             : 
    2858        1871 : netCDFDataset::~netCDFDataset()
    2859             : 
    2860             : {
    2861         966 :     netCDFDataset::Close();
    2862        1871 : }
    2863             : 
    2864             : /************************************************************************/
    2865             : /*                              Close()                                 */
    2866             : /************************************************************************/
    2867             : 
    2868        1694 : CPLErr netCDFDataset::Close()
    2869             : {
    2870        1694 :     CPLErr eErr = CE_None;
    2871        1694 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    2872             :     {
    2873        1932 :         CPLMutexHolderD(&hNCMutex);
    2874             : 
    2875             : #ifdef NCDF_DEBUG
    2876             :         CPLDebug("GDAL_netCDF",
    2877             :                  "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
    2878             :                  osFilename.c_str());
    2879             : #endif
    2880             : 
    2881             :         // Write data related to geotransform
    2882        1197 :         if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
    2883         231 :             (m_bHasProjection || m_bHasGeoTransform))
    2884             :         {
    2885             :             // Ensure projection is written if GeoTransform OR Projection are
    2886             :             // missing.
    2887          37 :             if (!m_bAddedProjectionVarsDefs)
    2888             :             {
    2889           2 :                 AddProjectionVars(true, nullptr, nullptr);
    2890             :             }
    2891          37 :             AddProjectionVars(false, nullptr, nullptr);
    2892             :         }
    2893             : 
    2894         966 :         if (netCDFDataset::FlushCache(true) != CE_None)
    2895           0 :             eErr = CE_Failure;
    2896             : 
    2897         966 :         if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
    2898           0 :             eErr = CE_Failure;
    2899             : 
    2900         968 :         for (size_t i = 0; i < apoVectorDatasets.size(); i++)
    2901           2 :             delete apoVectorDatasets[i];
    2902             : 
    2903             :         // Make sure projection variable is written to band variable.
    2904         966 :         if (GetAccess() == GA_Update && !bAddedGridMappingRef)
    2905             :         {
    2906         244 :             if (!AddGridMappingRef())
    2907           0 :                 eErr = CE_Failure;
    2908             :         }
    2909             : 
    2910         966 :         CSLDestroy(papszMetadata);
    2911         966 :         CSLDestroy(papszSubDatasets);
    2912         966 :         CSLDestroy(papszCreationOptions);
    2913             : 
    2914         966 :         CPLFree(pszCFProjection);
    2915             : 
    2916         966 :         if (cdfid > 0)
    2917             :         {
    2918             : #ifdef NCDF_DEBUG
    2919             :             CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
    2920             : #endif
    2921         643 :             int status = GDAL_nc_close(cdfid);
    2922             : #ifdef ENABLE_UFFD
    2923         643 :             NETCDF_UFFD_UNMAP(pCtx);
    2924             : #endif
    2925         643 :             NCDF_ERR(status);
    2926         643 :             if (status != NC_NOERR)
    2927           0 :                 eErr = CE_Failure;
    2928             :         }
    2929             : 
    2930         966 :         if (fpVSIMEM)
    2931          15 :             VSIFCloseL(fpVSIMEM);
    2932             : 
    2933             : #ifdef ENABLE_NCDUMP
    2934         966 :         if (bFileToDestroyAtClosing)
    2935           0 :             VSIUnlink(osFilename);
    2936             : #endif
    2937             : 
    2938         966 :         if (GDALPamDataset::Close() != CE_None)
    2939           0 :             eErr = CE_Failure;
    2940             :     }
    2941        1694 :     return eErr;
    2942             : }
    2943             : 
    2944             : /************************************************************************/
    2945             : /*                            SetDefineMode()                           */
    2946             : /************************************************************************/
    2947       10452 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
    2948             : {
    2949             :     // Do nothing if already in new define mode
    2950             :     // or if dataset is in read-only mode or if dataset is true NC4 dataset.
    2951       11000 :     if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
    2952         548 :         eFormat == NCDF_FORMAT_NC4)
    2953       10051 :         return true;
    2954             : 
    2955         401 :     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
    2956         401 :              static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
    2957             : 
    2958         401 :     bDefineMode = bNewDefineMode;
    2959             : 
    2960             :     int status;
    2961         401 :     if (bDefineMode)
    2962         139 :         status = nc_redef(cdfid);
    2963             :     else
    2964         262 :         status = nc_enddef(cdfid);
    2965             : 
    2966         401 :     NCDF_ERR(status);
    2967         401 :     return status == NC_NOERR;
    2968             : }
    2969             : 
    2970             : /************************************************************************/
    2971             : /*                      GetMetadataDomainList()                         */
    2972             : /************************************************************************/
    2973             : 
    2974          27 : char **netCDFDataset::GetMetadataDomainList()
    2975             : {
    2976          27 :     char **papszDomains = BuildMetadataDomainList(
    2977             :         GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
    2978          28 :     for (const auto &kv : m_oMapDomainToJSon)
    2979           1 :         papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
    2980          27 :     return papszDomains;
    2981             : }
    2982             : 
    2983             : /************************************************************************/
    2984             : /*                            GetMetadata()                             */
    2985             : /************************************************************************/
    2986         353 : char **netCDFDataset::GetMetadata(const char *pszDomain)
    2987             : {
    2988         353 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
    2989          36 :         return papszSubDatasets;
    2990             : 
    2991         317 :     if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
    2992             :     {
    2993           1 :         auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
    2994           1 :         if (iter != m_oMapDomainToJSon.end())
    2995           1 :             return iter->second.List();
    2996             :     }
    2997             : 
    2998         316 :     return GDALDataset::GetMetadata(pszDomain);
    2999             : }
    3000             : 
    3001             : /************************************************************************/
    3002             : /*                        SetMetadataItem()                             */
    3003             : /************************************************************************/
    3004             : 
    3005          42 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    3006             :                                       const char *pszDomain)
    3007             : {
    3008          83 :     if (GetAccess() == GA_Update &&
    3009          83 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    3010             :     {
    3011          41 :         std::string osName(pszName);
    3012             : 
    3013             :         // Same logic as in CopyMetadata()
    3014          41 :         if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
    3015           8 :             osName = osName.substr(strlen("NC_GLOBAL#"));
    3016          33 :         else if (strchr(osName.c_str(), '#') == nullptr)
    3017           4 :             osName = "GDAL_" + osName;
    3018             : 
    3019          82 :         if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
    3020          41 :             strchr(osName.c_str(), '#') != nullptr)
    3021             :         {
    3022             :             // do nothing
    3023          29 :             return CE_None;
    3024             :         }
    3025             :         else
    3026             :         {
    3027          12 :             SetDefineMode(true);
    3028             : 
    3029          12 :             if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
    3030          12 :                 return CE_Failure;
    3031             :         }
    3032             :     }
    3033             : 
    3034           1 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    3035             : }
    3036             : 
    3037             : /************************************************************************/
    3038             : /*                          SetMetadata()                               */
    3039             : /************************************************************************/
    3040             : 
    3041           7 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
    3042             : {
    3043          11 :     if (GetAccess() == GA_Update &&
    3044           4 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    3045             :     {
    3046             :         // We don't handle metadata item removal for now
    3047          48 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    3048             :              ++papszIter)
    3049             :         {
    3050          41 :             char *pszName = nullptr;
    3051          41 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    3052          41 :             if (pszName && pszValue)
    3053          41 :                 SetMetadataItem(pszName, pszValue);
    3054          41 :             CPLFree(pszName);
    3055             :         }
    3056           7 :         return CE_None;
    3057             :     }
    3058           0 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    3059             : }
    3060             : 
    3061             : /************************************************************************/
    3062             : /*                          GetSpatialRef()                             */
    3063             : /************************************************************************/
    3064             : 
    3065         168 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
    3066             : {
    3067         168 :     if (m_bHasProjection)
    3068          69 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3069             : 
    3070          99 :     return GDALPamDataset::GetSpatialRef();
    3071             : }
    3072             : 
    3073             : /************************************************************************/
    3074             : /*                           FetchCopyParam()                            */
    3075             : /************************************************************************/
    3076             : 
    3077         312 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
    3078             :                                      const char *pszParam, double dfDefault,
    3079             :                                      bool *pbFound)
    3080             : 
    3081             : {
    3082             :     char *pszTemp =
    3083         312 :         CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
    3084         312 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
    3085         312 :     CPLFree(pszTemp);
    3086             : 
    3087         312 :     if (pbFound)
    3088             :     {
    3089         312 :         *pbFound = (pszValue != nullptr);
    3090             :     }
    3091             : 
    3092         312 :     if (pszValue)
    3093             :     {
    3094           0 :         return CPLAtofM(pszValue);
    3095             :     }
    3096             : 
    3097         312 :     return dfDefault;
    3098             : }
    3099             : 
    3100             : /************************************************************************/
    3101             : /*                           FetchStandardParallels()                   */
    3102             : /************************************************************************/
    3103             : 
    3104             : std::vector<std::string>
    3105           0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
    3106             : {
    3107             :     // cf-1.0 tags
    3108           0 :     const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
    3109             : 
    3110           0 :     std::vector<std::string> ret;
    3111           0 :     if (pszValue != nullptr)
    3112             :     {
    3113           0 :         CPLStringList aosValues;
    3114           0 :         if (pszValue[0] != '{' &&
    3115           0 :             CPLString(pszValue).Trim().find(' ') != std::string::npos)
    3116             :         {
    3117             :             // Some files like
    3118             :             // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
    3119             :             // do not use standard formatting for arrays, but just space
    3120             :             // separated syntax
    3121           0 :             aosValues = CSLTokenizeString2(pszValue, " ", 0);
    3122             :         }
    3123             :         else
    3124             :         {
    3125           0 :             aosValues = NCDFTokenizeArray(pszValue);
    3126             :         }
    3127           0 :         for (int i = 0; i < aosValues.size(); i++)
    3128             :         {
    3129           0 :             ret.push_back(aosValues[i]);
    3130             :         }
    3131             :     }
    3132             :     // Try gdal tags.
    3133             :     else
    3134             :     {
    3135           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
    3136             : 
    3137           0 :         if (pszValue != nullptr)
    3138           0 :             ret.push_back(pszValue);
    3139             : 
    3140           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
    3141             : 
    3142           0 :         if (pszValue != nullptr)
    3143           0 :             ret.push_back(pszValue);
    3144             :     }
    3145             : 
    3146           0 :     return ret;
    3147             : }
    3148             : 
    3149             : /************************************************************************/
    3150             : /*                           FetchAttr()                                */
    3151             : /************************************************************************/
    3152             : 
    3153        3550 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
    3154             :                                      const char *pszAttr)
    3155             : 
    3156             : {
    3157        3550 :     char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
    3158        3550 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
    3159        3550 :     CPLFree(pszKey);
    3160        3550 :     return pszValue;
    3161             : }
    3162             : 
    3163        2357 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
    3164             :                                      const char *pszAttr)
    3165             : 
    3166             : {
    3167        2357 :     char *pszVarFullName = nullptr;
    3168        2357 :     NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
    3169        2357 :     const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
    3170        2357 :     CPLFree(pszVarFullName);
    3171        2357 :     return pszValue;
    3172             : }
    3173             : 
    3174             : /************************************************************************/
    3175             : /*                       IsDifferenceBelow()                            */
    3176             : /************************************************************************/
    3177             : 
    3178        1039 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
    3179             : {
    3180        1039 :     const double dfAbsDiff = fabs(dfA - dfB);
    3181        1039 :     return dfAbsDiff <= dfError;
    3182             : }
    3183             : 
    3184             : /************************************************************************/
    3185             : /*                      SetProjectionFromVar()                          */
    3186             : /************************************************************************/
    3187         490 : void netCDFDataset::SetProjectionFromVar(
    3188             :     int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
    3189             :     std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
    3190             :     std::vector<std::string> *paosRemovedMDItems)
    3191             : {
    3192         490 :     bool bGotGeogCS = false;
    3193         490 :     bool bGotCfSRS = false;
    3194         490 :     bool bGotCfWktSRS = false;
    3195         490 :     bool bGotGdalSRS = false;
    3196         490 :     bool bGotCfGT = false;
    3197         490 :     bool bGotGdalGT = false;
    3198             : 
    3199             :     // These values from CF metadata.
    3200         490 :     OGRSpatialReference oSRS;
    3201         490 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3202         490 :     size_t xdim = nRasterXSize;
    3203         490 :     size_t ydim = nRasterYSize;
    3204             : 
    3205             :     // These values from GDAL metadata.
    3206         490 :     const char *pszWKT = nullptr;
    3207         490 :     const char *pszGeoTransform = nullptr;
    3208             : 
    3209         490 :     netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.
    3210             : 
    3211         490 :     CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
    3212             :              nVarId);
    3213             : 
    3214             :     // Get x/y range information.
    3215             : 
    3216             :     // Temp variables to use in SetGeoTransform() and SetProjection().
    3217         490 :     double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
    3218             : 
    3219             :     // Look for grid_mapping metadata.
    3220         490 :     const char *pszValue = pszGivenGM;
    3221         490 :     CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may
    3222             :         // point to it
    3223         490 :     if (pszValue == nullptr)
    3224             :     {
    3225         447 :         pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
    3226         447 :         if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
    3227             :         {
    3228             :             // Expanded form of grid_mapping
    3229             :             // e.g. "crsOSGB: x y crsWGS84: lat lon"
    3230             :             // Pickup the grid_mapping whose coordinates are dimensions of the
    3231             :             // variable
    3232           6 :             CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
    3233           3 :             if ((aosTokens.size() % 3) == 0)
    3234             :             {
    3235           3 :                 for (int i = 0; i < aosTokens.size() / 3; i++)
    3236             :                 {
    3237           3 :                     if (CSLFindString(poDS->papszDimName,
    3238           9 :                                       aosTokens[3 * i + 1]) >= 0 &&
    3239           3 :                         CSLFindString(poDS->papszDimName,
    3240           3 :                                       aosTokens[3 * i + 2]) >= 0)
    3241             :                     {
    3242           3 :                         osTmpGridMapping = aosTokens[3 * i];
    3243           6 :                         if (!osTmpGridMapping.empty() &&
    3244           3 :                             osTmpGridMapping.back() == ':')
    3245             :                         {
    3246           3 :                             osTmpGridMapping.resize(osTmpGridMapping.size() -
    3247             :                                                     1);
    3248             :                         }
    3249           3 :                         pszValue = osTmpGridMapping.c_str();
    3250           3 :                         break;
    3251             :                     }
    3252             :                 }
    3253             :             }
    3254             :         }
    3255             :     }
    3256         490 :     char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
    3257             : 
    3258         490 :     if (!EQUAL(pszGridMappingValue, ""))
    3259             :     {
    3260             :         // Read grid_mapping metadata.
    3261         211 :         int nProjGroupID = -1;
    3262         211 :         int nProjVarID = -1;
    3263         211 :         if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
    3264         211 :                            &nProjVarID) == CE_None)
    3265             :         {
    3266         210 :             poDS->ReadAttributes(nProjGroupID, nProjVarID);
    3267             : 
    3268             :             // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
    3269         210 :             CPLFree(pszGridMappingValue);
    3270         210 :             pszGridMappingValue = nullptr;
    3271         210 :             NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
    3272         210 :             if (pszGridMappingValue)
    3273             :             {
    3274         210 :                 CPLDebug("GDAL_netCDF", "got grid_mapping %s",
    3275             :                          pszGridMappingValue);
    3276         210 :                 pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
    3277         210 :                 if (!pszWKT)
    3278             :                 {
    3279          34 :                     pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
    3280             :                 }
    3281             :                 else
    3282             :                 {
    3283         176 :                     bGotGdalSRS = true;
    3284         176 :                     CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
    3285             :                 }
    3286         210 :                 if (pszWKT)
    3287             :                 {
    3288         180 :                     if (!bGotGdalSRS)
    3289             :                     {
    3290           4 :                         bGotCfWktSRS = true;
    3291           4 :                         CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3292             :                     }
    3293         180 :                     if (returnProjStr != nullptr)
    3294             :                     {
    3295          41 :                         (*returnProjStr) = std::string(pszWKT);
    3296             :                     }
    3297             :                     else
    3298             :                     {
    3299         139 :                         m_bAddedProjectionVarsDefs = true;
    3300         139 :                         m_bAddedProjectionVarsData = true;
    3301         278 :                         OGRSpatialReference oSRSTmp;
    3302         139 :                         oSRSTmp.SetAxisMappingStrategy(
    3303             :                             OAMS_TRADITIONAL_GIS_ORDER);
    3304         139 :                         oSRSTmp.importFromWkt(pszWKT);
    3305         139 :                         SetSpatialRefNoUpdate(&oSRSTmp);
    3306             :                     }
    3307             :                     pszGeoTransform =
    3308         180 :                         FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
    3309             :                 }
    3310             :             }
    3311             :             else
    3312             :             {
    3313           0 :                 pszGridMappingValue = CPLStrdup("");
    3314             :             }
    3315             :         }
    3316             :     }
    3317             : 
    3318             :     // Get information about the file.
    3319             :     //
    3320             :     // Was this file created by the GDAL netcdf driver?
    3321             :     // Was this file created by the newer (CF-conformant) driver?
    3322             :     //
    3323             :     // 1) If GDAL netcdf metadata is set, and version >= 1.9,
    3324             :     //    it was created with the new driver
    3325             :     // 2) Else, if spatial_ref and GeoTransform are present in the
    3326             :     //    grid_mapping variable, it was created by the old driver
    3327         490 :     pszValue = FetchAttr("NC_GLOBAL", "GDAL");
    3328             : 
    3329         490 :     if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
    3330             :     {
    3331         236 :         bIsGdalFile = true;
    3332         236 :         bIsGdalCfFile = true;
    3333             :     }
    3334         254 :     else if (pszWKT != nullptr && pszGeoTransform != nullptr)
    3335             :     {
    3336          14 :         bIsGdalFile = true;
    3337          14 :         bIsGdalCfFile = false;
    3338             :     }
    3339             : 
    3340             :     // Set default bottom-up default value.
    3341             :     // Y axis dimension and absence of GT can modify this value.
    3342             :     // Override with Config option GDAL_NETCDF_BOTTOMUP.
    3343             : 
    3344             :     // New driver is bottom-up by default.
    3345         490 :     if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
    3346          16 :         poDS->bBottomUp = false;
    3347             :     else
    3348         474 :         poDS->bBottomUp = true;
    3349             : 
    3350         490 :     CPLDebug("GDAL_netCDF",
    3351             :              "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
    3352         490 :              static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
    3353         490 :              static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
    3354             : 
    3355             :     // Read projection coordinates.
    3356             : 
    3357         490 :     int nGroupDimXID = -1;
    3358         490 :     int nVarDimXID = -1;
    3359         490 :     int nGroupDimYID = -1;
    3360         490 :     int nVarDimYID = -1;
    3361         490 :     if (sg != nullptr)
    3362             :     {
    3363          43 :         nGroupDimXID = sg->get_ncID();
    3364          43 :         nGroupDimYID = sg->get_ncID();
    3365          43 :         nVarDimXID = sg->getNodeCoordVars()[0];
    3366          43 :         nVarDimYID = sg->getNodeCoordVars()[1];
    3367             :     }
    3368             : 
    3369         490 :     if (!bReadSRSOnly)
    3370             :     {
    3371         337 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
    3372             :                        &nVarDimXID);
    3373         337 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
    3374             :                        &nVarDimYID);
    3375             :         // TODO: if above resolving fails we should also search for coordinate
    3376             :         // variables without same name than dimension using the same resolving
    3377             :         // logic. This should handle for example NASA Ocean Color L2 products.
    3378             : 
    3379             :         const bool bIgnoreXYAxisNameChecks =
    3380         674 :             CPLTestBool(CSLFetchNameValueDef(
    3381         337 :                 papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
    3382             :                 CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
    3383         337 :                                    "NO"))) ||
    3384             :             // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
    3385             :             // and transform attributes
    3386         337 :             (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
    3387         674 :              FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
    3388         336 :             FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
    3389             : 
    3390             :         // Check that they are 1D or 2D variables
    3391         337 :         if (nVarDimXID >= 0)
    3392             :         {
    3393         244 :             int ndims = -1;
    3394         244 :             nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
    3395         244 :             if (ndims == 0 || ndims > 2)
    3396           0 :                 nVarDimXID = -1;
    3397         244 :             else if (!bIgnoreXYAxisNameChecks)
    3398             :             {
    3399         242 :                 if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    3400         159 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
    3401             :                     // In case of inversion of X/Y
    3402         432 :                     !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
    3403          31 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
    3404             :                 {
    3405             :                     char szVarNameX[NC_MAX_NAME + 1];
    3406          31 :                     CPL_IGNORE_RET_VAL(
    3407          31 :                         nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    3408          31 :                     if (!(ndims == 1 &&
    3409          30 :                           (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
    3410          29 :                            EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
    3411             :                     {
    3412          30 :                         CPLDebug(
    3413             :                             "netCDF",
    3414             :                             "Georeferencing ignored due to non-specific "
    3415             :                             "enough X axis name. "
    3416             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3417             :                             "as configuration option to bypass this check");
    3418          30 :                         nVarDimXID = -1;
    3419             :                     }
    3420             :                 }
    3421             :             }
    3422             :         }
    3423             : 
    3424         337 :         if (nVarDimYID >= 0)
    3425             :         {
    3426         246 :             int ndims = -1;
    3427         246 :             nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
    3428         246 :             if (ndims == 0 || ndims > 2)
    3429           1 :                 nVarDimYID = -1;
    3430         245 :             else if (!bIgnoreXYAxisNameChecks)
    3431             :             {
    3432         243 :                 if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
    3433         160 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
    3434             :                     // In case of inversion of X/Y
    3435         435 :                     !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    3436          32 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
    3437             :                 {
    3438             :                     char szVarNameY[NC_MAX_NAME + 1];
    3439          32 :                     CPL_IGNORE_RET_VAL(
    3440          32 :                         nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    3441          32 :                     if (!(ndims == 1 &&
    3442          32 :                           (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
    3443          31 :                            EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
    3444             :                     {
    3445          31 :                         CPLDebug(
    3446             :                             "netCDF",
    3447             :                             "Georeferencing ignored due to non-specific "
    3448             :                             "enough Y axis name. "
    3449             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3450             :                             "as configuration option to bypass this check");
    3451          31 :                         nVarDimYID = -1;
    3452             :                     }
    3453             :                 }
    3454             :             }
    3455             :         }
    3456             : 
    3457         337 :         if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
    3458             :         {
    3459           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3460             :                      "1-pixel width/height files not supported, "
    3461             :                      "xdim: %ld ydim: %ld",
    3462             :                      static_cast<long>(xdim), static_cast<long>(ydim));
    3463           0 :             nVarDimXID = -1;
    3464           0 :             nVarDimYID = -1;
    3465             :         }
    3466             :     }
    3467             : 
    3468         490 :     const char *pszUnits = nullptr;
    3469         490 :     if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3470             :     {
    3471         257 :         const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
    3472         257 :         const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
    3473             :         // Normalize degrees_east/degrees_north to degrees
    3474             :         // Cf https://github.com/OSGeo/gdal/issues/11009
    3475         257 :         if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
    3476          72 :             pszUnitsX = "degrees";
    3477         257 :         if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
    3478          72 :             pszUnitsY = "degrees";
    3479             : 
    3480         257 :         if (pszUnitsX && pszUnitsY)
    3481             :         {
    3482         210 :             if (EQUAL(pszUnitsX, pszUnitsY))
    3483         207 :                 pszUnits = pszUnitsX;
    3484           3 :             else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3485             :             {
    3486           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3487             :                          "X axis unit (%s) is different from Y axis "
    3488             :                          "unit (%s). SRS will ignore axis unit and be "
    3489             :                          "likely wrong.",
    3490             :                          pszUnitsX, pszUnitsY);
    3491             :             }
    3492             :         }
    3493          47 :         else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3494             :         {
    3495           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3496             :                      "X axis unit is defined, but not Y one ."
    3497             :                      "SRS will ignore axis unit and be likely wrong.");
    3498             :         }
    3499          47 :         else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3500             :         {
    3501           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3502             :                      "Y axis unit is defined, but not X one ."
    3503             :                      "SRS will ignore axis unit and be likely wrong.");
    3504             :         }
    3505             :     }
    3506             : 
    3507         490 :     if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3508             :     {
    3509          31 :         CPLStringList aosGridMappingKeyValues;
    3510          31 :         const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
    3511         789 :         for (const char *const *papszIter = papszMetadata;
    3512         789 :              papszIter && *papszIter; ++papszIter)
    3513             :         {
    3514         758 :             if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
    3515         236 :                 (*papszIter)[nLenGridMappingValue] == '#')
    3516             :             {
    3517         236 :                 char *pszKey = nullptr;
    3518         472 :                 pszValue = CPLParseNameValue(
    3519         236 :                     *papszIter + nLenGridMappingValue + 1, &pszKey);
    3520         236 :                 if (pszKey && pszValue)
    3521         236 :                     aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
    3522         236 :                 CPLFree(pszKey);
    3523             :             }
    3524             :         }
    3525             : 
    3526          31 :         bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
    3527             :                          CF_PP_SEMI_MAJOR_AXIS) != nullptr;
    3528             : 
    3529          31 :         oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
    3530          31 :         bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
    3531             :     }
    3532             :     else
    3533             :     {
    3534             :         // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
    3535             :         // attribute hold on the variable of interest that contains a PROJ.4
    3536             :         // string
    3537         459 :         pszValue = FetchAttr(nGroupId, nVarId, "crs");
    3538         460 :         if (pszValue &&
    3539           1 :             (strstr(pszValue, "+proj=") != nullptr ||
    3540           0 :              strstr(pszValue, "GEOGCS") != nullptr ||
    3541           0 :              strstr(pszValue, "PROJCS") != nullptr ||
    3542         460 :              strstr(pszValue, "EPSG:") != nullptr) &&
    3543           1 :             oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
    3544             :         {
    3545           1 :             bGotCfSRS = true;
    3546             :         }
    3547             :     }
    3548             : 
    3549             :     // Set Projection from CF.
    3550         490 :     double dfLinearUnitsConvFactor = 1.0;
    3551         490 :     if ((bGotGeogCS || bGotCfSRS))
    3552             :     {
    3553          31 :         if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3554             :         {
    3555             :             // Set SRS Units.
    3556             : 
    3557             :             // Check units for x and y.
    3558          28 :             if (oSRS.IsProjected())
    3559             :             {
    3560          25 :                 dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
    3561             : 
    3562             :                 // If the user doesn't ask to preserve the axis unit,
    3563             :                 // then normalize to metre
    3564          31 :                 if (dfLinearUnitsConvFactor != 1.0 &&
    3565           6 :                     !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
    3566             :                                   false))
    3567             :                 {
    3568           5 :                     oSRS.SetLinearUnits("metre", 1.0);
    3569           5 :                     oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
    3570             :                 }
    3571             :                 else
    3572             :                 {
    3573          20 :                     dfLinearUnitsConvFactor = 1.0;
    3574             :                 }
    3575             :             }
    3576             :         }
    3577             : 
    3578             :         // Set projection.
    3579          31 :         char *pszTempProjection = nullptr;
    3580          31 :         oSRS.exportToWkt(&pszTempProjection);
    3581          31 :         if (pszTempProjection)
    3582             :         {
    3583          31 :             CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3584          31 :             if (returnProjStr != nullptr)
    3585             :             {
    3586           2 :                 (*returnProjStr) = std::string(pszTempProjection);
    3587             :             }
    3588             :             else
    3589             :             {
    3590          29 :                 m_bAddedProjectionVarsDefs = true;
    3591          29 :                 m_bAddedProjectionVarsData = true;
    3592          29 :                 SetSpatialRefNoUpdate(&oSRS);
    3593             :             }
    3594             :         }
    3595          31 :         CPLFree(pszTempProjection);
    3596             :     }
    3597             : 
    3598         490 :     if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
    3599             :         ydim > 0)
    3600             :     {
    3601             :         double *pdfXCoord =
    3602         214 :             static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
    3603             :         double *pdfYCoord =
    3604         214 :             static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
    3605             : 
    3606         214 :         size_t start[2] = {0, 0};
    3607         214 :         size_t edge[2] = {xdim, 0};
    3608         214 :         int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
    3609             :                                         pdfXCoord);
    3610         214 :         NCDF_ERR(status);
    3611             : 
    3612         214 :         edge[0] = ydim;
    3613         214 :         status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
    3614             :                                     pdfYCoord);
    3615         214 :         NCDF_ERR(status);
    3616             : 
    3617         214 :         nc_type nc_var_dimx_datatype = NC_NAT;
    3618             :         status =
    3619         214 :             nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
    3620         214 :         NCDF_ERR(status);
    3621             : 
    3622         214 :         nc_type nc_var_dimy_datatype = NC_NAT;
    3623             :         status =
    3624         214 :             nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
    3625         214 :         NCDF_ERR(status);
    3626             : 
    3627         214 :         if (!poDS->bSwitchedXY)
    3628             :         {
    3629             :             // Convert ]180,540] longitude values to ]-180,0].
    3630         295 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3631          83 :                 CPLTestBool(
    3632             :                     CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
    3633             :             {
    3634             :                 // If minimum longitude is > 180, subtract 360 from all.
    3635             :                 // Add a check on the maximum X value too, since
    3636             :                 // NCDFIsVarLongitude() is not very specific by default (see
    3637             :                 // https://github.com/OSGeo/gdal/issues/1440)
    3638          90 :                 if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
    3639           7 :                     std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
    3640             :                 {
    3641           0 :                     CPLDebug(
    3642             :                         "GDAL_netCDF",
    3643             :                         "Offsetting longitudes from ]180,540] to ]-180,180]. "
    3644             :                         "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
    3645           0 :                     for (size_t i = 0; i < xdim; i++)
    3646           0 :                         pdfXCoord[i] -= 360;
    3647             :                 }
    3648             :             }
    3649             :         }
    3650             : 
    3651             :         // Is pixel spacing uniform across the map?
    3652             : 
    3653             :         // Check Longitude.
    3654             : 
    3655         214 :         bool bLonSpacingOK = false;
    3656         214 :         if (xdim == 2)
    3657             :         {
    3658          28 :             bLonSpacingOK = true;
    3659             :         }
    3660             :         else
    3661             :         {
    3662         186 :             bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
    3663             : 
    3664             :             // fix longitudes if longitudes should increase from
    3665             :             // west to east, but west > east
    3666         259 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3667          73 :                 !bWestIsLeft)
    3668             :             {
    3669           2 :                 size_t ndecreases = 0;
    3670             : 
    3671             :                 // there is lon wrap if longitudes increase
    3672             :                 // with one single decrease
    3673         107 :                 for (size_t i = 1; i < xdim; i++)
    3674             :                 {
    3675         105 :                     if (pdfXCoord[i] < pdfXCoord[i - 1])
    3676           1 :                         ndecreases++;
    3677             :                 }
    3678             : 
    3679           2 :                 if (ndecreases == 1)
    3680             :                 {
    3681           1 :                     CPLDebug("GDAL_netCDF", "longitude wrap detected");
    3682           4 :                     for (size_t i = 0; i < xdim; i++)
    3683             :                     {
    3684           3 :                         if (pdfXCoord[i] > pdfXCoord[xdim - 1])
    3685           1 :                             pdfXCoord[i] -= 360;
    3686             :                     }
    3687             :                 }
    3688             :             }
    3689             : 
    3690         186 :             const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
    3691         186 :             const double dfSpacingMiddle =
    3692         186 :                 pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
    3693         186 :             const double dfSpacingLast =
    3694         186 :                 pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
    3695             : 
    3696         186 :             CPLDebug("GDAL_netCDF",
    3697             :                      "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3698             :                      "dfSpacingLast: %f",
    3699             :                      static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
    3700             :                      dfSpacingLast);
    3701             : #ifdef NCDF_DEBUG
    3702             :             CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
    3703             :                      pdfXCoord[1], pdfXCoord[xdim / 2],
    3704             :                      pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
    3705             :                      pdfXCoord[xdim - 1]);
    3706             : #endif
    3707             : 
    3708             :             // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
    3709             :             // requires a 0.02% tolerance, so let's settle for 0.05%
    3710             : 
    3711             :             // For float variables, increase to 0.2% (as seen in
    3712             :             // https://github.com/OSGeo/gdal/issues/3663)
    3713         186 :             const double dfEpsRel =
    3714         186 :                 nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3715             : 
    3716             :             const double dfEps =
    3717             :                 dfEpsRel *
    3718         372 :                 std::max(fabs(dfSpacingBegin),
    3719         186 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3720         370 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3721         370 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3722         184 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3723             :             {
    3724         184 :                 bLonSpacingOK = true;
    3725             :             }
    3726           2 :             else if (CPLTestBool(CPLGetConfigOption(
    3727             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3728             :             {
    3729           0 :                 bLonSpacingOK = true;
    3730           0 :                 CPLDebug(
    3731             :                     "GDAL_netCDF",
    3732             :                     "Longitude/X is not equally spaced, but will be considered "
    3733             :                     "as such because of "
    3734             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3735             :             }
    3736             :         }
    3737             : 
    3738         214 :         if (bLonSpacingOK == false)
    3739             :         {
    3740           2 :             CPLDebug(
    3741             :                 "GDAL_netCDF", "%s",
    3742             :                 "Longitude/X is not equally spaced (with a 0.05% tolerance). "
    3743             :                 "You may set the "
    3744             :                 "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3745             :                 "option to YES to ignore this check");
    3746             :         }
    3747             : 
    3748             :         // Check Latitude.
    3749         214 :         bool bLatSpacingOK = false;
    3750             : 
    3751         214 :         if (ydim == 2)
    3752             :         {
    3753          48 :             bLatSpacingOK = true;
    3754             :         }
    3755             :         else
    3756             :         {
    3757         166 :             const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
    3758         166 :             const double dfSpacingMiddle =
    3759         166 :                 pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
    3760             : 
    3761         166 :             const double dfSpacingLast =
    3762         166 :                 pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
    3763             : 
    3764         166 :             CPLDebug("GDAL_netCDF",
    3765             :                      "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3766             :                      "dfSpacingLast: %f",
    3767             :                      (long)ydim, dfSpacingBegin, dfSpacingMiddle,
    3768             :                      dfSpacingLast);
    3769             : #ifdef NCDF_DEBUG
    3770             :             CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
    3771             :                      pdfYCoord[1], pdfYCoord[ydim / 2],
    3772             :                      pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
    3773             :                      pdfYCoord[ydim - 1]);
    3774             : #endif
    3775             : 
    3776         166 :             const double dfEpsRel =
    3777         166 :                 nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3778             : 
    3779             :             const double dfEps =
    3780             :                 dfEpsRel *
    3781         332 :                 std::max(fabs(dfSpacingBegin),
    3782         166 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3783         330 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3784         330 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3785         155 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3786             :             {
    3787         155 :                 bLatSpacingOK = true;
    3788             :             }
    3789          11 :             else if (CPLTestBool(CPLGetConfigOption(
    3790             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3791             :             {
    3792           0 :                 bLatSpacingOK = true;
    3793           0 :                 CPLDebug(
    3794             :                     "GDAL_netCDF",
    3795             :                     "Latitude/Y is not equally spaced, but will be considered "
    3796             :                     "as such because of "
    3797             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3798             :             }
    3799          11 :             else if (!oSRS.IsProjected() &&
    3800          11 :                      fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
    3801          30 :                      fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
    3802           8 :                      fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
    3803             :             {
    3804           8 :                 bLatSpacingOK = true;
    3805           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3806             :                          "Latitude grid not spaced evenly.  "
    3807             :                          "Setting projection for grid spacing is "
    3808             :                          "within 0.1 degrees threshold.");
    3809             : 
    3810           8 :                 CPLDebug("GDAL_netCDF",
    3811             :                          "Latitude grid not spaced evenly, but within 0.1 "
    3812             :                          "degree threshold (probably a Gaussian grid).  "
    3813             :                          "Saving original latitude values in Y_VALUES "
    3814             :                          "geolocation metadata");
    3815           8 :                 Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
    3816             :             }
    3817             : 
    3818         166 :             if (bLatSpacingOK == false)
    3819             :             {
    3820           3 :                 CPLDebug(
    3821             :                     "GDAL_netCDF", "%s",
    3822             :                     "Latitude/Y is not equally spaced (with a 0.05% "
    3823             :                     "tolerance). "
    3824             :                     "You may set the "
    3825             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3826             :                     "option to YES to ignore this check");
    3827             :             }
    3828             :         }
    3829             : 
    3830         214 :         if (bLonSpacingOK && bLatSpacingOK)
    3831             :         {
    3832             :             // We have gridded data so we can set the Georeferencing info.
    3833             : 
    3834             :             // Enable GeoTransform.
    3835             : 
    3836             :             // In the following "actual_range" and "node_offset"
    3837             :             // are attributes used by netCDF files created by GMT.
    3838             :             // If we find them we know how to proceed. Else, use
    3839             :             // the original algorithm.
    3840         211 :             bGotCfGT = true;
    3841             : 
    3842         211 :             int node_offset = 0;
    3843         211 :             NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
    3844             : 
    3845         211 :             double adfActualRange[2] = {0.0, 0.0};
    3846         211 :             double xMinMax[2] = {0.0, 0.0};
    3847         211 :             double yMinMax[2] = {0.0, 0.0};
    3848             : 
    3849             :             const auto RoundMinMaxForFloatVals =
    3850          58 :                 [](double &dfMin, double &dfMax, int nIntervals)
    3851             :             {
    3852             :                 // Helps for a case where longitudes range from
    3853             :                 // -179.99 to 180.0 with a 0.01 degree spacing.
    3854             :                 // However as this is encoded in a float array,
    3855             :                 // -179.99 is actually read as -179.99000549316406 as
    3856             :                 // a double. Try to detect that and correct the rounding
    3857             : 
    3858          87 :                 const auto IsAlmostInteger = [](double dfVal)
    3859             :                 {
    3860          87 :                     constexpr double THRESHOLD_INTEGER = 1e-3;
    3861          87 :                     return std::fabs(dfVal - std::round(dfVal)) <=
    3862          87 :                            THRESHOLD_INTEGER;
    3863             :                 };
    3864             : 
    3865          58 :                 const double dfSpacing = (dfMax - dfMin) / nIntervals;
    3866          58 :                 if (dfSpacing > 0)
    3867             :                 {
    3868          47 :                     const double dfInvSpacing = 1.0 / dfSpacing;
    3869          47 :                     if (IsAlmostInteger(dfInvSpacing))
    3870             :                     {
    3871          20 :                         const double dfRoundedSpacing =
    3872          20 :                             1.0 / std::round(dfInvSpacing);
    3873          20 :                         const double dfMinDivRoundedSpacing =
    3874          20 :                             dfMin / dfRoundedSpacing;
    3875          20 :                         const double dfMaxDivRoundedSpacing =
    3876          20 :                             dfMax / dfRoundedSpacing;
    3877          40 :                         if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
    3878          20 :                             IsAlmostInteger(dfMaxDivRoundedSpacing))
    3879             :                         {
    3880          20 :                             const double dfRoundedMin =
    3881          20 :                                 std::round(dfMinDivRoundedSpacing) *
    3882             :                                 dfRoundedSpacing;
    3883          20 :                             const double dfRoundedMax =
    3884          20 :                                 std::round(dfMaxDivRoundedSpacing) *
    3885             :                                 dfRoundedSpacing;
    3886          20 :                             if (static_cast<float>(dfMin) ==
    3887          20 :                                     static_cast<float>(dfRoundedMin) &&
    3888           8 :                                 static_cast<float>(dfMax) ==
    3889           8 :                                     static_cast<float>(dfRoundedMax))
    3890             :                             {
    3891           7 :                                 dfMin = dfRoundedMin;
    3892           7 :                                 dfMax = dfRoundedMax;
    3893             :                             }
    3894             :                         }
    3895             :                     }
    3896             :                 }
    3897          58 :             };
    3898             : 
    3899         211 :             if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
    3900             :                                    adfActualRange))
    3901             :             {
    3902           3 :                 xMinMax[0] = adfActualRange[0];
    3903           3 :                 xMinMax[1] = adfActualRange[1];
    3904             : 
    3905             :                 // Present xMinMax[] in the same order as padfXCoord
    3906           3 :                 if ((xMinMax[0] - xMinMax[1]) *
    3907           3 :                         (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
    3908             :                     0)
    3909             :                 {
    3910           0 :                     std::swap(xMinMax[0], xMinMax[1]);
    3911             :                 }
    3912             :             }
    3913             :             else
    3914             :             {
    3915         208 :                 xMinMax[0] = pdfXCoord[0];
    3916         208 :                 xMinMax[1] = pdfXCoord[xdim - 1];
    3917         208 :                 node_offset = 0;
    3918             : 
    3919         208 :                 if (nc_var_dimx_datatype == NC_FLOAT)
    3920             :                 {
    3921          29 :                     RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
    3922          29 :                                             poDS->nRasterXSize - 1);
    3923             :                 }
    3924             :             }
    3925             : 
    3926         211 :             if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
    3927             :                                    adfActualRange))
    3928             :             {
    3929           3 :                 yMinMax[0] = adfActualRange[0];
    3930           3 :                 yMinMax[1] = adfActualRange[1];
    3931             : 
    3932             :                 // Present yMinMax[] in the same order as pdfYCoord
    3933           3 :                 if ((yMinMax[0] - yMinMax[1]) *
    3934           3 :                         (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
    3935             :                     0)
    3936             :                 {
    3937           1 :                     std::swap(yMinMax[0], yMinMax[1]);
    3938             :                 }
    3939             :             }
    3940             :             else
    3941             :             {
    3942         208 :                 yMinMax[0] = pdfYCoord[0];
    3943         208 :                 yMinMax[1] = pdfYCoord[ydim - 1];
    3944         208 :                 node_offset = 0;
    3945             : 
    3946         208 :                 if (nc_var_dimy_datatype == NC_FLOAT)
    3947             :                 {
    3948          29 :                     RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
    3949          29 :                                             poDS->nRasterYSize - 1);
    3950             :                 }
    3951             :             }
    3952             : 
    3953         211 :             double dfCoordOffset = 0.0;
    3954         211 :             double dfCoordScale = 1.0;
    3955         211 :             if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
    3956         215 :                                    &dfCoordOffset) &&
    3957           4 :                 !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
    3958             :                                    &dfCoordScale))
    3959             :             {
    3960           4 :                 xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
    3961           4 :                 xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
    3962             :             }
    3963             : 
    3964         211 :             if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
    3965         215 :                                    &dfCoordOffset) &&
    3966           4 :                 !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
    3967             :                                    &dfCoordScale))
    3968             :             {
    3969           4 :                 yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
    3970           4 :                 yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
    3971             :             }
    3972             : 
    3973             :             // Check for reverse order of y-coordinate.
    3974         211 :             if (!bSwitchedXY)
    3975             :             {
    3976         209 :                 poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
    3977         209 :                 CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
    3978         209 :                          static_cast<int>(poDS->bBottomUp));
    3979         209 :                 if (!poDS->bBottomUp)
    3980             :                 {
    3981          32 :                     std::swap(yMinMax[0], yMinMax[1]);
    3982             :                 }
    3983             :             }
    3984             : 
    3985             :             // Geostationary satellites can specify units in (micro)radians
    3986             :             // So we check if they do, and if so convert to linear units
    3987             :             // (meters)
    3988         211 :             const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
    3989         211 :             if (pszProjName != nullptr)
    3990             :             {
    3991          24 :                 if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
    3992             :                 {
    3993             :                     double satelliteHeight =
    3994           3 :                         oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
    3995           3 :                     size_t nAttlen = 0;
    3996             :                     char szUnits[NC_MAX_NAME + 1];
    3997           3 :                     szUnits[0] = '\0';
    3998           3 :                     nc_type nAttype = NC_NAT;
    3999           3 :                     nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
    4000             :                                &nAttlen);
    4001           6 :                     if (nAttlen < sizeof(szUnits) &&
    4002           3 :                         nc_get_att_text(nGroupId, nVarDimXID, "units",
    4003             :                                         szUnits) == NC_NOERR)
    4004             :                     {
    4005           3 :                         szUnits[nAttlen] = '\0';
    4006           3 :                         if (EQUAL(szUnits, "microradian"))
    4007             :                         {
    4008           1 :                             xMinMax[0] =
    4009           1 :                                 xMinMax[0] * satelliteHeight * 0.000001;
    4010           1 :                             xMinMax[1] =
    4011           1 :                                 xMinMax[1] * satelliteHeight * 0.000001;
    4012             :                         }
    4013           2 :                         else if (EQUAL(szUnits, "rad") ||
    4014           1 :                                  EQUAL(szUnits, "radian"))
    4015             :                         {
    4016           2 :                             xMinMax[0] = xMinMax[0] * satelliteHeight;
    4017           2 :                             xMinMax[1] = xMinMax[1] * satelliteHeight;
    4018             :                         }
    4019             :                     }
    4020           3 :                     szUnits[0] = '\0';
    4021           3 :                     nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
    4022             :                                &nAttlen);
    4023           6 :                     if (nAttlen < sizeof(szUnits) &&
    4024           3 :                         nc_get_att_text(nGroupId, nVarDimYID, "units",
    4025             :                                         szUnits) == NC_NOERR)
    4026             :                     {
    4027           3 :                         szUnits[nAttlen] = '\0';
    4028           3 :                         if (EQUAL(szUnits, "microradian"))
    4029             :                         {
    4030           1 :                             yMinMax[0] =
    4031           1 :                                 yMinMax[0] * satelliteHeight * 0.000001;
    4032           1 :                             yMinMax[1] =
    4033           1 :                                 yMinMax[1] * satelliteHeight * 0.000001;
    4034             :                         }
    4035           2 :                         else if (EQUAL(szUnits, "rad") ||
    4036           1 :                                  EQUAL(szUnits, "radian"))
    4037             :                         {
    4038           2 :                             yMinMax[0] = yMinMax[0] * satelliteHeight;
    4039           2 :                             yMinMax[1] = yMinMax[1] * satelliteHeight;
    4040             :                         }
    4041             :                     }
    4042             :                 }
    4043             :             }
    4044             : 
    4045         211 :             adfTempGeoTransform[0] = xMinMax[0];
    4046         211 :             adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
    4047         211 :                                      (poDS->nRasterXSize + (node_offset - 1));
    4048         211 :             adfTempGeoTransform[2] = 0;
    4049         211 :             if (bSwitchedXY)
    4050             :             {
    4051           2 :                 adfTempGeoTransform[3] = yMinMax[0];
    4052           2 :                 adfTempGeoTransform[4] = 0;
    4053           2 :                 adfTempGeoTransform[5] =
    4054           2 :                     (yMinMax[1] - yMinMax[0]) /
    4055           2 :                     (poDS->nRasterYSize + (node_offset - 1));
    4056             :             }
    4057             :             else
    4058             :             {
    4059         209 :                 adfTempGeoTransform[3] = yMinMax[1];
    4060         209 :                 adfTempGeoTransform[4] = 0;
    4061         209 :                 adfTempGeoTransform[5] =
    4062         209 :                     (yMinMax[0] - yMinMax[1]) /
    4063         209 :                     (poDS->nRasterYSize + (node_offset - 1));
    4064             :             }
    4065             : 
    4066             :             // Compute the center of the pixel.
    4067         211 :             if (!node_offset)
    4068             :             {
    4069             :                 // Otherwise its already the pixel center.
    4070         211 :                 adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
    4071         211 :                 adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
    4072             :             }
    4073             :         }
    4074             : 
    4075             :         const auto AreSRSEqualThroughProj4String =
    4076           2 :             [](const OGRSpatialReference &oSRS1,
    4077             :                const OGRSpatialReference &oSRS2)
    4078             :         {
    4079           2 :             char *pszProj4Str1 = nullptr;
    4080           2 :             oSRS1.exportToProj4(&pszProj4Str1);
    4081             : 
    4082           2 :             char *pszProj4Str2 = nullptr;
    4083           2 :             oSRS2.exportToProj4(&pszProj4Str2);
    4084             : 
    4085             :             {
    4086           2 :                 char *pszTmp = strstr(pszProj4Str1, "+datum=");
    4087           2 :                 if (pszTmp)
    4088           0 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4089             :             }
    4090             : 
    4091             :             {
    4092           2 :                 char *pszTmp = strstr(pszProj4Str2, "+datum=");
    4093           2 :                 if (pszTmp)
    4094           2 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4095             :             }
    4096             : 
    4097           2 :             bool bRet = false;
    4098           2 :             if (pszProj4Str1 && pszProj4Str2 &&
    4099           2 :                 EQUAL(pszProj4Str1, pszProj4Str2))
    4100             :             {
    4101           1 :                 bRet = true;
    4102             :             }
    4103             : 
    4104           2 :             CPLFree(pszProj4Str1);
    4105           2 :             CPLFree(pszProj4Str2);
    4106           2 :             return bRet;
    4107             :         };
    4108             : 
    4109         214 :         if (dfLinearUnitsConvFactor != 1.0)
    4110             :         {
    4111          35 :             for (int i = 0; i < 6; ++i)
    4112          30 :                 adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
    4113             : 
    4114           5 :             if (paosRemovedMDItems)
    4115             :             {
    4116             :                 char szVarNameX[NC_MAX_NAME + 1];
    4117           5 :                 CPL_IGNORE_RET_VAL(
    4118           5 :                     nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4119             : 
    4120             :                 char szVarNameY[NC_MAX_NAME + 1];
    4121           5 :                 CPL_IGNORE_RET_VAL(
    4122           5 :                     nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4123             : 
    4124           5 :                 paosRemovedMDItems->push_back(
    4125             :                     CPLSPrintf("%s#units", szVarNameX));
    4126           5 :                 paosRemovedMDItems->push_back(
    4127             :                     CPLSPrintf("%s#units", szVarNameY));
    4128             :             }
    4129             :         }
    4130             : 
    4131             :         // If there is a global "geospatial_bounds_crs" attribute, check that it
    4132             :         // is consistent with the SRS, and if so, use it as the SRS
    4133             :         const char *pszGBCRS =
    4134         214 :             FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
    4135         214 :         if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
    4136             :         {
    4137           4 :             OGRSpatialReference oSRSFromGBCRS;
    4138           2 :             oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4139           2 :             if (oSRSFromGBCRS.SetFromUserInput(
    4140             :                     pszGBCRS,
    4141             :                     OGRSpatialReference::
    4142           4 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
    4143           2 :                 AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
    4144             :             {
    4145           1 :                 oSRS = std::move(oSRSFromGBCRS);
    4146           1 :                 SetSpatialRefNoUpdate(&oSRS);
    4147             :             }
    4148             :         }
    4149             : 
    4150         214 :         CPLFree(pdfXCoord);
    4151         214 :         CPLFree(pdfYCoord);
    4152             :     }  // end if(has dims)
    4153             : 
    4154             :     // Process custom GeoTransform GDAL value.
    4155         490 :     if (!EQUAL(pszGridMappingValue, "") && !bGotCfGT)
    4156             :     {
    4157             :         // TODO: Read the GT values and detect for conflict with CF.
    4158             :         // This could resolve the GT precision loss issue.
    4159             : 
    4160          91 :         if (pszGeoTransform != nullptr)
    4161             :         {
    4162             :             char **papszGeoTransform =
    4163          13 :                 CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS);
    4164          13 :             if (CSLCount(papszGeoTransform) == 6)
    4165             :             {
    4166          13 :                 bGotGdalGT = true;
    4167          91 :                 for (int i = 0; i < 6; i++)
    4168          78 :                     adfTempGeoTransform[i] = CPLAtof(papszGeoTransform[i]);
    4169             :             }
    4170          13 :             CSLDestroy(papszGeoTransform);
    4171             :         }
    4172             :         else
    4173             :         {
    4174             :             // Look for corner array values.
    4175             :             // CPLDebug("GDAL_netCDF",
    4176             :             //           "looking for geotransform corners");
    4177          78 :             bool bGotNN = false;
    4178          78 :             double dfNN = FetchCopyParam(pszGridMappingValue,
    4179             :                                          "Northernmost_Northing", 0, &bGotNN);
    4180             : 
    4181          78 :             bool bGotSN = false;
    4182          78 :             double dfSN = FetchCopyParam(pszGridMappingValue,
    4183             :                                          "Southernmost_Northing", 0, &bGotSN);
    4184             : 
    4185          78 :             bool bGotEE = false;
    4186          78 :             double dfEE = FetchCopyParam(pszGridMappingValue,
    4187             :                                          "Easternmost_Easting", 0, &bGotEE);
    4188             : 
    4189          78 :             bool bGotWE = false;
    4190          78 :             double dfWE = FetchCopyParam(pszGridMappingValue,
    4191             :                                          "Westernmost_Easting", 0, &bGotWE);
    4192             : 
    4193             :             // Only set the GeoTransform if we got all the values.
    4194          78 :             if (bGotNN && bGotSN && bGotEE && bGotWE)
    4195             :             {
    4196           0 :                 bGotGdalGT = true;
    4197             : 
    4198           0 :                 adfTempGeoTransform[0] = dfWE;
    4199           0 :                 adfTempGeoTransform[1] =
    4200           0 :                     (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
    4201           0 :                 adfTempGeoTransform[2] = 0.0;
    4202           0 :                 adfTempGeoTransform[3] = dfNN;
    4203           0 :                 adfTempGeoTransform[4] = 0.0;
    4204           0 :                 adfTempGeoTransform[5] =
    4205           0 :                     (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
    4206             :                 // Compute the center of the pixel.
    4207           0 :                 adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
    4208           0 :                 adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
    4209             :             }
    4210             :         }  // (pszGeoTransform != NULL)
    4211             : 
    4212          91 :         if (bGotGdalSRS && !bGotGdalGT)
    4213          72 :             CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
    4214             :     }
    4215             : 
    4216         490 :     if (!pszWKT && !bGotCfSRS)
    4217             :     {
    4218             :         // Some netCDF files have a srid attribute (#6613) like
    4219             :         // urn:ogc:def:crs:EPSG::6931
    4220         279 :         const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
    4221         279 :         if (pszSRID != nullptr)
    4222             :         {
    4223           0 :             oSRS.Clear();
    4224           0 :             if (oSRS.SetFromUserInput(
    4225             :                     pszSRID,
    4226             :                     OGRSpatialReference::
    4227           0 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    4228             :             {
    4229           0 :                 char *pszWKTExport = nullptr;
    4230           0 :                 CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
    4231           0 :                 oSRS.exportToWkt(&pszWKTExport);
    4232           0 :                 if (returnProjStr != nullptr)
    4233             :                 {
    4234           0 :                     (*returnProjStr) = std::string(pszWKTExport);
    4235             :                 }
    4236             :                 else
    4237             :                 {
    4238           0 :                     m_bAddedProjectionVarsDefs = true;
    4239           0 :                     m_bAddedProjectionVarsData = true;
    4240           0 :                     SetSpatialRefNoUpdate(&oSRS);
    4241             :                 }
    4242           0 :                 CPLFree(pszWKTExport);
    4243             :             }
    4244             :         }
    4245             :     }
    4246             : 
    4247         490 :     CPLFree(pszGridMappingValue);
    4248             : 
    4249         490 :     if (bReadSRSOnly)
    4250         153 :         return;
    4251             : 
    4252             :     // Determines the SRS to be used by the geolocation array, if any
    4253         674 :     std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
    4254         337 :     if (!m_oSRS.IsEmpty())
    4255             :     {
    4256         252 :         OGRSpatialReference oGeogCRS;
    4257         126 :         oGeogCRS.CopyGeogCSFrom(&m_oSRS);
    4258         126 :         char *pszWKTTmp = nullptr;
    4259         126 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
    4260         126 :         if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
    4261             :         {
    4262         126 :             osGeolocWKT = pszWKTTmp;
    4263             :         }
    4264         126 :         CPLFree(pszWKTTmp);
    4265             :     }
    4266             : 
    4267             :     // Process geolocation arrays from CF "coordinates" attribute.
    4268         674 :     std::string osGeolocXName, osGeolocYName;
    4269         337 :     if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
    4270         337 :                              osGeolocYName))
    4271             :     {
    4272          51 :         bool bCanCancelGT = true;
    4273          51 :         if ((nVarDimXID != -1) && (nVarDimYID != -1))
    4274             :         {
    4275             :             char szVarNameX[NC_MAX_NAME + 1];
    4276          40 :             CPL_IGNORE_RET_VAL(
    4277          40 :                 nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4278             :             char szVarNameY[NC_MAX_NAME + 1];
    4279          40 :             CPL_IGNORE_RET_VAL(
    4280          40 :                 nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4281          40 :             bCanCancelGT =
    4282          40 :                 !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
    4283             :         }
    4284          88 :         if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
    4285          37 :             !bSwitchedXY)
    4286             :         {
    4287          35 :             bGotCfGT = false;
    4288             :         }
    4289             :     }
    4290         115 :     else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
    4291         404 :              (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
    4292           3 :              ((!bSwitchedXY &&
    4293           3 :                NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    4294           1 :                NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
    4295           2 :               (bSwitchedXY &&
    4296           0 :                NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    4297           0 :                NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
    4298             :     {
    4299             :         // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
    4300             :         // which is indexed by lat, lon variables, but lat has irregular
    4301             :         // spacing.
    4302           1 :         const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
    4303           1 :         const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
    4304           1 :         if (bSwitchedXY)
    4305             :         {
    4306           0 :             std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4307           0 :             GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4308             :         }
    4309             : 
    4310           1 :         CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4311             :                  pszGeolocXFullName, pszGeolocYFullName);
    4312             : 
    4313           1 :         GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4314             :                                         "GEOLOCATION");
    4315             : 
    4316           2 :         CPLString osTMP;
    4317           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4318           1 :                      pszGeolocXFullName);
    4319             : 
    4320           1 :         GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4321           1 :         GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4322           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4323           1 :                      pszGeolocYFullName);
    4324             : 
    4325           1 :         GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4326           1 :         GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4327             : 
    4328           1 :         GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4329           1 :         GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4330             : 
    4331           1 :         GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4332           1 :         GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4333             : 
    4334           1 :         GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4335             :                                         "PIXEL_CENTER", "GEOLOCATION");
    4336             :     }
    4337             : 
    4338             :     // Set GeoTransform if we got a complete one - after projection has been set
    4339         337 :     if (bGotCfGT || bGotGdalGT)
    4340             :     {
    4341         193 :         m_bAddedProjectionVarsDefs = true;
    4342         193 :         m_bAddedProjectionVarsData = true;
    4343         193 :         SetGeoTransformNoUpdate(adfTempGeoTransform);
    4344             :     }
    4345             : 
    4346             :     // Debugging reports.
    4347         337 :     CPLDebug("GDAL_netCDF",
    4348             :              "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
    4349             :              "bGotGdalSRS=%d bGotGdalGT=%d",
    4350             :              static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
    4351             :              static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
    4352             :              static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
    4353             : 
    4354         337 :     if (!bGotCfGT && !bGotGdalGT)
    4355         144 :         CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
    4356             : 
    4357         337 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
    4358         144 :         CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
    4359             : 
    4360             :     // wish of 6195
    4361             :     // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
    4362         337 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
    4363             :     {
    4364         211 :         if (bGotCfGT || bGotGdalGT)
    4365             :         {
    4366         134 :             bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
    4367          67 :                 papszOpenOptions, "ASSUME_LONGLAT",
    4368             :                 CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
    4369             : 
    4370           2 :             if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
    4371           2 :                 adfTempGeoTransform[0] < 360 &&
    4372           4 :                 (adfTempGeoTransform[0] +
    4373           2 :                  adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
    4374          71 :                 adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
    4375           4 :                 (adfTempGeoTransform[3] +
    4376           2 :                  adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
    4377             :             {
    4378             : 
    4379           2 :                 poDS->bIsGeographic = true;
    4380           2 :                 char *pszTempProjection = nullptr;
    4381             :                 // seems odd to use 4326 so OGC:CRS84
    4382           2 :                 oSRS.SetFromUserInput("OGC:CRS84");
    4383           2 :                 oSRS.exportToWkt(&pszTempProjection);
    4384           2 :                 if (returnProjStr != nullptr)
    4385             :                 {
    4386           0 :                     (*returnProjStr) = std::string(pszTempProjection);
    4387             :                 }
    4388             :                 else
    4389             :                 {
    4390           2 :                     m_bAddedProjectionVarsDefs = true;
    4391           2 :                     m_bAddedProjectionVarsData = true;
    4392           2 :                     SetSpatialRefNoUpdate(&oSRS);
    4393             :                 }
    4394           2 :                 CPLFree(pszTempProjection);
    4395             : 
    4396           2 :                 CPLDebug("netCDF",
    4397             :                          "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
    4398             :                          "none otherwise available and geotransform within "
    4399             :                          "suitable bounds. "
    4400             :                          "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
    4401             :                          "option or "
    4402             :                          "    ASSUME_LONGLAT=NO as open option to bypass this "
    4403             :                          "assumption.");
    4404             :             }
    4405             :         }
    4406             :     }
    4407             : 
    4408             : // Search for Well-known GeogCS if got only CF WKT
    4409             : // Disabled for now, as a named datum also include control points
    4410             : // (see mailing list and bug#4281
    4411             : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
    4412             : 
    4413             : // Disabled for now, but could be set in a config option.
    4414             : #if 0
    4415             :     bool bLookForWellKnownGCS = false;  // This could be a Config Option.
    4416             : 
    4417             :     if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
    4418             :     {
    4419             :         // ET - Could use a more exhaustive method by scanning all EPSG codes in
    4420             :         // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
    4421             :         // for comparing two WKT".
    4422             :         // This code could be contributed to a new function.
    4423             :         // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
    4424             :         //     const OGRSpatialReference *poOther) */
    4425             :         CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
    4426             :         const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
    4427             :         char *pszWKGCS = NULL;
    4428             :         oSRS.exportToPrettyWkt(&pszWKGCS);
    4429             :         for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
    4430             :         {
    4431             :             pszWKGCS = CPLStrdup(pszWKGCSList[i]);
    4432             :             OGRSpatialReference oSRSTmp;
    4433             :             oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
    4434             :             // Set datum to unknown, bug #4281.
    4435             :             if( oSRSTmp.GetAttrNode("DATUM" ) )
    4436             :                 oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
    4437             :             // Could use OGRSpatialReference::StripCTParms(), but let's keep
    4438             :             // TOWGS84.
    4439             :             oSRSTmp.GetRoot()->StripNodes("AXIS");
    4440             :             oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
    4441             :             oSRSTmp.GetRoot()->StripNodes("EXTENSION");
    4442             : 
    4443             :             oSRSTmp.exportToPrettyWkt(&pszWKGCS);
    4444             :             if( oSRS.IsSameGeogCS(&oSRSTmp) )
    4445             :             {
    4446             :                 oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
    4447             :                 oSRS.exportToWkt(&(pszTempProjection));
    4448             :                 SetProjection(pszTempProjection);
    4449             :                 CPLFree(pszTempProjection);
    4450             :             }
    4451             :         }
    4452             :     }
    4453             : #endif
    4454             : }
    4455             : 
    4456         110 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
    4457             :                                          bool bReadSRSOnly)
    4458             : {
    4459         110 :     SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
    4460             :                          nullptr, nullptr);
    4461         110 : }
    4462             : 
    4463         295 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
    4464             : {
    4465             :     // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
    4466             :     // and https://github.com/OSGeo/gdal/issues/7605
    4467             : 
    4468             :     // Check for a structure like:
    4469             :     /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
    4470             :         dimensions:
    4471             :             number_of_lines = 3248 ;
    4472             :             pixels_per_line = 3200 ;
    4473             :             [...]
    4474             :             pixel_control_points = 3200 ;
    4475             :         [...]
    4476             :         group: geophysical_data {
    4477             :           variables:
    4478             :             short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId
    4479             :                 [...]
    4480             :         }
    4481             :         group: navigation_data {
    4482             :           variables:
    4483             :             float longitude(number_of_lines, pixel_control_points) ;
    4484             :                 [...]
    4485             :             float latitude(number_of_lines, pixel_control_points) ;
    4486             :                 [...]
    4487             :         }
    4488             :     }
    4489             :     */
    4490             :     // Note that the longitude and latitude arrays are not indexed by the
    4491             :     // same dimensions. Handle only the case where
    4492             :     // pixel_control_points == pixels_per_line
    4493             :     // If there was a subsampling of the geolocation arrays, we'd need to
    4494             :     // add more logic.
    4495             : 
    4496         590 :     std::string osGroupName;
    4497         295 :     osGroupName.resize(NC_MAX_NAME);
    4498         295 :     NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
    4499         295 :     osGroupName.resize(strlen(osGroupName.data()));
    4500         295 :     if (osGroupName != "geophysical_data")
    4501         294 :         return false;
    4502             : 
    4503           1 :     int nVarDims = 0;
    4504           1 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4505           1 :     if (nVarDims != 2)
    4506           0 :         return false;
    4507             : 
    4508           1 :     int nNavigationDataGrpId = 0;
    4509           1 :     if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
    4510             :         NC_NOERR)
    4511           0 :         return false;
    4512             : 
    4513             :     std::array<int, 2> anVarDimIds;
    4514           1 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4515             : 
    4516           1 :     int nLongitudeId = 0;
    4517           1 :     int nLatitudeId = 0;
    4518           1 :     if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
    4519           2 :             NC_NOERR ||
    4520           1 :         nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
    4521             :             NC_NOERR)
    4522             :     {
    4523           0 :         return false;
    4524             :     }
    4525             : 
    4526           1 :     int nDimsLongitude = 0;
    4527           1 :     NCDF_ERR(
    4528             :         nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
    4529           1 :     int nDimsLatitude = 0;
    4530           1 :     NCDF_ERR(
    4531             :         nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
    4532           1 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4533             :     {
    4534           0 :         return false;
    4535             :     }
    4536             : 
    4537             :     std::array<int, 2> anDimLongitudeIds;
    4538           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
    4539             :                              anDimLongitudeIds.data()));
    4540             :     std::array<int, 2> anDimLatitudeIds;
    4541           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
    4542             :                              anDimLatitudeIds.data()));
    4543           1 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4544             :     {
    4545           0 :         return false;
    4546             :     }
    4547             : 
    4548             :     std::array<size_t, 2> anSizeVarDimIds;
    4549             :     std::array<size_t, 2> anSizeLongLatIds;
    4550           2 :     if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
    4551           1 :               NC_NOERR &&
    4552           1 :           nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
    4553           1 :               NC_NOERR &&
    4554           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
    4555           1 :               NC_NOERR &&
    4556           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
    4557             :               NC_NOERR &&
    4558           1 :           anSizeVarDimIds == anSizeLongLatIds))
    4559             :     {
    4560           0 :         return false;
    4561             :     }
    4562             : 
    4563           1 :     const char *pszGeolocXFullName = "/navigation_data/longitude";
    4564           1 :     const char *pszGeolocYFullName = "/navigation_data/latitude";
    4565             : 
    4566           1 :     if (bSwitchedXY)
    4567             :     {
    4568           0 :         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4569           0 :         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4570             :     }
    4571             : 
    4572           1 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4573             :              pszGeolocXFullName, pszGeolocYFullName);
    4574             : 
    4575           1 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4576             :                                     "GEOLOCATION");
    4577             : 
    4578           1 :     CPLString osTMP;
    4579           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4580             : 
    4581           1 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4582           1 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4583           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4584             : 
    4585           1 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4586           1 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4587             : 
    4588           1 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4589           1 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4590             : 
    4591           1 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4592           1 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4593             : 
    4594           1 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4595             :                                     "GEOLOCATION");
    4596           1 :     return true;
    4597             : }
    4598             : 
    4599         294 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
    4600             : {
    4601             :     // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
    4602             : 
    4603             :     // Check for a structure like:
    4604             :     /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
    4605             :         dimensions:
    4606             :             downtrack = 1280 ;
    4607             :             crosstrack = 1242 ;
    4608             :             bands = 285 ;
    4609             :             [...]
    4610             : 
    4611             :         variables:
    4612             :             float reflectance(downtrack, crosstrack, bands) ;
    4613             : 
    4614             :         group: location {
    4615             :           variables:
    4616             :                 double lon(downtrack, crosstrack) ;
    4617             :                         lon:_FillValue = -9999. ;
    4618             :                         lon:long_name = "Longitude (WGS-84)" ;
    4619             :                         lon:units = "degrees east" ;
    4620             :                 double lat(downtrack, crosstrack) ;
    4621             :                         lat:_FillValue = -9999. ;
    4622             :                         lat:long_name = "Latitude (WGS-84)" ;
    4623             :                         lat:units = "degrees north" ;
    4624             :           } // group location
    4625             : 
    4626             :     }
    4627             :     or
    4628             :     netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
    4629             :         dimensions:
    4630             :                 downtrack = 1664 ;
    4631             :                 crosstrack = 1242 ;
    4632             :                 [...]
    4633             :         variables:
    4634             :                 float group_1_band_depth(downtrack, crosstrack) ;
    4635             :                         group_1_band_depth:_FillValue = -9999.f ;
    4636             :                         group_1_band_depth:long_name = "Group 1 Band Depth" ;
    4637             :                         group_1_band_depth:units = "unitless" ;
    4638             :                 [...]
    4639             :         group: location {
    4640             :           variables:
    4641             :                 double lon(downtrack, crosstrack) ;
    4642             :                         lon:_FillValue = -9999. ;
    4643             :                         lon:long_name = "Longitude (WGS-84)" ;
    4644             :                         lon:units = "degrees east" ;
    4645             :                 double lat(downtrack, crosstrack) ;
    4646             :                         lat:_FillValue = -9999. ;
    4647             :                         lat:long_name = "Latitude (WGS-84)" ;
    4648             :                         lat:units = "degrees north" ;
    4649             :         }
    4650             :     */
    4651             : 
    4652         294 :     int nVarDims = 0;
    4653         294 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4654         294 :     if (nVarDims != 2 && nVarDims != 3)
    4655          14 :         return false;
    4656             : 
    4657         280 :     int nLocationGrpId = 0;
    4658         280 :     if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
    4659          56 :         return false;
    4660             : 
    4661             :     std::array<int, 3> anVarDimIds;
    4662         224 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4663         224 :     if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
    4664          21 :         return false;
    4665             : 
    4666         203 :     int nLongitudeId = 0;
    4667         203 :     int nLatitudeId = 0;
    4668         254 :     if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
    4669          51 :         nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
    4670             :     {
    4671         152 :         return false;
    4672             :     }
    4673             : 
    4674          51 :     int nDimsLongitude = 0;
    4675          51 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
    4676          51 :     int nDimsLatitude = 0;
    4677          51 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
    4678          51 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4679             :     {
    4680          31 :         return false;
    4681             :     }
    4682             : 
    4683             :     std::array<int, 2> anDimLongitudeIds;
    4684          20 :     NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
    4685             :                              anDimLongitudeIds.data()));
    4686             :     std::array<int, 2> anDimLatitudeIds;
    4687          20 :     NCDF_ERR(
    4688             :         nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
    4689          20 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4690             :     {
    4691           0 :         return false;
    4692             :     }
    4693             : 
    4694          40 :     if (anDimLongitudeIds[0] != anVarDimIds[0] ||
    4695          20 :         anDimLongitudeIds[1] != anVarDimIds[1])
    4696             :     {
    4697           0 :         return false;
    4698             :     }
    4699             : 
    4700          20 :     const char *pszGeolocXFullName = "/location/lon";
    4701          20 :     const char *pszGeolocYFullName = "/location/lat";
    4702             : 
    4703          20 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4704             :              pszGeolocXFullName, pszGeolocYFullName);
    4705             : 
    4706          20 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4707             :                                     "GEOLOCATION");
    4708             : 
    4709          20 :     CPLString osTMP;
    4710          20 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4711             : 
    4712          20 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4713          20 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4714          20 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4715             : 
    4716          20 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4717          20 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4718             : 
    4719          20 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4720          20 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4721             : 
    4722          20 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4723          20 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4724             : 
    4725          20 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4726             :                                     "GEOLOCATION");
    4727          20 :     return true;
    4728             : }
    4729             : 
    4730         337 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
    4731             :                                         const std::string &osGeolocWKT,
    4732             :                                         std::string &osGeolocXNameOut,
    4733             :                                         std::string &osGeolocYNameOut)
    4734             : {
    4735         337 :     bool bAddGeoloc = false;
    4736         337 :     char *pszTemp = nullptr;
    4737             : 
    4738         337 :     if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszTemp) == CE_None)
    4739             :     {
    4740             :         // Get X and Y geolocation names from coordinates attribute.
    4741          42 :         char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
    4742          42 :         if (CSLCount(papszTokens) >= 2)
    4743             :         {
    4744             :             char szGeolocXName[NC_MAX_NAME + 1];
    4745             :             char szGeolocYName[NC_MAX_NAME + 1];
    4746          39 :             szGeolocXName[0] = '\0';
    4747          39 :             szGeolocYName[0] = '\0';
    4748             : 
    4749             :             // Test that each variable is longitude/latitude.
    4750         126 :             for (int i = 0; i < CSLCount(papszTokens); i++)
    4751             :             {
    4752          87 :                 if (NCDFIsVarLongitude(nGroupId, -1, papszTokens[i]))
    4753             :                 {
    4754          32 :                     int nOtherGroupId = -1;
    4755          32 :                     int nOtherVarId = -1;
    4756             :                     // Check that the variable actually exists
    4757             :                     // Needed on Sentinel-3 products
    4758          32 :                     if (NCDFResolveVar(nGroupId, papszTokens[i], &nOtherGroupId,
    4759          32 :                                        &nOtherVarId) == CE_None)
    4760             :                     {
    4761          30 :                         snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
    4762          30 :                                  papszTokens[i]);
    4763             :                     }
    4764             :                 }
    4765          55 :                 else if (NCDFIsVarLatitude(nGroupId, -1, papszTokens[i]))
    4766             :                 {
    4767          32 :                     int nOtherGroupId = -1;
    4768          32 :                     int nOtherVarId = -1;
    4769             :                     // Check that the variable actually exists
    4770             :                     // Needed on Sentinel-3 products
    4771          32 :                     if (NCDFResolveVar(nGroupId, papszTokens[i], &nOtherGroupId,
    4772          32 :                                        &nOtherVarId) == CE_None)
    4773             :                     {
    4774          30 :                         snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
    4775          30 :                                  papszTokens[i]);
    4776             :                     }
    4777             :                 }
    4778             :             }
    4779             :             // Add GEOLOCATION metadata.
    4780          39 :             if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
    4781             :             {
    4782          30 :                 osGeolocXNameOut = szGeolocXName;
    4783          30 :                 osGeolocYNameOut = szGeolocYName;
    4784             : 
    4785          30 :                 char *pszGeolocXFullName = nullptr;
    4786          30 :                 char *pszGeolocYFullName = nullptr;
    4787          30 :                 if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
    4788          60 :                                            &pszGeolocXFullName) == CE_None &&
    4789          30 :                     NCDFResolveVarFullName(nGroupId, szGeolocYName,
    4790             :                                            &pszGeolocYFullName) == CE_None)
    4791             :                 {
    4792          30 :                     if (bSwitchedXY)
    4793             :                     {
    4794           2 :                         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4795           2 :                         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
    4796             :                                                         "GEOLOCATION");
    4797             :                     }
    4798             : 
    4799          30 :                     bAddGeoloc = true;
    4800          30 :                     CPLDebug("GDAL_netCDF",
    4801             :                              "using variables %s and %s for GEOLOCATION",
    4802             :                              pszGeolocXFullName, pszGeolocYFullName);
    4803             : 
    4804          30 :                     GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4805             :                                                     "GEOLOCATION");
    4806             : 
    4807          60 :                     CPLString osTMP;
    4808          30 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4809          30 :                                  pszGeolocXFullName);
    4810             : 
    4811          30 :                     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
    4812             :                                                     "GEOLOCATION");
    4813          30 :                     GDALPamDataset::SetMetadataItem("X_BAND", "1",
    4814             :                                                     "GEOLOCATION");
    4815          30 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4816          30 :                                  pszGeolocYFullName);
    4817             : 
    4818          30 :                     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
    4819             :                                                     "GEOLOCATION");
    4820          30 :                     GDALPamDataset::SetMetadataItem("Y_BAND", "1",
    4821             :                                                     "GEOLOCATION");
    4822             : 
    4823          30 :                     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
    4824             :                                                     "GEOLOCATION");
    4825          30 :                     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
    4826             :                                                     "GEOLOCATION");
    4827             : 
    4828          30 :                     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
    4829             :                                                     "GEOLOCATION");
    4830          30 :                     GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
    4831             :                                                     "GEOLOCATION");
    4832             : 
    4833          30 :                     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4834             :                                                     "PIXEL_CENTER",
    4835             :                                                     "GEOLOCATION");
    4836             :                 }
    4837             :                 else
    4838             :                 {
    4839           0 :                     CPLDebug("GDAL_netCDF",
    4840             :                              "cannot resolve location of "
    4841             :                              "lat/lon variables specified by the coordinates "
    4842             :                              "attribute [%s]",
    4843             :                              pszTemp);
    4844             :                 }
    4845          30 :                 CPLFree(pszGeolocXFullName);
    4846          30 :                 CPLFree(pszGeolocYFullName);
    4847             :             }
    4848             :             else
    4849             :             {
    4850           9 :                 CPLDebug("GDAL_netCDF",
    4851             :                          "coordinates attribute [%s] is unsupported", pszTemp);
    4852             :             }
    4853             :         }
    4854             :         else
    4855             :         {
    4856           3 :             CPLDebug("GDAL_netCDF",
    4857             :                      "coordinates attribute [%s] with %d element(s) is "
    4858             :                      "unsupported",
    4859             :                      pszTemp, CSLCount(papszTokens));
    4860             :         }
    4861          42 :         if (papszTokens)
    4862          42 :             CSLDestroy(papszTokens);
    4863             :     }
    4864             : 
    4865             :     else
    4866             :     {
    4867         295 :         bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
    4868             : 
    4869         295 :         if (!bAddGeoloc)
    4870         294 :             bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
    4871             :     }
    4872             : 
    4873         337 :     CPLFree(pszTemp);
    4874             : 
    4875         337 :     return bAddGeoloc;
    4876             : }
    4877             : 
    4878           8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
    4879             :                                        const char *szDimName)
    4880             : {
    4881             :     // Get values.
    4882           8 :     char *pszVarValues = nullptr;
    4883           8 :     CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
    4884           8 :     if (eErr != CE_None)
    4885           0 :         return eErr;
    4886             : 
    4887             :     // Write metadata.
    4888           8 :     char szTemp[NC_MAX_NAME + 1 + 32] = {};
    4889           8 :     snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
    4890           8 :     GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
    4891             : 
    4892           8 :     CPLFree(pszVarValues);
    4893             : 
    4894           8 :     return CE_None;
    4895             : }
    4896             : 
    4897           0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
    4898             :                                         int &nVarLen)
    4899             : {
    4900           0 :     nVarLen = 0;
    4901             : 
    4902             :     // Get Y_VALUES as tokens.
    4903             :     char **papszValues =
    4904           0 :         NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
    4905           0 :     if (papszValues == nullptr)
    4906           0 :         return nullptr;
    4907             : 
    4908             :     // Initialize and fill array.
    4909           0 :     nVarLen = CSLCount(papszValues);
    4910             :     double *pdfVarValues =
    4911           0 :         static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
    4912             : 
    4913           0 :     for (int i = 0, j = 0; i < nVarLen; i++)
    4914             :     {
    4915           0 :         if (!bBottomUp)
    4916           0 :             j = nVarLen - 1 - i;
    4917             :         else
    4918           0 :             j = i;  // Invert latitude values.
    4919           0 :         char *pszTemp = nullptr;
    4920           0 :         pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
    4921             :     }
    4922           0 :     CSLDestroy(papszValues);
    4923             : 
    4924           0 :     return pdfVarValues;
    4925             : }
    4926             : 
    4927             : /************************************************************************/
    4928             : /*                        SetSpatialRefNoUpdate()                       */
    4929             : /************************************************************************/
    4930             : 
    4931         244 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
    4932             : {
    4933         244 :     m_oSRS.Clear();
    4934         244 :     if (poSRS)
    4935         237 :         m_oSRS = *poSRS;
    4936         244 :     m_bHasProjection = true;
    4937         244 : }
    4938             : 
    4939             : /************************************************************************/
    4940             : /*                          SetSpatialRef()                             */
    4941             : /************************************************************************/
    4942             : 
    4943          73 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    4944             : {
    4945         146 :     CPLMutexHolderD(&hNCMutex);
    4946             : 
    4947          73 :     if (GetAccess() != GA_Update || m_bHasProjection)
    4948             :     {
    4949           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4950             :                  "netCDFDataset::_SetProjection() should only be called once "
    4951             :                  "in update mode!");
    4952           0 :         return CE_Failure;
    4953             :     }
    4954             : 
    4955          73 :     if (m_bHasGeoTransform)
    4956             :     {
    4957          32 :         SetSpatialRefNoUpdate(poSRS);
    4958             : 
    4959             :         // For NC4/NC4C, writing both projection variables and data,
    4960             :         // followed by redefining nodata value, cancels the projection
    4961             :         // info from the Band variable, so for now only write the
    4962             :         // variable definitions, and write data at the end.
    4963             :         // See https://trac.osgeo.org/gdal/ticket/7245
    4964          32 :         return AddProjectionVars(true, nullptr, nullptr);
    4965             :     }
    4966             : 
    4967          41 :     SetSpatialRefNoUpdate(poSRS);
    4968             : 
    4969          41 :     return CE_None;
    4970             : }
    4971             : 
    4972             : /************************************************************************/
    4973             : /*                     SetGeoTransformNoUpdate()                        */
    4974             : /************************************************************************/
    4975             : 
    4976         267 : void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
    4977             : {
    4978         267 :     memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
    4979         267 :     m_bHasGeoTransform = true;
    4980         267 : }
    4981             : 
    4982             : /************************************************************************/
    4983             : /*                          SetGeoTransform()                           */
    4984             : /************************************************************************/
    4985             : 
    4986          74 : CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
    4987             : {
    4988         148 :     CPLMutexHolderD(&hNCMutex);
    4989             : 
    4990          74 :     if (GetAccess() != GA_Update || m_bHasGeoTransform)
    4991             :     {
    4992           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4993             :                  "netCDFDataset::SetGeoTransform() should only be called once "
    4994             :                  "in update mode!");
    4995           0 :         return CE_Failure;
    4996             :     }
    4997             : 
    4998          74 :     CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
    4999          74 :              padfTransform[0], padfTransform[1], padfTransform[2],
    5000          74 :              padfTransform[3], padfTransform[4], padfTransform[5]);
    5001             : 
    5002          74 :     if (m_bHasProjection)
    5003             :     {
    5004           3 :         SetGeoTransformNoUpdate(padfTransform);
    5005             : 
    5006             :         // For NC4/NC4C, writing both projection variables and data,
    5007             :         // followed by redefining nodata value, cancels the projection
    5008             :         // info from the Band variable, so for now only write the
    5009             :         // variable definitions, and write data at the end.
    5010             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5011           3 :         return AddProjectionVars(true, nullptr, nullptr);
    5012             :     }
    5013             : 
    5014          71 :     SetGeoTransformNoUpdate(padfTransform);
    5015          71 :     return CE_None;
    5016             : }
    5017             : 
    5018             : /************************************************************************/
    5019             : /*                         NCDFWriteSRSVariable()                       */
    5020             : /************************************************************************/
    5021             : 
    5022         125 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
    5023             :                          char **ppszCFProjection, bool bWriteGDALTags,
    5024             :                          const std::string &srsVarName)
    5025             : {
    5026         125 :     char *pszCFProjection = nullptr;
    5027         125 :     char **papszKeyValues = nullptr;
    5028         125 :     poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
    5029             : 
    5030         125 :     if (bWriteGDALTags)
    5031             :     {
    5032         124 :         const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
    5033         124 :         if (pszWKT)
    5034             :         {
    5035             :             // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
    5036         124 :             papszKeyValues =
    5037         124 :                 CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
    5038             :         }
    5039             :     }
    5040             : 
    5041         125 :     const int nValues = CSLCount(papszKeyValues);
    5042             : 
    5043             :     int NCDFVarID;
    5044         250 :     std::string varNameRadix(pszCFProjection);
    5045         125 :     int nCounter = 2;
    5046             :     while (true)
    5047             :     {
    5048         127 :         NCDFVarID = -1;
    5049         127 :         nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
    5050         127 :         if (NCDFVarID < 0)
    5051         122 :             break;
    5052             : 
    5053           5 :         int nbAttr = 0;
    5054           5 :         NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
    5055           5 :         bool bSame = nbAttr == nValues;
    5056          41 :         for (int i = 0; bSame && (i < nbAttr); i++)
    5057             :         {
    5058             :             char szAttrName[NC_MAX_NAME + 1];
    5059          38 :             szAttrName[0] = 0;
    5060          38 :             NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
    5061             : 
    5062             :             const char *pszValue =
    5063          38 :                 CSLFetchNameValue(papszKeyValues, szAttrName);
    5064          38 :             if (!pszValue)
    5065             :             {
    5066           0 :                 bSame = false;
    5067           2 :                 break;
    5068             :             }
    5069             : 
    5070          38 :             nc_type atttype = NC_NAT;
    5071          38 :             size_t attlen = 0;
    5072          38 :             NCDF_ERR(
    5073             :                 nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
    5074          38 :             if (atttype != NC_CHAR && atttype != NC_DOUBLE)
    5075             :             {
    5076           0 :                 bSame = false;
    5077           0 :                 break;
    5078             :             }
    5079          38 :             if (atttype == NC_CHAR)
    5080             :             {
    5081          15 :                 if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
    5082             :                 {
    5083           0 :                     bSame = false;
    5084           0 :                     break;
    5085             :                 }
    5086          15 :                 std::string val;
    5087          15 :                 val.resize(attlen);
    5088          15 :                 nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
    5089          15 :                 if (val != pszValue)
    5090             :                 {
    5091           0 :                     bSame = false;
    5092           0 :                     break;
    5093             :                 }
    5094             :             }
    5095             :             else
    5096             :             {
    5097             :                 const CPLStringList aosTokens(
    5098          23 :                     CSLTokenizeString2(pszValue, ",", 0));
    5099          23 :                 if (static_cast<size_t>(aosTokens.size()) != attlen)
    5100             :                 {
    5101           0 :                     bSame = false;
    5102           0 :                     break;
    5103             :                 }
    5104             :                 double vals[2];
    5105          23 :                 nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
    5106          44 :                 if (vals[0] != CPLAtof(aosTokens[0]) ||
    5107          21 :                     (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
    5108             :                 {
    5109           2 :                     bSame = false;
    5110           2 :                     break;
    5111             :                 }
    5112             :             }
    5113             :         }
    5114           5 :         if (bSame)
    5115             :         {
    5116           3 :             *ppszCFProjection = pszCFProjection;
    5117           3 :             CSLDestroy(papszKeyValues);
    5118           3 :             return NCDFVarID;
    5119             :         }
    5120           2 :         CPLFree(pszCFProjection);
    5121           2 :         pszCFProjection =
    5122           2 :             CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
    5123           2 :         nCounter++;
    5124           2 :     }
    5125             : 
    5126         122 :     *ppszCFProjection = pszCFProjection;
    5127             : 
    5128             :     const char *pszVarName;
    5129             : 
    5130         122 :     if (srsVarName != "")
    5131             :     {
    5132          38 :         pszVarName = srsVarName.c_str();
    5133             :     }
    5134             :     else
    5135             :     {
    5136          84 :         pszVarName = pszCFProjection;
    5137             :     }
    5138             : 
    5139         122 :     int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
    5140         122 :     NCDF_ERR(status);
    5141        1170 :     for (int i = 0; i < nValues; ++i)
    5142             :     {
    5143        1048 :         char *pszKey = nullptr;
    5144        1048 :         const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
    5145        1048 :         if (pszKey && pszValue)
    5146             :         {
    5147        2096 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
    5148        1048 :             double adfValues[2] = {0, 0};
    5149        1048 :             const int nDoubleCount = std::min(2, aosTokens.size());
    5150        1048 :             if (!(aosTokens.size() == 2 &&
    5151        2095 :                   CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
    5152        1047 :                 CPLGetValueType(pszValue) == CPL_VALUE_STRING)
    5153             :             {
    5154         487 :                 status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
    5155             :                                          strlen(pszValue), pszValue);
    5156             :             }
    5157             :             else
    5158             :             {
    5159        1123 :                 for (int j = 0; j < nDoubleCount; ++j)
    5160         562 :                     adfValues[j] = CPLAtof(aosTokens[j]);
    5161         561 :                 status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
    5162             :                                            nDoubleCount, adfValues);
    5163             :             }
    5164        1048 :             NCDF_ERR(status);
    5165             :         }
    5166        1048 :         CPLFree(pszKey);
    5167             :     }
    5168             : 
    5169         122 :     CSLDestroy(papszKeyValues);
    5170         122 :     return NCDFVarID;
    5171             : }
    5172             : 
    5173             : /************************************************************************/
    5174             : /*                   NCDFWriteLonLatVarsAttributes()                    */
    5175             : /************************************************************************/
    5176             : 
    5177          99 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
    5178             :                                    int nVarLatID)
    5179             : {
    5180             : 
    5181             :     try
    5182             :     {
    5183          99 :         vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
    5184          99 :         vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
    5185          99 :         vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
    5186          99 :         vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
    5187          99 :         vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
    5188          99 :         vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
    5189             :     }
    5190           0 :     catch (nccfdriver::SG_Exception &e)
    5191             :     {
    5192           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5193             :     }
    5194          99 : }
    5195             : 
    5196             : /************************************************************************/
    5197             : /*                   NCDFWriteRLonRLatVarsAttributes()                    */
    5198             : /************************************************************************/
    5199             : 
    5200           0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
    5201             :                                      int nVarRLonID, int nVarRLatID)
    5202             : {
    5203             :     try
    5204             :     {
    5205           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
    5206           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
    5207             :                               "latitude in rotated pole grid");
    5208           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
    5209           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
    5210             : 
    5211           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
    5212           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
    5213             :                               "longitude in rotated pole grid");
    5214           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
    5215           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
    5216             :     }
    5217           0 :     catch (nccfdriver::SG_Exception &e)
    5218             :     {
    5219           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5220             :     }
    5221           0 : }
    5222             : 
    5223             : /************************************************************************/
    5224             : /*                        NCDFGetProjectedCFUnit()                      */
    5225             : /************************************************************************/
    5226             : 
    5227          39 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
    5228             : {
    5229          39 :     char *pszUnitsToWrite = nullptr;
    5230          39 :     poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
    5231          39 :     const std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
    5232          39 :     CPLFree(pszUnitsToWrite);
    5233          78 :     return osRet;
    5234             : }
    5235             : 
    5236             : /************************************************************************/
    5237             : /*                     NCDFWriteXYVarsAttributes()                      */
    5238             : /************************************************************************/
    5239             : 
    5240          28 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
    5241             :                                int nVarYID, const OGRSpatialReference *poSRS)
    5242             : {
    5243          56 :     const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
    5244             : 
    5245             :     try
    5246             :     {
    5247          28 :         vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
    5248          28 :         vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
    5249          28 :         if (!osUnitsToWrite.empty())
    5250          28 :             vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
    5251          28 :         vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
    5252          28 :         vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
    5253          28 :         if (!osUnitsToWrite.empty())
    5254          28 :             vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
    5255             :     }
    5256           0 :     catch (nccfdriver::SG_Exception &e)
    5257             :     {
    5258           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5259             :     }
    5260          28 : }
    5261             : 
    5262             : /************************************************************************/
    5263             : /*                          AddProjectionVars()                         */
    5264             : /************************************************************************/
    5265             : 
    5266         158 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
    5267             :                                         GDALProgressFunc pfnProgress,
    5268             :                                         void *pProgressData)
    5269             : {
    5270         158 :     if (nCFVersion >= 1.8)
    5271           0 :         return CE_None;  // do nothing
    5272             : 
    5273         158 :     bool bWriteGridMapping = false;
    5274         158 :     bool bWriteLonLat = false;
    5275         158 :     bool bHasGeoloc = false;
    5276         158 :     bool bWriteGDALTags = false;
    5277         158 :     bool bWriteGeoTransform = false;
    5278             : 
    5279             :     // For GEOLOCATION information.
    5280         158 :     GDALDatasetH hDS_X = nullptr;
    5281         158 :     GDALRasterBandH hBand_X = nullptr;
    5282         158 :     GDALDatasetH hDS_Y = nullptr;
    5283         158 :     GDALRasterBandH hBand_Y = nullptr;
    5284             : 
    5285         316 :     OGRSpatialReference oSRS(m_oSRS);
    5286         158 :     if (!m_oSRS.IsEmpty())
    5287             :     {
    5288         132 :         if (oSRS.IsProjected())
    5289          48 :             bIsProjected = true;
    5290          84 :         else if (oSRS.IsGeographic())
    5291          84 :             bIsGeographic = true;
    5292             :     }
    5293             : 
    5294         158 :     if (bDefsOnly)
    5295             :     {
    5296          79 :         char *pszProjection = nullptr;
    5297          79 :         m_oSRS.exportToWkt(&pszProjection);
    5298          79 :         CPLDebug("GDAL_netCDF",
    5299             :                  "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
    5300          79 :                  pszProjection ? pszProjection : "(null)",
    5301          79 :                  static_cast<int>(bIsProjected),
    5302          79 :                  static_cast<int>(bIsGeographic));
    5303          79 :         CPLFree(pszProjection);
    5304             : 
    5305          79 :         if (!m_bHasGeoTransform)
    5306           5 :             CPLDebug("GDAL_netCDF",
    5307             :                      "netCDFDataset::AddProjectionVars() called, "
    5308             :                      "but GeoTransform has not yet been defined!");
    5309             : 
    5310          79 :         if (!m_bHasProjection)
    5311           6 :             CPLDebug("GDAL_netCDF",
    5312             :                      "netCDFDataset::AddProjectionVars() called, "
    5313             :                      "but Projection has not yet been defined!");
    5314             :     }
    5315             : 
    5316             :     // Check GEOLOCATION information.
    5317         158 :     char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
    5318         158 :     if (papszGeolocationInfo != nullptr)
    5319             :     {
    5320             :         // Look for geolocation datasets.
    5321             :         const char *pszDSName =
    5322          10 :             CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
    5323          10 :         if (pszDSName != nullptr)
    5324          10 :             hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
    5325          10 :         pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
    5326          10 :         if (pszDSName != nullptr)
    5327          10 :             hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
    5328             : 
    5329          10 :         if (hDS_X != nullptr && hDS_Y != nullptr)
    5330             :         {
    5331          10 :             int nBand = std::max(1, atoi(CSLFetchNameValueDef(
    5332          10 :                                         papszGeolocationInfo, "X_BAND", "0")));
    5333          10 :             hBand_X = GDALGetRasterBand(hDS_X, nBand);
    5334          10 :             nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
    5335          10 :                                                           "Y_BAND", "0")));
    5336          10 :             hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
    5337             : 
    5338             :             // If geoloc bands are found, do basic validation based on their
    5339             :             // dimensions.
    5340          10 :             if (hBand_X != nullptr && hBand_Y != nullptr)
    5341             :             {
    5342          10 :                 int nXSize_XBand = GDALGetRasterXSize(hDS_X);
    5343          10 :                 int nYSize_XBand = GDALGetRasterYSize(hDS_X);
    5344          10 :                 int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
    5345          10 :                 int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
    5346             : 
    5347             :                 // TODO 1D geolocation arrays not implemented.
    5348          10 :                 if (nYSize_XBand == 1 && nYSize_YBand == 1)
    5349             :                 {
    5350           0 :                     bHasGeoloc = false;
    5351           0 :                     CPLDebug("GDAL_netCDF",
    5352             :                              "1D GEOLOCATION arrays not supported yet");
    5353             :                 }
    5354             :                 // 2D bands must have same sizes as the raster bands.
    5355          10 :                 else if (nXSize_XBand != nRasterXSize ||
    5356          10 :                          nYSize_XBand != nRasterYSize ||
    5357          10 :                          nXSize_YBand != nRasterXSize ||
    5358          10 :                          nYSize_YBand != nRasterYSize)
    5359             :                 {
    5360           0 :                     bHasGeoloc = false;
    5361           0 :                     CPLDebug("GDAL_netCDF",
    5362             :                              "GEOLOCATION array sizes (%dx%d %dx%d) differ "
    5363             :                              "from raster (%dx%d), not supported",
    5364             :                              nXSize_XBand, nYSize_XBand, nXSize_YBand,
    5365             :                              nYSize_YBand, nRasterXSize, nRasterYSize);
    5366             :                 }
    5367             :                 else
    5368             :                 {
    5369          10 :                     bHasGeoloc = true;
    5370          10 :                     CPLDebug("GDAL_netCDF",
    5371             :                              "dataset has GEOLOCATION information, will try to "
    5372             :                              "write it");
    5373             :                 }
    5374             :             }
    5375             :         }
    5376             :     }
    5377             : 
    5378             :     // Process projection options.
    5379         158 :     if (bIsProjected)
    5380             :     {
    5381             :         bool bIsCfProjection =
    5382          48 :             oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
    5383          48 :         bWriteGridMapping = true;
    5384          48 :         bWriteGDALTags = CPL_TO_BOOL(
    5385          48 :             CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
    5386             :         // Force WRITE_GDAL_TAGS if is not a CF projection.
    5387          48 :         if (!bWriteGDALTags && !bIsCfProjection)
    5388           0 :             bWriteGDALTags = true;
    5389          48 :         if (bWriteGDALTags)
    5390          48 :             bWriteGeoTransform = true;
    5391             : 
    5392             :         // Write lon/lat: default is NO, except if has geolocation.
    5393             :         // With IF_NEEDED: write if has geoloc or is not CF projection.
    5394             :         const char *pszValue =
    5395          48 :             CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
    5396          48 :         if (pszValue)
    5397             :         {
    5398           2 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5399             :             {
    5400           0 :                 bWriteLonLat = bHasGeoloc || !bIsCfProjection;
    5401             :             }
    5402             :             else
    5403             :             {
    5404           2 :                 bWriteLonLat = CPLTestBool(pszValue);
    5405             :             }
    5406             :         }
    5407             :         else
    5408             :         {
    5409          46 :             bWriteLonLat = bHasGeoloc;
    5410             :         }
    5411             : 
    5412             :         // Save value of pszCFCoordinates for later.
    5413          48 :         if (bWriteLonLat)
    5414             :         {
    5415           4 :             pszCFCoordinates = NCDF_LONLAT;
    5416             :         }
    5417             :     }
    5418             :     else
    5419             :     {
    5420             :         // Files without a Datum will not have a grid_mapping variable and
    5421             :         // geographic information.
    5422         110 :         bWriteGridMapping = bIsGeographic;
    5423             : 
    5424         110 :         if (bHasGeoloc)
    5425             :         {
    5426           8 :             bWriteLonLat = true;
    5427             :         }
    5428             :         else
    5429             :         {
    5430         102 :             bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
    5431         102 :                 papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
    5432         102 :             if (bWriteGDALTags)
    5433          84 :                 bWriteGeoTransform = true;
    5434             : 
    5435         102 :             const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
    5436             :                                                         "WRITE_LONLAT", "YES");
    5437         102 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5438           0 :                 bWriteLonLat = true;
    5439             :             else
    5440         102 :                 bWriteLonLat = CPLTestBool(pszValue);
    5441             :             //  Don't write lon/lat if no source geotransform.
    5442         102 :             if (!m_bHasGeoTransform)
    5443           0 :                 bWriteLonLat = false;
    5444             :             // If we don't write lon/lat, set dimnames to X/Y and write gdal
    5445             :             // tags.
    5446         102 :             if (!bWriteLonLat)
    5447             :             {
    5448           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5449             :                          "creating geographic file without lon/lat values!");
    5450           0 :                 if (m_bHasGeoTransform)
    5451             :                 {
    5452           0 :                     bWriteGDALTags = true;  // Not desirable if no geotransform.
    5453           0 :                     bWriteGeoTransform = true;
    5454             :                 }
    5455             :             }
    5456             :         }
    5457             :     }
    5458             : 
    5459             :     // Make sure we write grid_mapping if we need to write GDAL tags.
    5460         158 :     if (bWriteGDALTags)
    5461         132 :         bWriteGridMapping = true;
    5462             : 
    5463             :     // bottom-up value: new driver is bottom-up by default.
    5464             :     // Override with WRITE_BOTTOMUP.
    5465         158 :     bBottomUp = CPL_TO_BOOL(
    5466         158 :         CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
    5467             : 
    5468         158 :     if (bDefsOnly)
    5469             :     {
    5470          79 :         CPLDebug(
    5471             :             "GDAL_netCDF",
    5472             :             "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
    5473             :             "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
    5474          79 :             static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
    5475             :             static_cast<int>(bWriteGridMapping),
    5476             :             static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
    5477          79 :             static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
    5478             :     }
    5479             : 
    5480             :     // Exit if nothing to do.
    5481         158 :     if (!bIsProjected && !bWriteLonLat)
    5482           0 :         return CE_None;
    5483             : 
    5484             :     // Define dimension names.
    5485             : 
    5486         158 :     constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
    5487             : 
    5488         158 :     if (bDefsOnly)
    5489             :     {
    5490          79 :         int nVarLonID = -1;
    5491          79 :         int nVarLatID = -1;
    5492          79 :         int nVarXID = -1;
    5493          79 :         int nVarYID = -1;
    5494             : 
    5495          79 :         m_bAddedProjectionVarsDefs = true;
    5496             : 
    5497             :         // Make sure we are in define mode.
    5498          79 :         SetDefineMode(true);
    5499             : 
    5500             :         // Write projection attributes.
    5501          79 :         if (bWriteGridMapping)
    5502             :         {
    5503          66 :             const int NCDFVarID = NCDFWriteSRSVariable(
    5504             :                 cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
    5505          66 :             if (NCDFVarID < 0)
    5506           0 :                 return CE_Failure;
    5507             : 
    5508             :             // Optional GDAL custom projection tags.
    5509          66 :             if (bWriteGDALTags)
    5510             :             {
    5511         132 :                 CPLString osGeoTransform;
    5512         462 :                 for (int i = 0; i < 6; i++)
    5513             :                 {
    5514             :                     osGeoTransform +=
    5515         396 :                         CPLSPrintf("%.16g ", m_adfGeoTransform[i]);
    5516             :                 }
    5517          66 :                 CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
    5518             :                          osGeoTransform.c_str());
    5519             : 
    5520             :                 // if( strlen(pszProj4Defn) > 0 ) {
    5521             :                 //     nc_put_att_text(cdfid, NCDFVarID, "proj4",
    5522             :                 //                      strlen(pszProj4Defn), pszProj4Defn);
    5523             :                 // }
    5524             : 
    5525             :                 // For now, write the geotransform for back-compat or else
    5526             :                 // the old (1.8.1) driver overrides the CF geotransform with
    5527             :                 // empty values from dfNN, dfSN, dfEE, dfWE;
    5528             : 
    5529             :                 // TODO: fix this in 1.8 branch, and then remove this here.
    5530          66 :                 if (bWriteGeoTransform && m_bHasGeoTransform)
    5531             :                 {
    5532             :                     {
    5533          65 :                         const int status = nc_put_att_text(
    5534             :                             cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
    5535             :                             osGeoTransform.size(), osGeoTransform.c_str());
    5536          65 :                         NCDF_ERR(status);
    5537             :                     }
    5538             :                 }
    5539             :             }
    5540             : 
    5541             :             // Write projection variable to band variable.
    5542             :             // Need to call later if there are no bands.
    5543          66 :             AddGridMappingRef();
    5544             :         }  // end if( bWriteGridMapping )
    5545             : 
    5546             :         // Write CF Projection vars.
    5547             : 
    5548          79 :         const bool bIsRotatedPole =
    5549         145 :             pszCFProjection != nullptr &&
    5550          66 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5551          79 :         if (bIsRotatedPole)
    5552             :         {
    5553             :             // Rename dims to rlat/rlon.
    5554             :             papszDimName
    5555           0 :                 .Clear();  // If we add other dims one day, this has to change
    5556           0 :             papszDimName.AddString(NCDF_DIMNAME_RLAT);
    5557           0 :             papszDimName.AddString(NCDF_DIMNAME_RLON);
    5558             : 
    5559           0 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
    5560           0 :             NCDF_ERR(status);
    5561           0 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
    5562           0 :             NCDF_ERR(status);
    5563             :         }
    5564             :         // Rename dimensions if lon/lat.
    5565          79 :         else if (!bIsProjected && !bHasGeoloc)
    5566             :         {
    5567             :             // Rename dims to lat/lon.
    5568             :             papszDimName
    5569          51 :                 .Clear();  // If we add other dims one day, this has to change
    5570          51 :             papszDimName.AddString(NCDF_DIMNAME_LAT);
    5571          51 :             papszDimName.AddString(NCDF_DIMNAME_LON);
    5572             : 
    5573          51 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
    5574          51 :             NCDF_ERR(status);
    5575          51 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
    5576          51 :             NCDF_ERR(status);
    5577             :         }
    5578             : 
    5579             :         // Write X/Y attributes.
    5580             :         else /* if( bIsProjected || bHasGeoloc ) */
    5581             :         {
    5582             :             // X
    5583             :             int anXDims[1];
    5584          28 :             anXDims[0] = nXDimID;
    5585          28 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5586             :                      CF_PROJ_X_VAR_NAME, NC_DOUBLE);
    5587          28 :             int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
    5588             :                                     anXDims, &nVarXID);
    5589          28 :             NCDF_ERR(status);
    5590             : 
    5591             :             // Y
    5592             :             int anYDims[1];
    5593          28 :             anYDims[0] = nYDimID;
    5594          28 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5595             :                      CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
    5596          28 :             status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
    5597             :                                 anYDims, &nVarYID);
    5598          28 :             NCDF_ERR(status);
    5599             : 
    5600          28 :             if (bIsProjected)
    5601             :             {
    5602          24 :                 NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
    5603             :             }
    5604             :             else
    5605             :             {
    5606           4 :                 CPLAssert(bHasGeoloc);
    5607             :                 try
    5608             :                 {
    5609           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
    5610           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
    5611             :                                           "x-coordinate in Cartesian system");
    5612           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
    5613           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
    5614           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
    5615             :                                           "y-coordinate in Cartesian system");
    5616           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
    5617             : 
    5618           4 :                     pszCFCoordinates = NCDF_LONLAT;
    5619             :                 }
    5620           0 :                 catch (nccfdriver::SG_Exception &e)
    5621             :                 {
    5622           0 :                     CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5623           0 :                     return CE_Failure;
    5624             :                 }
    5625             :             }
    5626             :         }
    5627             : 
    5628             :         // Write lat/lon attributes if needed.
    5629          79 :         if (bWriteLonLat)
    5630             :         {
    5631          57 :             int *panLatDims = nullptr;
    5632          57 :             int *panLonDims = nullptr;
    5633          57 :             int nLatDims = -1;
    5634          57 :             int nLonDims = -1;
    5635             : 
    5636             :             // Get information.
    5637          57 :             if (bHasGeoloc)
    5638             :             {
    5639             :                 // Geoloc
    5640           5 :                 nLatDims = 2;
    5641             :                 panLatDims =
    5642           5 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5643           5 :                 panLatDims[0] = nYDimID;
    5644           5 :                 panLatDims[1] = nXDimID;
    5645           5 :                 nLonDims = 2;
    5646             :                 panLonDims =
    5647           5 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5648           5 :                 panLonDims[0] = nYDimID;
    5649           5 :                 panLonDims[1] = nXDimID;
    5650             :             }
    5651          52 :             else if (bIsProjected)
    5652             :             {
    5653             :                 // Projected
    5654           1 :                 nLatDims = 2;
    5655             :                 panLatDims =
    5656           1 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5657           1 :                 panLatDims[0] = nYDimID;
    5658           1 :                 panLatDims[1] = nXDimID;
    5659           1 :                 nLonDims = 2;
    5660             :                 panLonDims =
    5661           1 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5662           1 :                 panLonDims[0] = nYDimID;
    5663           1 :                 panLonDims[1] = nXDimID;
    5664             :             }
    5665             :             else
    5666             :             {
    5667             :                 // Geographic
    5668          51 :                 nLatDims = 1;
    5669             :                 panLatDims =
    5670          51 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5671          51 :                 panLatDims[0] = nYDimID;
    5672          51 :                 nLonDims = 1;
    5673             :                 panLonDims =
    5674          51 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5675          51 :                 panLonDims[0] = nXDimID;
    5676             :             }
    5677             : 
    5678          57 :             nc_type eLonLatType = NC_NAT;
    5679          57 :             if (bIsProjected)
    5680             :             {
    5681           2 :                 eLonLatType = NC_FLOAT;
    5682           4 :                 const char *pszValue = CSLFetchNameValueDef(
    5683           2 :                     papszCreationOptions, "TYPE_LONLAT", "FLOAT");
    5684           2 :                 if (EQUAL(pszValue, "DOUBLE"))
    5685           0 :                     eLonLatType = NC_DOUBLE;
    5686             :             }
    5687             :             else
    5688             :             {
    5689          55 :                 eLonLatType = NC_DOUBLE;
    5690         110 :                 const char *pszValue = CSLFetchNameValueDef(
    5691          55 :                     papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
    5692          55 :                 if (EQUAL(pszValue, "FLOAT"))
    5693           0 :                     eLonLatType = NC_FLOAT;
    5694             :             }
    5695             : 
    5696             :             // Def vars and attributes.
    5697             :             {
    5698          57 :                 const char *pszVarName =
    5699          57 :                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
    5700          57 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5701             :                                         nLatDims, panLatDims, &nVarLatID);
    5702          57 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5703             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
    5704          57 :                 NCDF_ERR(status);
    5705          57 :                 DefVarDeflate(nVarLatID, false);  // Don't set chunking.
    5706             :             }
    5707             : 
    5708             :             {
    5709          57 :                 const char *pszVarName =
    5710          57 :                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
    5711          57 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5712             :                                         nLonDims, panLonDims, &nVarLonID);
    5713          57 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5714             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
    5715          57 :                 NCDF_ERR(status);
    5716          57 :                 DefVarDeflate(nVarLonID, false);  // Don't set chunking.
    5717             :             }
    5718             : 
    5719          57 :             if (bIsRotatedPole)
    5720           0 :                 NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
    5721             :                                                 nVarLatID);
    5722             :             else
    5723          57 :                 NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
    5724             : 
    5725          57 :             CPLFree(panLatDims);
    5726          57 :             CPLFree(panLonDims);
    5727             :         }
    5728             :     }
    5729             : 
    5730         158 :     if (!bDefsOnly)
    5731             :     {
    5732          79 :         m_bAddedProjectionVarsData = true;
    5733             : 
    5734          79 :         int nVarXID = -1;
    5735          79 :         int nVarYID = -1;
    5736             : 
    5737          79 :         nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
    5738          79 :         nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
    5739             : 
    5740          79 :         int nVarLonID = -1;
    5741          79 :         int nVarLatID = -1;
    5742             : 
    5743          79 :         const bool bIsRotatedPole =
    5744         145 :             pszCFProjection != nullptr &&
    5745          66 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5746          79 :         nc_inq_varid(cdfid,
    5747             :                      bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
    5748             :                      &nVarLonID);
    5749          79 :         nc_inq_varid(cdfid,
    5750             :                      bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
    5751             :                      &nVarLatID);
    5752             : 
    5753             :         // Get projection values.
    5754             : 
    5755          79 :         double *padLonVal = nullptr;
    5756          79 :         double *padLatVal = nullptr;
    5757             : 
    5758          79 :         if (bIsProjected)
    5759             :         {
    5760          24 :             OGRSpatialReference *poLatLonSRS = nullptr;
    5761          24 :             OGRCoordinateTransformation *poTransform = nullptr;
    5762             : 
    5763             :             size_t startX[1];
    5764             :             size_t countX[1];
    5765             :             size_t startY[1];
    5766             :             size_t countY[1];
    5767             : 
    5768          24 :             CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
    5769             : 
    5770             :             double *padXVal =
    5771          24 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    5772             :             double *padYVal =
    5773          24 :                 static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
    5774             : 
    5775             :             // Get Y values.
    5776          24 :             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
    5777             :                                              // Invert latitude values.
    5778          24 :                                     m_adfGeoTransform[3] +
    5779          24 :                                         (m_adfGeoTransform[5] * nRasterYSize);
    5780          24 :             const double dfDY = m_adfGeoTransform[5];
    5781             : 
    5782        1435 :             for (int j = 0; j < nRasterYSize; j++)
    5783             :             {
    5784             :                 // The data point is centered inside the pixel.
    5785        1411 :                 if (!bBottomUp)
    5786           0 :                     padYVal[j] = dfY0 + (j + 0.5) * dfDY;
    5787             :                 else  // Invert latitude values.
    5788        1411 :                     padYVal[j] = dfY0 - (j + 0.5) * dfDY;
    5789             :             }
    5790          24 :             startX[0] = 0;
    5791          24 :             countX[0] = nRasterXSize;
    5792             : 
    5793             :             // Get X values.
    5794          24 :             const double dfX0 = m_adfGeoTransform[0];
    5795          24 :             const double dfDX = m_adfGeoTransform[1];
    5796             : 
    5797        1456 :             for (int i = 0; i < nRasterXSize; i++)
    5798             :             {
    5799             :                 // The data point is centered inside the pixel.
    5800        1432 :                 padXVal[i] = dfX0 + (i + 0.5) * dfDX;
    5801             :             }
    5802          24 :             startY[0] = 0;
    5803          24 :             countY[0] = nRasterYSize;
    5804             : 
    5805             :             // Write X/Y values.
    5806             : 
    5807             :             // Make sure we are in data mode.
    5808          24 :             SetDefineMode(false);
    5809             : 
    5810          24 :             CPLDebug("GDAL_netCDF", "Writing X values");
    5811             :             int status =
    5812          24 :                 nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
    5813          24 :             NCDF_ERR(status);
    5814             : 
    5815          24 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    5816             :             status =
    5817          24 :                 nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
    5818          24 :             NCDF_ERR(status);
    5819             : 
    5820          24 :             if (pfnProgress)
    5821          20 :                 pfnProgress(0.20, nullptr, pProgressData);
    5822             : 
    5823             :             // Write lon/lat arrays (CF coordinates) if requested.
    5824             : 
    5825             :             // Get OGR transform if GEOLOCATION is not available.
    5826          24 :             if (bWriteLonLat && !bHasGeoloc)
    5827             :             {
    5828           1 :                 poLatLonSRS = m_oSRS.CloneGeogCS();
    5829           1 :                 if (poLatLonSRS != nullptr)
    5830             :                 {
    5831           1 :                     poLatLonSRS->SetAxisMappingStrategy(
    5832             :                         OAMS_TRADITIONAL_GIS_ORDER);
    5833             :                     poTransform =
    5834           1 :                         OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
    5835             :                 }
    5836             :                 // If no OGR transform, then don't write CF lon/lat.
    5837           1 :                 if (poTransform == nullptr)
    5838             :                 {
    5839           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5840             :                              "Unable to get Coordinate Transform");
    5841           0 :                     bWriteLonLat = false;
    5842             :                 }
    5843             :             }
    5844             : 
    5845          24 :             if (bWriteLonLat)
    5846             :             {
    5847           2 :                 if (!bHasGeoloc)
    5848           1 :                     CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
    5849             :                 else
    5850           1 :                     CPLDebug("GDAL_netCDF",
    5851             :                              "Writing (lon,lat) from GEOLOCATION arrays");
    5852             : 
    5853           2 :                 bool bOK = true;
    5854           2 :                 double dfProgress = 0.2;
    5855             : 
    5856           2 :                 size_t start[] = {0, 0};
    5857           2 :                 size_t count[] = {1, (size_t)nRasterXSize};
    5858             :                 padLatVal = static_cast<double *>(
    5859           2 :                     CPLMalloc(nRasterXSize * sizeof(double)));
    5860             :                 padLonVal = static_cast<double *>(
    5861           2 :                     CPLMalloc(nRasterXSize * sizeof(double)));
    5862             : 
    5863          61 :                 for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
    5864             :                      j++)
    5865             :                 {
    5866          59 :                     start[0] = j;
    5867             : 
    5868             :                     // Get values from geotransform.
    5869          59 :                     if (!bHasGeoloc)
    5870             :                     {
    5871             :                         // Fill values to transform.
    5872         420 :                         for (int i = 0; i < nRasterXSize; i++)
    5873             :                         {
    5874         400 :                             padLatVal[i] = padYVal[j];
    5875         400 :                             padLonVal[i] = padXVal[i];
    5876             :                         }
    5877             : 
    5878             :                         // Do the transform.
    5879          20 :                         bOK = CPL_TO_BOOL(poTransform->Transform(
    5880          20 :                             nRasterXSize, padLonVal, padLatVal, nullptr));
    5881          20 :                         if (!bOK)
    5882             :                         {
    5883           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5884             :                                      "Unable to Transform (X,Y) to (lon,lat).");
    5885             :                         }
    5886             :                     }
    5887             :                     // Get values from geoloc arrays.
    5888             :                     else
    5889             :                     {
    5890          39 :                         CPLErr eErr = GDALRasterIO(
    5891             :                             hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
    5892             :                             nRasterXSize, 1, GDT_Float64, 0, 0);
    5893          39 :                         if (eErr == CE_None)
    5894             :                         {
    5895          39 :                             eErr = GDALRasterIO(
    5896             :                                 hBand_X, GF_Read, 0, j, nRasterXSize, 1,
    5897             :                                 padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
    5898             :                         }
    5899             : 
    5900          39 :                         if (eErr == CE_None)
    5901             :                         {
    5902          39 :                             bOK = true;
    5903             :                         }
    5904             :                         else
    5905             :                         {
    5906           0 :                             bOK = false;
    5907           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5908             :                                      "Unable to get scanline %d", j);
    5909             :                         }
    5910             :                     }
    5911             : 
    5912             :                     // Write data.
    5913          59 :                     if (bOK)
    5914             :                     {
    5915          59 :                         status = nc_put_vara_double(cdfid, nVarLatID, start,
    5916             :                                                     count, padLatVal);
    5917          59 :                         NCDF_ERR(status);
    5918          59 :                         status = nc_put_vara_double(cdfid, nVarLonID, start,
    5919             :                                                     count, padLonVal);
    5920          59 :                         NCDF_ERR(status);
    5921             :                     }
    5922             : 
    5923          59 :                     if (pfnProgress && (nRasterYSize / 10) > 0 &&
    5924          59 :                         (j % (nRasterYSize / 10) == 0))
    5925             :                     {
    5926          23 :                         dfProgress += 0.08;
    5927          23 :                         pfnProgress(dfProgress, nullptr, pProgressData);
    5928             :                     }
    5929             :                 }
    5930             :             }
    5931             : 
    5932          24 :             if (poLatLonSRS != nullptr)
    5933           1 :                 delete poLatLonSRS;
    5934          24 :             if (poTransform != nullptr)
    5935           1 :                 delete poTransform;
    5936             : 
    5937          24 :             CPLFree(padXVal);
    5938          24 :             CPLFree(padYVal);
    5939             :         }  // Projected
    5940             : 
    5941             :         // If not projected/geographic and has geoloc
    5942          55 :         else if (!bIsGeographic && bHasGeoloc)
    5943             :         {
    5944             :             // Use
    5945             :             // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
    5946             : 
    5947           4 :             bool bOK = true;
    5948           4 :             double dfProgress = 0.2;
    5949             : 
    5950             :             // Make sure we are in data mode.
    5951           4 :             SetDefineMode(false);
    5952             : 
    5953             :             size_t startX[1];
    5954             :             size_t countX[1];
    5955             :             size_t startY[1];
    5956             :             size_t countY[1];
    5957           4 :             startX[0] = 0;
    5958           4 :             countX[0] = nRasterXSize;
    5959             : 
    5960           4 :             startY[0] = 0;
    5961           4 :             countY[0] = nRasterYSize;
    5962             : 
    5963           8 :             std::vector<double> adfXVal(nRasterXSize);
    5964          16 :             for (int i = 0; i < nRasterXSize; i++)
    5965          12 :                 adfXVal[i] = i;
    5966             : 
    5967           8 :             std::vector<double> adfYVal(nRasterYSize);
    5968          12 :             for (int i = 0; i < nRasterYSize; i++)
    5969           8 :                 adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
    5970             : 
    5971           4 :             CPLDebug("GDAL_netCDF", "Writing X values");
    5972           4 :             int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
    5973           4 :                                             adfXVal.data());
    5974           4 :             NCDF_ERR(status);
    5975             : 
    5976           4 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    5977           4 :             status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
    5978           4 :                                         adfYVal.data());
    5979           4 :             NCDF_ERR(status);
    5980             : 
    5981           4 :             if (pfnProgress)
    5982           0 :                 pfnProgress(0.20, nullptr, pProgressData);
    5983             : 
    5984           4 :             size_t start[] = {0, 0};
    5985           4 :             size_t count[] = {1, (size_t)nRasterXSize};
    5986             :             padLatVal =
    5987           4 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    5988             :             padLonVal =
    5989           4 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    5990             : 
    5991          12 :             for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
    5992             :             {
    5993           8 :                 start[0] = j;
    5994             : 
    5995           8 :                 CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
    5996           8 :                                            bBottomUp ? nRasterYSize - 1 - j : j,
    5997             :                                            nRasterXSize, 1, padLatVal,
    5998             :                                            nRasterXSize, 1, GDT_Float64, 0, 0);
    5999           8 :                 if (eErr == CE_None)
    6000             :                 {
    6001           8 :                     eErr = GDALRasterIO(hBand_X, GF_Read, 0,
    6002           8 :                                         bBottomUp ? nRasterYSize - 1 - j : j,
    6003             :                                         nRasterXSize, 1, padLonVal,
    6004             :                                         nRasterXSize, 1, GDT_Float64, 0, 0);
    6005             :                 }
    6006             : 
    6007           8 :                 if (eErr == CE_None)
    6008             :                 {
    6009           8 :                     bOK = true;
    6010             :                 }
    6011             :                 else
    6012             :                 {
    6013           0 :                     bOK = false;
    6014           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    6015             :                              "Unable to get scanline %d", j);
    6016             :                 }
    6017             : 
    6018             :                 // Write data.
    6019           8 :                 if (bOK)
    6020             :                 {
    6021           8 :                     status = nc_put_vara_double(cdfid, nVarLatID, start, count,
    6022             :                                                 padLatVal);
    6023           8 :                     NCDF_ERR(status);
    6024           8 :                     status = nc_put_vara_double(cdfid, nVarLonID, start, count,
    6025             :                                                 padLonVal);
    6026           8 :                     NCDF_ERR(status);
    6027             :                 }
    6028             : 
    6029           8 :                 if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6030           0 :                     (j % (nRasterYSize / 10) == 0))
    6031             :                 {
    6032           0 :                     dfProgress += 0.08;
    6033           0 :                     pfnProgress(dfProgress, nullptr, pProgressData);
    6034             :                 }
    6035           4 :             }
    6036             :         }
    6037             : 
    6038             :         // If not projected, assume geographic to catch grids without Datum.
    6039          51 :         else if (bWriteLonLat)
    6040             :         {
    6041             :             // Get latitude values.
    6042          51 :             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
    6043             :                                              // Invert latitude values.
    6044          51 :                                     m_adfGeoTransform[3] +
    6045          51 :                                         (m_adfGeoTransform[5] * nRasterYSize);
    6046          51 :             const double dfDY = m_adfGeoTransform[5];
    6047             : 
    6048             :             // Override lat values with the ones in GEOLOCATION/Y_VALUES.
    6049          51 :             if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
    6050             :                 nullptr)
    6051             :             {
    6052           0 :                 int nTemp = 0;
    6053           0 :                 padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
    6054             :                 // Make sure we got the correct amount, if not fallback to GT */
    6055             :                 // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
    6056           0 :                 if (nTemp == nRasterYSize)
    6057             :                 {
    6058           0 :                     CPLDebug(
    6059             :                         "GDAL_netCDF",
    6060             :                         "Using Y_VALUES geolocation metadata for lat values");
    6061             :                 }
    6062             :                 else
    6063             :                 {
    6064           0 :                     CPLDebug("GDAL_netCDF",
    6065             :                              "Got %d elements from Y_VALUES geolocation "
    6066             :                              "metadata, need %d",
    6067             :                              nTemp, nRasterYSize);
    6068           0 :                     if (padLatVal)
    6069             :                     {
    6070           0 :                         CPLFree(padLatVal);
    6071           0 :                         padLatVal = nullptr;
    6072             :                     }
    6073             :                 }
    6074             :             }
    6075             : 
    6076          51 :             if (padLatVal == nullptr)
    6077             :             {
    6078             :                 padLatVal = static_cast<double *>(
    6079          51 :                     CPLMalloc(nRasterYSize * sizeof(double)));
    6080        3375 :                 for (int i = 0; i < nRasterYSize; i++)
    6081             :                 {
    6082             :                     // The data point is centered inside the pixel.
    6083        3324 :                     if (!bBottomUp)
    6084           0 :                         padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
    6085             :                     else  // Invert latitude values.
    6086        3324 :                         padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
    6087             :                 }
    6088             :             }
    6089             : 
    6090          51 :             size_t startLat[1] = {0};
    6091          51 :             size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
    6092             : 
    6093             :             // Get longitude values.
    6094          51 :             const double dfX0 = m_adfGeoTransform[0];
    6095          51 :             const double dfDX = m_adfGeoTransform[1];
    6096             : 
    6097             :             padLonVal =
    6098          51 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    6099        3427 :             for (int i = 0; i < nRasterXSize; i++)
    6100             :             {
    6101             :                 // The data point is centered inside the pixel.
    6102        3376 :                 padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
    6103             :             }
    6104             : 
    6105          51 :             size_t startLon[1] = {0};
    6106          51 :             size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
    6107             : 
    6108             :             // Write latitude and longitude values.
    6109             : 
    6110             :             // Make sure we are in data mode.
    6111          51 :             SetDefineMode(false);
    6112             : 
    6113             :             // Write values.
    6114          51 :             CPLDebug("GDAL_netCDF", "Writing lat values");
    6115             : 
    6116          51 :             int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
    6117             :                                             countLat, padLatVal);
    6118          51 :             NCDF_ERR(status);
    6119             : 
    6120          51 :             CPLDebug("GDAL_netCDF", "Writing lon values");
    6121          51 :             status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
    6122             :                                         padLonVal);
    6123          51 :             NCDF_ERR(status);
    6124             : 
    6125             :         }  // Not projected.
    6126             : 
    6127          79 :         CPLFree(padLatVal);
    6128          79 :         CPLFree(padLonVal);
    6129             : 
    6130          79 :         if (pfnProgress)
    6131          38 :             pfnProgress(1.00, nullptr, pProgressData);
    6132             :     }
    6133             : 
    6134         158 :     if (hDS_X != nullptr)
    6135             :     {
    6136          10 :         GDALClose(hDS_X);
    6137             :     }
    6138         158 :     if (hDS_Y != nullptr)
    6139             :     {
    6140          10 :         GDALClose(hDS_Y);
    6141             :     }
    6142             : 
    6143         158 :     return CE_None;
    6144             : }
    6145             : 
    6146             : // Write Projection variable to band variable.
    6147             : // Moved from AddProjectionVars() for cases when bands are added after
    6148             : // projection.
    6149         365 : bool netCDFDataset::AddGridMappingRef()
    6150             : {
    6151         365 :     bool bRet = true;
    6152         365 :     bool bOldDefineMode = bDefineMode;
    6153             : 
    6154         549 :     if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
    6155         184 :         ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
    6156         178 :          (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
    6157             :     {
    6158          70 :         bAddedGridMappingRef = true;
    6159             : 
    6160             :         // Make sure we are in define mode.
    6161          70 :         SetDefineMode(true);
    6162             : 
    6163         186 :         for (int i = 1; i <= nBands; i++)
    6164             :         {
    6165             :             const int nVarId =
    6166         116 :                 static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
    6167             : 
    6168         116 :             if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
    6169             :             {
    6170             :                 int status =
    6171         224 :                     nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
    6172         112 :                                     strlen(pszCFProjection), pszCFProjection);
    6173         112 :                 NCDF_ERR(status);
    6174         112 :                 if (status != NC_NOERR)
    6175           0 :                     bRet = false;
    6176             :             }
    6177         116 :             if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
    6178             :             {
    6179             :                 int status =
    6180           6 :                     nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
    6181             :                                     strlen(pszCFCoordinates), pszCFCoordinates);
    6182           6 :                 NCDF_ERR(status);
    6183           6 :                 if (status != NC_NOERR)
    6184           0 :                     bRet = false;
    6185             :             }
    6186             :         }
    6187             : 
    6188             :         // Go back to previous define mode.
    6189          70 :         SetDefineMode(bOldDefineMode);
    6190             :     }
    6191         365 :     return bRet;
    6192             : }
    6193             : 
    6194             : /************************************************************************/
    6195             : /*                          GetGeoTransform()                           */
    6196             : /************************************************************************/
    6197             : 
    6198         107 : CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
    6199             : 
    6200             : {
    6201         107 :     memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
    6202         107 :     if (m_bHasGeoTransform)
    6203          77 :         return CE_None;
    6204             : 
    6205          30 :     return GDALPamDataset::GetGeoTransform(padfTransform);
    6206             : }
    6207             : 
    6208             : /************************************************************************/
    6209             : /*                                rint()                                */
    6210             : /************************************************************************/
    6211             : 
    6212           0 : double netCDFDataset::rint(double dfX)
    6213             : {
    6214           0 :     return std::round(dfX);
    6215             : }
    6216             : 
    6217             : /************************************************************************/
    6218             : /*                          NCDFReadIsoMetadata()                       */
    6219             : /************************************************************************/
    6220             : 
    6221          16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
    6222             : {
    6223          16 :     int nbAttr = 0;
    6224          16 :     NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
    6225             : 
    6226          32 :     std::map<std::string, CPLJSONArray> oMapNameToArray;
    6227          40 :     for (int l = 0; l < nbAttr; l++)
    6228             :     {
    6229             :         char szAttrName[NC_MAX_NAME + 1];
    6230          24 :         szAttrName[0] = 0;
    6231          24 :         NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
    6232             : 
    6233          24 :         char *pszMetaValue = nullptr;
    6234          24 :         if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
    6235             :         {
    6236          24 :             nc_type nAttrType = NC_NAT;
    6237          24 :             size_t nAttrLen = 0;
    6238             : 
    6239          24 :             NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
    6240             :                                 &nAttrLen));
    6241             : 
    6242          24 :             std::string osAttrName(szAttrName);
    6243          24 :             const auto sharpPos = osAttrName.find('#');
    6244          24 :             if (sharpPos == std::string::npos)
    6245             :             {
    6246          16 :                 if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
    6247           4 :                     obj.Add(osAttrName, CPLAtof(pszMetaValue));
    6248             :                 else
    6249          12 :                     obj.Add(osAttrName, pszMetaValue);
    6250             :             }
    6251             :             else
    6252             :             {
    6253           8 :                 osAttrName.resize(sharpPos);
    6254           8 :                 auto iter = oMapNameToArray.find(osAttrName);
    6255           8 :                 if (iter == oMapNameToArray.end())
    6256             :                 {
    6257           8 :                     CPLJSONArray array;
    6258           4 :                     obj.Add(osAttrName, array);
    6259           4 :                     oMapNameToArray[osAttrName] = array;
    6260           4 :                     array.Add(pszMetaValue);
    6261             :                 }
    6262             :                 else
    6263             :                 {
    6264           4 :                     iter->second.Add(pszMetaValue);
    6265             :                 }
    6266             :             }
    6267          24 :             CPLFree(pszMetaValue);
    6268          24 :             pszMetaValue = nullptr;
    6269             :         }
    6270             :     }
    6271             : 
    6272          16 :     int nSubGroups = 0;
    6273          16 :     int *panSubGroupIds = nullptr;
    6274          16 :     NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
    6275          16 :     oMapNameToArray.clear();
    6276          28 :     for (int i = 0; i < nSubGroups; i++)
    6277             :     {
    6278          24 :         CPLJSONObject subObj;
    6279          12 :         NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
    6280             : 
    6281          24 :         std::string osGroupName;
    6282          12 :         osGroupName.resize(NC_MAX_NAME);
    6283          12 :         NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
    6284          12 :         osGroupName.resize(strlen(osGroupName.data()));
    6285          12 :         const auto sharpPos = osGroupName.find('#');
    6286          12 :         if (sharpPos == std::string::npos)
    6287             :         {
    6288           4 :             obj.Add(osGroupName, subObj);
    6289             :         }
    6290             :         else
    6291             :         {
    6292           8 :             osGroupName.resize(sharpPos);
    6293           8 :             auto iter = oMapNameToArray.find(osGroupName);
    6294           8 :             if (iter == oMapNameToArray.end())
    6295             :             {
    6296           8 :                 CPLJSONArray array;
    6297           4 :                 obj.Add(osGroupName, array);
    6298           4 :                 oMapNameToArray[osGroupName] = array;
    6299           4 :                 array.Add(subObj);
    6300             :             }
    6301             :             else
    6302             :             {
    6303           4 :                 iter->second.Add(subObj);
    6304             :             }
    6305             :         }
    6306             :     }
    6307          16 :     CPLFree(panSubGroupIds);
    6308          16 : }
    6309             : 
    6310           4 : std::string NCDFReadMetadataAsJson(int cdfid)
    6311             : {
    6312           8 :     CPLJSONDocument oDoc;
    6313           8 :     CPLJSONObject oRoot = oDoc.GetRoot();
    6314           4 :     NCDFReadMetadataAsJson(cdfid, oRoot);
    6315           8 :     return oDoc.SaveAsString();
    6316             : }
    6317             : 
    6318             : /************************************************************************/
    6319             : /*                        ReadAttributes()                              */
    6320             : /************************************************************************/
    6321        1726 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
    6322             : 
    6323             : {
    6324        1726 :     char *pszVarFullName = nullptr;
    6325        1726 :     ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
    6326             : 
    6327             :     // For metadata in Sentinel 5
    6328        1726 :     if (STARTS_WITH(pszVarFullName, "/METADATA/"))
    6329             :     {
    6330           6 :         for (const char *key :
    6331             :              {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
    6332           8 :               "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
    6333             :         {
    6334          14 :             if (var == NC_GLOBAL &&
    6335           7 :                 strcmp(pszVarFullName,
    6336             :                        CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
    6337             :             {
    6338           1 :                 CPLFree(pszVarFullName);
    6339           1 :                 CPLStringList aosList;
    6340           2 :                 aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
    6341           1 :                                       .replaceAll("\\/", '/'));
    6342           1 :                 m_oMapDomainToJSon[key] = std::move(aosList);
    6343           1 :                 return CE_None;
    6344             :             }
    6345             :         }
    6346             :     }
    6347        1725 :     if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
    6348             :     {
    6349           0 :         CPLFree(pszVarFullName);
    6350           0 :         CPLStringList aosList;
    6351             :         aosList.AddString(
    6352           0 :             CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
    6353           0 :         m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
    6354           0 :         return CE_None;
    6355             :     }
    6356             : 
    6357        1725 :     size_t nMetaNameSize =
    6358        1725 :         sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
    6359        1725 :     char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
    6360             : 
    6361        1725 :     int nbAttr = 0;
    6362        1725 :     NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
    6363             : 
    6364        8635 :     for (int l = 0; l < nbAttr; l++)
    6365             :     {
    6366             :         char szAttrName[NC_MAX_NAME + 1];
    6367        6910 :         szAttrName[0] = 0;
    6368        6910 :         NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
    6369        6910 :         snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
    6370             :                  szAttrName);
    6371             : 
    6372        6910 :         char *pszMetaTemp = nullptr;
    6373        6910 :         if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
    6374             :         {
    6375        6909 :             papszMetadata =
    6376        6909 :                 CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
    6377        6909 :             CPLFree(pszMetaTemp);
    6378        6909 :             pszMetaTemp = nullptr;
    6379             :         }
    6380             :         else
    6381             :         {
    6382           1 :             CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
    6383             :         }
    6384             :     }
    6385             : 
    6386        1725 :     CPLFree(pszVarFullName);
    6387        1725 :     CPLFree(pszMetaName);
    6388             : 
    6389        1725 :     if (var == NC_GLOBAL)
    6390             :     {
    6391             :         // Recurse on sub-groups.
    6392         509 :         int nSubGroups = 0;
    6393         509 :         int *panSubGroupIds = nullptr;
    6394         509 :         NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
    6395         538 :         for (int i = 0; i < nSubGroups; i++)
    6396             :         {
    6397          29 :             ReadAttributes(panSubGroupIds[i], var);
    6398             :         }
    6399         509 :         CPLFree(panSubGroupIds);
    6400             :     }
    6401             : 
    6402        1725 :     return CE_None;
    6403             : }
    6404             : 
    6405             : /************************************************************************/
    6406             : /*                netCDFDataset::CreateSubDatasetList()                 */
    6407             : /************************************************************************/
    6408          50 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
    6409             : {
    6410             :     char szVarStdName[NC_MAX_NAME + 1];
    6411          50 :     int *ponDimIds = nullptr;
    6412             :     nc_type nAttype;
    6413             :     size_t nAttlen;
    6414             : 
    6415          50 :     netCDFDataset *poDS = this;
    6416             : 
    6417             :     int nVarCount;
    6418          50 :     nc_inq_nvars(nGroupId, &nVarCount);
    6419             : 
    6420         324 :     for (int nVar = 0; nVar < nVarCount; nVar++)
    6421             :     {
    6422             : 
    6423             :         int nDims;
    6424         274 :         nc_inq_varndims(nGroupId, nVar, &nDims);
    6425             : 
    6426         274 :         if (nDims >= 2)
    6427             :         {
    6428         155 :             ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
    6429         155 :             nc_inq_vardimid(nGroupId, nVar, ponDimIds);
    6430             : 
    6431             :             // Create Sub dataset list.
    6432         155 :             CPLString osDim;
    6433         480 :             for (int i = 0; i < nDims; i++)
    6434             :             {
    6435             :                 size_t nDimLen;
    6436         325 :                 nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
    6437         325 :                 osDim += CPLSPrintf("%dx", (int)nDimLen);
    6438             :             }
    6439         155 :             CPLFree(ponDimIds);
    6440             : 
    6441             :             nc_type nVarType;
    6442         155 :             nc_inq_vartype(nGroupId, nVar, &nVarType);
    6443             :             // Get rid of the last "x" character.
    6444         155 :             osDim.pop_back();
    6445         155 :             const char *pszType = "";
    6446         155 :             switch (nVarType)
    6447             :             {
    6448          37 :                 case NC_BYTE:
    6449          37 :                     pszType = "8-bit integer";
    6450          37 :                     break;
    6451           2 :                 case NC_CHAR:
    6452           2 :                     pszType = "8-bit character";
    6453           2 :                     break;
    6454           6 :                 case NC_SHORT:
    6455           6 :                     pszType = "16-bit integer";
    6456           6 :                     break;
    6457          10 :                 case NC_INT:
    6458          10 :                     pszType = "32-bit integer";
    6459          10 :                     break;
    6460          51 :                 case NC_FLOAT:
    6461          51 :                     pszType = "32-bit floating-point";
    6462          51 :                     break;
    6463          31 :                 case NC_DOUBLE:
    6464          31 :                     pszType = "64-bit floating-point";
    6465          31 :                     break;
    6466           4 :                 case NC_UBYTE:
    6467           4 :                     pszType = "8-bit unsigned integer";
    6468           4 :                     break;
    6469           1 :                 case NC_USHORT:
    6470           1 :                     pszType = "16-bit unsigned integer";
    6471           1 :                     break;
    6472           1 :                 case NC_UINT:
    6473           1 :                     pszType = "32-bit unsigned integer";
    6474           1 :                     break;
    6475           1 :                 case NC_INT64:
    6476           1 :                     pszType = "64-bit integer";
    6477           1 :                     break;
    6478           1 :                 case NC_UINT64:
    6479           1 :                     pszType = "64-bit unsigned integer";
    6480           1 :                     break;
    6481          10 :                 default:
    6482          10 :                     break;
    6483             :             }
    6484             : 
    6485         155 :             char *pszName = nullptr;
    6486         155 :             if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
    6487           0 :                 continue;
    6488             : 
    6489         155 :             nSubDatasets++;
    6490             : 
    6491         155 :             nAttlen = 0;
    6492         155 :             nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
    6493         310 :             if (nAttlen < sizeof(szVarStdName) &&
    6494         155 :                 nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
    6495             :                     NC_NOERR)
    6496             :             {
    6497          51 :                 szVarStdName[nAttlen] = '\0';
    6498             :             }
    6499             :             else
    6500             :             {
    6501         104 :                 snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
    6502             :             }
    6503             : 
    6504             :             char szTemp[NC_MAX_NAME + 1];
    6505         155 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
    6506             :                      nSubDatasets);
    6507             : 
    6508         155 :             if (strchr(pszName, ' ') || strchr(pszName, ':'))
    6509             :             {
    6510           1 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6511             :                     poDS->papszSubDatasets, szTemp,
    6512             :                     CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
    6513             :                                pszName));
    6514             :             }
    6515             :             else
    6516             :             {
    6517         154 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6518             :                     poDS->papszSubDatasets, szTemp,
    6519             :                     CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
    6520             :                                pszName));
    6521             :             }
    6522             : 
    6523         155 :             CPLFree(pszName);
    6524             : 
    6525         155 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
    6526             :                      nSubDatasets);
    6527             : 
    6528         155 :             poDS->papszSubDatasets =
    6529         155 :                 CSLSetNameValue(poDS->papszSubDatasets, szTemp,
    6530             :                                 CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
    6531             :                                            szVarStdName, pszType));
    6532             :         }
    6533             :     }
    6534             : 
    6535             :     // Recurse on sub groups.
    6536          50 :     int nSubGroups = 0;
    6537          50 :     int *panSubGroupIds = nullptr;
    6538          50 :     NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
    6539          55 :     for (int i = 0; i < nSubGroups; i++)
    6540             :     {
    6541           5 :         CreateSubDatasetList(panSubGroupIds[i]);
    6542             :     }
    6543          50 :     CPLFree(panSubGroupIds);
    6544          50 : }
    6545             : 
    6546             : /************************************************************************/
    6547             : /*                            TestCapability()                          */
    6548             : /************************************************************************/
    6549             : 
    6550         248 : int netCDFDataset::TestCapability(const char *pszCap)
    6551             : {
    6552         248 :     if (EQUAL(pszCap, ODsCCreateLayer))
    6553             :     {
    6554         223 :         return eAccess == GA_Update && nBands == 0 &&
    6555         218 :                (eMultipleLayerBehavior != SINGLE_LAYER ||
    6556         229 :                 this->GetLayerCount() == 0 || bSGSupport);
    6557             :     }
    6558         136 :     else if (EQUAL(pszCap, ODsCZGeometries))
    6559           2 :         return true;
    6560             : 
    6561         134 :     return false;
    6562             : }
    6563             : 
    6564             : /************************************************************************/
    6565             : /*                            GetLayer()                                */
    6566             : /************************************************************************/
    6567             : 
    6568         384 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
    6569             : {
    6570         384 :     if (nIdx < 0 || nIdx >= this->GetLayerCount())
    6571           2 :         return nullptr;
    6572         382 :     return papoLayers[nIdx].get();
    6573             : }
    6574             : 
    6575             : /************************************************************************/
    6576             : /*                            ICreateLayer()                            */
    6577             : /************************************************************************/
    6578             : 
    6579          59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
    6580             :                                       const OGRGeomFieldDefn *poGeomFieldDefn,
    6581             :                                       CSLConstList papszOptions)
    6582             : {
    6583          59 :     int nLayerCDFId = cdfid;
    6584          59 :     if (!TestCapability(ODsCCreateLayer))
    6585           0 :         return nullptr;
    6586             : 
    6587          59 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    6588             :     const auto poSpatialRef =
    6589          59 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6590             : 
    6591         118 :     CPLString osNetCDFLayerName(pszName);
    6592          59 :     const netCDFWriterConfigLayer *poLayerConfig = nullptr;
    6593          59 :     if (oWriterConfig.m_bIsValid)
    6594             :     {
    6595             :         std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
    6596           2 :             oLayerIter = oWriterConfig.m_oLayers.find(pszName);
    6597           2 :         if (oLayerIter != oWriterConfig.m_oLayers.end())
    6598             :         {
    6599           1 :             poLayerConfig = &(oLayerIter->second);
    6600           1 :             osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
    6601             :         }
    6602             :     }
    6603             : 
    6604          59 :     netCDFDataset *poLayerDataset = nullptr;
    6605          59 :     if (eMultipleLayerBehavior == SEPARATE_FILES)
    6606             :     {
    6607           2 :         char **papszDatasetOptions = nullptr;
    6608           2 :         papszDatasetOptions = CSLSetNameValue(
    6609             :             papszDatasetOptions, "CONFIG_FILE",
    6610           2 :             CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
    6611             :         papszDatasetOptions =
    6612           2 :             CSLSetNameValue(papszDatasetOptions, "FORMAT",
    6613           2 :                             CSLFetchNameValue(papszCreationOptions, "FORMAT"));
    6614           2 :         papszDatasetOptions = CSLSetNameValue(
    6615             :             papszDatasetOptions, "WRITE_GDAL_TAGS",
    6616           2 :             CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
    6617             :         CPLString osLayerFilename(
    6618           2 :             CPLFormFilename(osFilename, osNetCDFLayerName, "nc"));
    6619           2 :         CPLAcquireMutex(hNCMutex, 1000.0);
    6620           2 :         poLayerDataset =
    6621           2 :             CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
    6622           2 :         CPLReleaseMutex(hNCMutex);
    6623           2 :         CSLDestroy(papszDatasetOptions);
    6624           2 :         if (poLayerDataset == nullptr)
    6625           0 :             return nullptr;
    6626             : 
    6627           2 :         nLayerCDFId = poLayerDataset->cdfid;
    6628           2 :         NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
    6629           2 :                            bWriteGDALHistory, "", "Create",
    6630             :                            NCDF_CONVENTIONS_CF_V1_6);
    6631             :     }
    6632          57 :     else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
    6633             :     {
    6634           2 :         SetDefineMode(true);
    6635             : 
    6636           2 :         nLayerCDFId = -1;
    6637           2 :         int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
    6638           2 :         NCDF_ERR(status);
    6639           2 :         if (status != NC_NOERR)
    6640           0 :             return nullptr;
    6641             : 
    6642           2 :         NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
    6643           2 :                            bWriteGDALHistory, "", "Create",
    6644             :                            NCDF_CONVENTIONS_CF_V1_6);
    6645             :     }
    6646             : 
    6647             :     // Make a clone to workaround a bug in released MapServer versions
    6648             :     // that destroys the passed SRS instead of releasing it .
    6649          59 :     OGRSpatialReference *poSRS = nullptr;
    6650          59 :     if (poSpatialRef)
    6651             :     {
    6652          43 :         poSRS = poSpatialRef->Clone();
    6653          43 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6654             :     }
    6655             :     std::shared_ptr<netCDFLayer> poLayer(
    6656          59 :         new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
    6657         118 :                         osNetCDFLayerName, eGType, poSRS));
    6658          59 :     if (poSRS != nullptr)
    6659          43 :         poSRS->Release();
    6660             : 
    6661             :     // Fetch layer creation options coming from config file
    6662          59 :     char **papszNewOptions = CSLDuplicate(papszOptions);
    6663          59 :     if (oWriterConfig.m_bIsValid)
    6664             :     {
    6665           2 :         std::map<CPLString, CPLString>::const_iterator oIter;
    6666           3 :         for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
    6667           3 :              oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
    6668             :         {
    6669             :             papszNewOptions =
    6670           1 :                 CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
    6671             :         }
    6672           2 :         if (poLayerConfig != nullptr)
    6673             :         {
    6674           3 :             for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
    6675           3 :                  oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
    6676             :             {
    6677           2 :                 papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
    6678           2 :                                                   oIter->second);
    6679             :             }
    6680             :         }
    6681             :     }
    6682             : 
    6683          59 :     const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
    6684          59 :     CSLDestroy(papszNewOptions);
    6685             : 
    6686          59 :     if (!bRet)
    6687             :     {
    6688           0 :         return nullptr;
    6689             :     }
    6690             : 
    6691          59 :     if (poLayerDataset != nullptr)
    6692           2 :         apoVectorDatasets.push_back(poLayerDataset);
    6693             : 
    6694          59 :     papoLayers.push_back(poLayer);
    6695          59 :     return poLayer.get();
    6696             : }
    6697             : 
    6698             : /************************************************************************/
    6699             : /*                           CloneAttributes()                          */
    6700             : /************************************************************************/
    6701             : 
    6702         137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
    6703             :                                     int nDstVarId)
    6704             : {
    6705         137 :     int nAttCount = -1;
    6706         137 :     int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
    6707         137 :     NCDF_ERR(status);
    6708             : 
    6709         693 :     for (int i = 0; i < nAttCount; i++)
    6710             :     {
    6711             :         char szName[NC_MAX_NAME + 1];
    6712         556 :         szName[0] = 0;
    6713         556 :         status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
    6714         556 :         NCDF_ERR(status);
    6715             : 
    6716             :         status =
    6717         556 :             nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
    6718         556 :         NCDF_ERR(status);
    6719         556 :         if (status != NC_NOERR)
    6720           0 :             return false;
    6721             :     }
    6722             : 
    6723         137 :     return true;
    6724             : }
    6725             : 
    6726             : /************************************************************************/
    6727             : /*                          CloneVariableContent()                      */
    6728             : /************************************************************************/
    6729             : 
    6730         121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
    6731             :                                          int nSrcVarId, int nDstVarId)
    6732             : {
    6733         121 :     int nVarDimCount = -1;
    6734         121 :     int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
    6735         121 :     NCDF_ERR(status);
    6736         121 :     int anDimIds[] = {-1, 1};
    6737         121 :     status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
    6738         121 :     NCDF_ERR(status);
    6739         121 :     nc_type nc_datatype = NC_NAT;
    6740         121 :     status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
    6741         121 :     NCDF_ERR(status);
    6742         121 :     size_t nTypeSize = 0;
    6743         121 :     switch (nc_datatype)
    6744             :     {
    6745          35 :         case NC_BYTE:
    6746             :         case NC_CHAR:
    6747          35 :             nTypeSize = 1;
    6748          35 :             break;
    6749           4 :         case NC_SHORT:
    6750           4 :             nTypeSize = 2;
    6751           4 :             break;
    6752          24 :         case NC_INT:
    6753          24 :             nTypeSize = 4;
    6754          24 :             break;
    6755           4 :         case NC_FLOAT:
    6756           4 :             nTypeSize = 4;
    6757           4 :             break;
    6758          43 :         case NC_DOUBLE:
    6759          43 :             nTypeSize = 8;
    6760          43 :             break;
    6761           2 :         case NC_UBYTE:
    6762           2 :             nTypeSize = 1;
    6763           2 :             break;
    6764           2 :         case NC_USHORT:
    6765           2 :             nTypeSize = 2;
    6766           2 :             break;
    6767           2 :         case NC_UINT:
    6768           2 :             nTypeSize = 4;
    6769           2 :             break;
    6770           4 :         case NC_INT64:
    6771             :         case NC_UINT64:
    6772           4 :             nTypeSize = 8;
    6773           4 :             break;
    6774           1 :         case NC_STRING:
    6775           1 :             nTypeSize = sizeof(char *);
    6776           1 :             break;
    6777           0 :         default:
    6778             :         {
    6779           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
    6780             :                      nc_datatype);
    6781           0 :             return false;
    6782             :         }
    6783             :     }
    6784             : 
    6785         121 :     size_t nElems = 1;
    6786             :     size_t anStart[NC_MAX_DIMS];
    6787             :     size_t anCount[NC_MAX_DIMS];
    6788         121 :     size_t nRecords = 1;
    6789         261 :     for (int i = 0; i < nVarDimCount; i++)
    6790             :     {
    6791         140 :         anStart[i] = 0;
    6792         140 :         if (i == 0)
    6793             :         {
    6794         116 :             anCount[i] = 1;
    6795         116 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
    6796         116 :             NCDF_ERR(status);
    6797             :         }
    6798             :         else
    6799             :         {
    6800          24 :             anCount[i] = 0;
    6801          24 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
    6802          24 :             NCDF_ERR(status);
    6803          24 :             nElems *= anCount[i];
    6804             :         }
    6805             :     }
    6806             : 
    6807             :     /* Workaround in some cases a netCDF bug:
    6808             :      * https://github.com/Unidata/netcdf-c/pull/1442 */
    6809         121 :     if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
    6810             :     {
    6811         119 :         nElems *= nRecords;
    6812         119 :         anCount[0] = nRecords;
    6813         119 :         nRecords = 1;
    6814             :     }
    6815             : 
    6816         121 :     void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
    6817         121 :     if (pBuffer == nullptr)
    6818           0 :         return false;
    6819             : 
    6820         240 :     for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
    6821             :     {
    6822         119 :         anStart[0] = iRecord;
    6823             : 
    6824         119 :         switch (nc_datatype)
    6825             :         {
    6826           5 :             case NC_BYTE:
    6827             :                 status =
    6828           5 :                     nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
    6829             :                                       static_cast<signed char *>(pBuffer));
    6830           5 :                 if (!status)
    6831           5 :                     status = nc_put_vara_schar(
    6832             :                         new_cdfid, nDstVarId, anStart, anCount,
    6833             :                         static_cast<signed char *>(pBuffer));
    6834           5 :                 break;
    6835          28 :             case NC_CHAR:
    6836             :                 status =
    6837          28 :                     nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
    6838             :                                      static_cast<char *>(pBuffer));
    6839          28 :                 if (!status)
    6840             :                     status =
    6841          28 :                         nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
    6842             :                                          static_cast<char *>(pBuffer));
    6843          28 :                 break;
    6844           4 :             case NC_SHORT:
    6845             :                 status =
    6846           4 :                     nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
    6847             :                                       static_cast<short *>(pBuffer));
    6848           4 :                 if (!status)
    6849           4 :                     status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
    6850             :                                                anCount,
    6851             :                                                static_cast<short *>(pBuffer));
    6852           4 :                 break;
    6853          24 :             case NC_INT:
    6854          24 :                 status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
    6855             :                                          static_cast<int *>(pBuffer));
    6856          24 :                 if (!status)
    6857             :                     status =
    6858          24 :                         nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
    6859             :                                         static_cast<int *>(pBuffer));
    6860          24 :                 break;
    6861           4 :             case NC_FLOAT:
    6862             :                 status =
    6863           4 :                     nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
    6864             :                                       static_cast<float *>(pBuffer));
    6865           4 :                 if (!status)
    6866           4 :                     status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
    6867             :                                                anCount,
    6868             :                                                static_cast<float *>(pBuffer));
    6869           4 :                 break;
    6870          43 :             case NC_DOUBLE:
    6871             :                 status =
    6872          43 :                     nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
    6873             :                                        static_cast<double *>(pBuffer));
    6874          43 :                 if (!status)
    6875          43 :                     status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
    6876             :                                                 anCount,
    6877             :                                                 static_cast<double *>(pBuffer));
    6878          43 :                 break;
    6879           1 :             case NC_STRING:
    6880             :                 status =
    6881           1 :                     nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
    6882             :                                        static_cast<char **>(pBuffer));
    6883           1 :                 if (!status)
    6884             :                 {
    6885           1 :                     status = nc_put_vara_string(
    6886             :                         new_cdfid, nDstVarId, anStart, anCount,
    6887             :                         static_cast<const char **>(pBuffer));
    6888           1 :                     nc_free_string(nElems, static_cast<char **>(pBuffer));
    6889             :                 }
    6890           1 :                 break;
    6891             : 
    6892           2 :             case NC_UBYTE:
    6893             :                 status =
    6894           2 :                     nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
    6895             :                                       static_cast<unsigned char *>(pBuffer));
    6896           2 :                 if (!status)
    6897           2 :                     status = nc_put_vara_uchar(
    6898             :                         new_cdfid, nDstVarId, anStart, anCount,
    6899             :                         static_cast<unsigned char *>(pBuffer));
    6900           2 :                 break;
    6901           2 :             case NC_USHORT:
    6902             :                 status =
    6903           2 :                     nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
    6904             :                                        static_cast<unsigned short *>(pBuffer));
    6905           2 :                 if (!status)
    6906           2 :                     status = nc_put_vara_ushort(
    6907             :                         new_cdfid, nDstVarId, anStart, anCount,
    6908             :                         static_cast<unsigned short *>(pBuffer));
    6909           2 :                 break;
    6910           2 :             case NC_UINT:
    6911             :                 status =
    6912           2 :                     nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
    6913             :                                      static_cast<unsigned int *>(pBuffer));
    6914           2 :                 if (!status)
    6915             :                     status =
    6916           2 :                         nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
    6917             :                                          static_cast<unsigned int *>(pBuffer));
    6918           2 :                 break;
    6919           2 :             case NC_INT64:
    6920             :                 status =
    6921           2 :                     nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
    6922             :                                          static_cast<long long *>(pBuffer));
    6923           2 :                 if (!status)
    6924           2 :                     status = nc_put_vara_longlong(
    6925             :                         new_cdfid, nDstVarId, anStart, anCount,
    6926             :                         static_cast<long long *>(pBuffer));
    6927           2 :                 break;
    6928           2 :             case NC_UINT64:
    6929           2 :                 status = nc_get_vara_ulonglong(
    6930             :                     old_cdfid, nSrcVarId, anStart, anCount,
    6931             :                     static_cast<unsigned long long *>(pBuffer));
    6932           2 :                 if (!status)
    6933           2 :                     status = nc_put_vara_ulonglong(
    6934             :                         new_cdfid, nDstVarId, anStart, anCount,
    6935             :                         static_cast<unsigned long long *>(pBuffer));
    6936           2 :                 break;
    6937           0 :             default:
    6938           0 :                 status = NC_EBADTYPE;
    6939             :         }
    6940             : 
    6941         119 :         NCDF_ERR(status);
    6942         119 :         if (status != NC_NOERR)
    6943             :         {
    6944           0 :             VSIFree(pBuffer);
    6945           0 :             return false;
    6946             :         }
    6947             :     }
    6948             : 
    6949         121 :     VSIFree(pBuffer);
    6950         121 :     return true;
    6951             : }
    6952             : 
    6953             : /************************************************************************/
    6954             : /*                         NCDFIsUnlimitedDim()                         */
    6955             : /************************************************************************/
    6956             : 
    6957          57 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
    6958             : {
    6959          57 :     if (bIsNC4)
    6960             :     {
    6961          16 :         int nUnlimitedDims = 0;
    6962          16 :         nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
    6963          16 :         bool bFound = false;
    6964          16 :         if (nUnlimitedDims)
    6965             :         {
    6966             :             int *panUnlimitedDimIds =
    6967          16 :                 static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
    6968          16 :             nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
    6969          30 :             for (int i = 0; i < nUnlimitedDims; i++)
    6970             :             {
    6971          22 :                 if (panUnlimitedDimIds[i] == nDimId)
    6972             :                 {
    6973           8 :                     bFound = true;
    6974           8 :                     break;
    6975             :                 }
    6976             :             }
    6977          16 :             CPLFree(panUnlimitedDimIds);
    6978             :         }
    6979          16 :         return bFound;
    6980             :     }
    6981             :     else
    6982             :     {
    6983          41 :         int nUnlimitedDimId = -1;
    6984          41 :         nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
    6985          41 :         return nDimId == nUnlimitedDimId;
    6986             :     }
    6987             : }
    6988             : 
    6989             : /************************************************************************/
    6990             : /*                              CloneGrp()                              */
    6991             : /************************************************************************/
    6992             : 
    6993          16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
    6994             :                              int nLayerId, int nDimIdToGrow, size_t nNewSize)
    6995             : {
    6996             :     // Clone dimensions
    6997          16 :     int nDimCount = -1;
    6998          16 :     int status = nc_inq_ndims(nOldGrpId, &nDimCount);
    6999          16 :     NCDF_ERR(status);
    7000          16 :     if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
    7001           0 :         return false;
    7002             :     int anDimIds[NC_MAX_DIMS];
    7003          16 :     int nUnlimiDimID = -1;
    7004          16 :     status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
    7005          16 :     NCDF_ERR(status);
    7006          16 :     if (bIsNC4)
    7007             :     {
    7008             :         // In NC4, the dimension ids of a group are not necessarily in
    7009             :         // [0,nDimCount-1] range
    7010           8 :         int nDimCount2 = -1;
    7011           8 :         status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
    7012           8 :         NCDF_ERR(status);
    7013           8 :         CPLAssert(nDimCount == nDimCount2);
    7014             :     }
    7015             :     else
    7016             :     {
    7017          36 :         for (int i = 0; i < nDimCount; i++)
    7018          28 :             anDimIds[i] = i;
    7019             :     }
    7020          60 :     for (int i = 0; i < nDimCount; i++)
    7021             :     {
    7022             :         char szDimName[NC_MAX_NAME + 1];
    7023          44 :         szDimName[0] = 0;
    7024          44 :         size_t nLen = 0;
    7025          44 :         const int nDimId = anDimIds[i];
    7026          44 :         status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
    7027          44 :         NCDF_ERR(status);
    7028          44 :         if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
    7029          16 :             nLen = NC_UNLIMITED;
    7030          28 :         else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
    7031          13 :             nLen = nNewSize;
    7032          44 :         int nNewDimId = -1;
    7033          44 :         status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
    7034          44 :         NCDF_ERR(status);
    7035          44 :         CPLAssert(nDimId == nNewDimId);
    7036          44 :         if (status != NC_NOERR)
    7037             :         {
    7038           0 :             return false;
    7039             :         }
    7040             :     }
    7041             : 
    7042             :     // Clone main attributes
    7043          16 :     if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
    7044             :     {
    7045           0 :         return false;
    7046             :     }
    7047             : 
    7048             :     // Clone variable definitions
    7049          16 :     int nVarCount = -1;
    7050          16 :     status = nc_inq_nvars(nOldGrpId, &nVarCount);
    7051          16 :     NCDF_ERR(status);
    7052             : 
    7053         137 :     for (int i = 0; i < nVarCount; i++)
    7054             :     {
    7055             :         char szVarName[NC_MAX_NAME + 1];
    7056         121 :         szVarName[0] = 0;
    7057         121 :         status = nc_inq_varname(nOldGrpId, i, szVarName);
    7058         121 :         NCDF_ERR(status);
    7059         121 :         nc_type nc_datatype = NC_NAT;
    7060         121 :         status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
    7061         121 :         NCDF_ERR(status);
    7062         121 :         int nVarDimCount = -1;
    7063         121 :         status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
    7064         121 :         NCDF_ERR(status);
    7065         121 :         status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
    7066         121 :         NCDF_ERR(status);
    7067         121 :         int nNewVarId = -1;
    7068         121 :         status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
    7069             :                             anDimIds, &nNewVarId);
    7070         121 :         NCDF_ERR(status);
    7071         121 :         CPLAssert(i == nNewVarId);
    7072         121 :         if (status != NC_NOERR)
    7073             :         {
    7074           0 :             return false;
    7075             :         }
    7076             : 
    7077         121 :         if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
    7078             :         {
    7079           0 :             return false;
    7080             :         }
    7081             :     }
    7082             : 
    7083          16 :     status = nc_enddef(nNewGrpId);
    7084          16 :     NCDF_ERR(status);
    7085          16 :     if (status != NC_NOERR)
    7086             :     {
    7087           0 :         return false;
    7088             :     }
    7089             : 
    7090             :     // Clone variable content
    7091         137 :     for (int i = 0; i < nVarCount; i++)
    7092             :     {
    7093         121 :         if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
    7094             :         {
    7095           0 :             return false;
    7096             :         }
    7097             :     }
    7098             : 
    7099          16 :     return true;
    7100             : }
    7101             : 
    7102             : /************************************************************************/
    7103             : /*                              GrowDim()                               */
    7104             : /************************************************************************/
    7105             : 
    7106          13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7107             : {
    7108             :     int nCreationMode;
    7109             :     // Set nCreationMode based on eFormat.
    7110          13 :     switch (eFormat)
    7111             :     {
    7112             : #ifdef NETCDF_HAS_NC2
    7113           0 :         case NCDF_FORMAT_NC2:
    7114           0 :             nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
    7115           0 :             break;
    7116             : #endif
    7117           5 :         case NCDF_FORMAT_NC4:
    7118           5 :             nCreationMode = NC_CLOBBER | NC_NETCDF4;
    7119           5 :             break;
    7120           0 :         case NCDF_FORMAT_NC4C:
    7121           0 :             nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
    7122           0 :             break;
    7123           8 :         case NCDF_FORMAT_NC:
    7124             :         default:
    7125           8 :             nCreationMode = NC_CLOBBER;
    7126           8 :             break;
    7127             :     }
    7128             : 
    7129          13 :     int new_cdfid = -1;
    7130          26 :     CPLString osTmpFilename(osFilename + ".tmp");
    7131          26 :     CPLString osFilenameForNCCreate(osTmpFilename);
    7132             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7133             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7134             :     {
    7135             :         char *pszTemp =
    7136             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    7137             :         osFilenameForNCCreate = pszTemp;
    7138             :         CPLFree(pszTemp);
    7139             :     }
    7140             : #endif
    7141          13 :     int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
    7142          13 :     NCDF_ERR(status);
    7143          13 :     if (status != NC_NOERR)
    7144           0 :         return false;
    7145             : 
    7146          13 :     if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
    7147             :                   nDimIdToGrow, nNewSize))
    7148             :     {
    7149           0 :         GDAL_nc_close(new_cdfid);
    7150           0 :         return false;
    7151             :     }
    7152             : 
    7153          13 :     int nGroupCount = 0;
    7154          26 :     std::vector<CPLString> oListGrpName;
    7155          31 :     if (eFormat == NCDF_FORMAT_NC4 &&
    7156          18 :         nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
    7157           5 :         nGroupCount > 0)
    7158             :     {
    7159             :         int *panGroupIds =
    7160           2 :             static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
    7161           2 :         status = nc_inq_grps(cdfid, nullptr, panGroupIds);
    7162           2 :         NCDF_ERR(status);
    7163           5 :         for (int i = 0; i < nGroupCount; i++)
    7164             :         {
    7165             :             char szGroupName[NC_MAX_NAME + 1];
    7166           3 :             szGroupName[0] = 0;
    7167           3 :             NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
    7168           3 :             int nNewGrpId = -1;
    7169           3 :             status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
    7170           3 :             NCDF_ERR(status);
    7171           3 :             if (status != NC_NOERR)
    7172             :             {
    7173           0 :                 CPLFree(panGroupIds);
    7174           0 :                 GDAL_nc_close(new_cdfid);
    7175           0 :                 return false;
    7176             :             }
    7177           3 :             if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
    7178             :                           nDimIdToGrow, nNewSize))
    7179             :             {
    7180           0 :                 CPLFree(panGroupIds);
    7181           0 :                 GDAL_nc_close(new_cdfid);
    7182           0 :                 return false;
    7183             :             }
    7184             :         }
    7185           2 :         CPLFree(panGroupIds);
    7186             : 
    7187           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7188             :         {
    7189           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7190           3 :             if (poLayer)
    7191             :             {
    7192             :                 char szGroupName[NC_MAX_NAME + 1];
    7193           3 :                 szGroupName[0] = 0;
    7194           3 :                 status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
    7195           3 :                 NCDF_ERR(status);
    7196           3 :                 oListGrpName.push_back(szGroupName);
    7197             :             }
    7198             :         }
    7199             :     }
    7200             : 
    7201          13 :     GDAL_nc_close(cdfid);
    7202          13 :     cdfid = -1;
    7203          13 :     GDAL_nc_close(new_cdfid);
    7204             : 
    7205          26 :     CPLString osOriFilename(osFilename + ".ori");
    7206          26 :     if (VSIRename(osFilename, osOriFilename) != 0 ||
    7207          13 :         VSIRename(osTmpFilename, osFilename) != 0)
    7208             :     {
    7209           0 :         CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
    7210           0 :         return false;
    7211             :     }
    7212          13 :     VSIUnlink(osOriFilename);
    7213             : 
    7214          26 :     CPLString osFilenameForNCOpen(osFilename);
    7215             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7216             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7217             :     {
    7218             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    7219             :         osFilenameForNCOpen = pszTemp;
    7220             :         CPLFree(pszTemp);
    7221             :     }
    7222             : #endif
    7223          13 :     status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
    7224          13 :     NCDF_ERR(status);
    7225          13 :     if (status != NC_NOERR)
    7226           0 :         return false;
    7227          13 :     bDefineMode = false;
    7228             : 
    7229          13 :     if (!oListGrpName.empty())
    7230             :     {
    7231           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7232             :         {
    7233           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7234           3 :             if (poLayer)
    7235             :             {
    7236           3 :                 int nNewLayerCDFID = -1;
    7237           3 :                 status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
    7238             :                                      &nNewLayerCDFID);
    7239           3 :                 NCDF_ERR(status);
    7240           3 :                 poLayer->SetCDFID(nNewLayerCDFID);
    7241             :             }
    7242             :         }
    7243             :     }
    7244             :     else
    7245             :     {
    7246          22 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7247             :         {
    7248          11 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7249          11 :             if (poLayer)
    7250          11 :                 poLayer->SetCDFID(cdfid);
    7251             :         }
    7252             :     }
    7253             : 
    7254          13 :     return true;
    7255             : }
    7256             : 
    7257             : #ifdef ENABLE_NCDUMP
    7258             : 
    7259             : /************************************************************************/
    7260             : /*                      netCDFDatasetCreateTempFile()                   */
    7261             : /************************************************************************/
    7262             : 
    7263             : /* Create a netCDF file from a text dump (format of ncdump) */
    7264             : /* Mostly to easy fuzzing of the driver, while still generating valid */
    7265             : /* netCDF files. */
    7266             : /* Note: not all data types are supported ! */
    7267           4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
    7268             :                                  const char *pszTmpFilename, VSILFILE *fpSrc)
    7269             : {
    7270           4 :     CPL_IGNORE_RET_VAL(eFormat);
    7271           4 :     int nCreateMode = NC_CLOBBER;
    7272           4 :     if (eFormat == NCDF_FORMAT_NC4)
    7273           1 :         nCreateMode |= NC_NETCDF4;
    7274           3 :     else if (eFormat == NCDF_FORMAT_NC4C)
    7275           0 :         nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
    7276           4 :     int nCdfId = -1;
    7277           4 :     int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
    7278           4 :     if (status != NC_NOERR)
    7279             :     {
    7280           0 :         return false;
    7281             :     }
    7282           4 :     VSIFSeekL(fpSrc, 0, SEEK_SET);
    7283             :     const char *pszLine;
    7284           4 :     constexpr int SECTION_NONE = 0;
    7285           4 :     constexpr int SECTION_DIMENSIONS = 1;
    7286           4 :     constexpr int SECTION_VARIABLES = 2;
    7287           4 :     constexpr int SECTION_DATA = 3;
    7288           4 :     int nActiveSection = SECTION_NONE;
    7289           8 :     std::map<CPLString, int> oMapDimToId;
    7290           8 :     std::map<int, int> oMapDimIdToDimLen;
    7291           8 :     std::map<CPLString, int> oMapVarToId;
    7292           8 :     std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
    7293           8 :     std::map<int, int> oMapVarIdToType;
    7294           4 :     std::set<CPLString> oSetAttrDefined;
    7295           4 :     oMapVarToId[""] = -1;
    7296           4 :     size_t nTotalVarSize = 0;
    7297         208 :     while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
    7298             :     {
    7299         204 :         if (STARTS_WITH(pszLine, "dimensions:") &&
    7300             :             nActiveSection == SECTION_NONE)
    7301             :         {
    7302           4 :             nActiveSection = SECTION_DIMENSIONS;
    7303             :         }
    7304         200 :         else if (STARTS_WITH(pszLine, "variables:") &&
    7305             :                  nActiveSection == SECTION_DIMENSIONS)
    7306             :         {
    7307           4 :             nActiveSection = SECTION_VARIABLES;
    7308             :         }
    7309         196 :         else if (STARTS_WITH(pszLine, "data:") &&
    7310             :                  nActiveSection == SECTION_VARIABLES)
    7311             :         {
    7312           4 :             nActiveSection = SECTION_DATA;
    7313           4 :             status = nc_enddef(nCdfId);
    7314           4 :             if (status != NC_NOERR)
    7315             :             {
    7316           0 :                 CPLDebug("netCDF", "nc_enddef() failed: %s",
    7317             :                          nc_strerror(status));
    7318             :             }
    7319             :         }
    7320         192 :         else if (nActiveSection == SECTION_DIMENSIONS)
    7321             :         {
    7322           9 :             char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
    7323           9 :             if (CSLCount(papszTokens) == 2)
    7324             :             {
    7325           9 :                 const char *pszDimName = papszTokens[0];
    7326           9 :                 bool bValidName = true;
    7327           9 :                 if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
    7328             :                 {
    7329             :                     // This is an internal netcdf prefix. Using it may
    7330             :                     // cause memory leaks.
    7331           0 :                     bValidName = false;
    7332             :                 }
    7333           9 :                 if (!bValidName)
    7334             :                 {
    7335           0 :                     CPLDebug("netCDF",
    7336             :                              "nc_def_dim(%s) failed: invalid name found",
    7337             :                              pszDimName);
    7338           0 :                     CSLDestroy(papszTokens);
    7339           0 :                     continue;
    7340             :                 }
    7341             : 
    7342             :                 const bool bIsASCII =
    7343           9 :                     CPLIsASCII(pszDimName, static_cast<size_t>(-1));
    7344           9 :                 if (!bIsASCII)
    7345             :                 {
    7346             :                     // Workaround https://github.com/Unidata/netcdf-c/pull/450
    7347           0 :                     CPLDebug("netCDF",
    7348             :                              "nc_def_dim(%s) failed: rejected because "
    7349             :                              "of non-ASCII characters",
    7350             :                              pszDimName);
    7351           0 :                     CSLDestroy(papszTokens);
    7352           0 :                     continue;
    7353             :                 }
    7354           9 :                 int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
    7355             :                                    ? NC_UNLIMITED
    7356           9 :                                    : atoi(papszTokens[1]);
    7357           9 :                 if (nDimSize >= 1000)
    7358           1 :                     nDimSize = 1000;  // to avoid very long processing
    7359           9 :                 if (nDimSize >= 0)
    7360             :                 {
    7361           9 :                     int nDimId = -1;
    7362           9 :                     status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
    7363           9 :                     if (status != NC_NOERR)
    7364             :                     {
    7365           0 :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
    7366             :                                  pszDimName, nDimSize, nc_strerror(status));
    7367             :                     }
    7368             :                     else
    7369             :                     {
    7370             : #ifdef DEBUG_VERBOSE
    7371             :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
    7372             :                                  pszDimName, nDimSize, pszLine);
    7373             : #endif
    7374           9 :                         oMapDimToId[pszDimName] = nDimId;
    7375           9 :                         oMapDimIdToDimLen[nDimId] = nDimSize;
    7376             :                     }
    7377             :                 }
    7378             :             }
    7379           9 :             CSLDestroy(papszTokens);
    7380             :         }
    7381         183 :         else if (nActiveSection == SECTION_VARIABLES)
    7382             :         {
    7383         390 :             while (*pszLine == ' ' || *pszLine == '\t')
    7384         249 :                 pszLine++;
    7385         141 :             const char *pszColumn = strchr(pszLine, ':');
    7386         141 :             const char *pszEqual = strchr(pszLine, '=');
    7387         141 :             if (pszColumn == nullptr)
    7388             :             {
    7389          21 :                 char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
    7390          21 :                 if (CSLCount(papszTokens) >= 2)
    7391             :                 {
    7392          17 :                     const char *pszVarName = papszTokens[1];
    7393          17 :                     bool bValidName = true;
    7394          17 :                     if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
    7395             :                     {
    7396             :                         // This is an internal netcdf prefix. Using it may
    7397             :                         // cause memory leaks.
    7398           0 :                         bValidName = false;
    7399             :                     }
    7400         138 :                     for (int i = 0; pszVarName[i]; i++)
    7401             :                     {
    7402         121 :                         if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
    7403          28 :                               (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
    7404           9 :                               (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
    7405           6 :                               pszVarName[i] == '_'))
    7406             :                         {
    7407           0 :                             bValidName = false;
    7408             :                         }
    7409             :                     }
    7410          17 :                     if (!bValidName)
    7411             :                     {
    7412           0 :                         CPLDebug(
    7413             :                             "netCDF",
    7414             :                             "nc_def_var(%s) failed: illegal character found",
    7415             :                             pszVarName);
    7416           0 :                         CSLDestroy(papszTokens);
    7417           0 :                         continue;
    7418             :                     }
    7419          17 :                     if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
    7420             :                     {
    7421           0 :                         CPLDebug("netCDF",
    7422             :                                  "nc_def_var(%s) failed: already defined",
    7423             :                                  pszVarName);
    7424           0 :                         CSLDestroy(papszTokens);
    7425           0 :                         continue;
    7426             :                     }
    7427          17 :                     const char *pszVarType = papszTokens[0];
    7428          17 :                     int nc_datatype = NC_BYTE;
    7429          17 :                     size_t nDataTypeSize = 1;
    7430          17 :                     if (EQUAL(pszVarType, "char"))
    7431             :                     {
    7432           6 :                         nc_datatype = NC_CHAR;
    7433           6 :                         nDataTypeSize = 1;
    7434             :                     }
    7435          11 :                     else if (EQUAL(pszVarType, "byte"))
    7436             :                     {
    7437           3 :                         nc_datatype = NC_BYTE;
    7438           3 :                         nDataTypeSize = 1;
    7439             :                     }
    7440           8 :                     else if (EQUAL(pszVarType, "short"))
    7441             :                     {
    7442           0 :                         nc_datatype = NC_SHORT;
    7443           0 :                         nDataTypeSize = 2;
    7444             :                     }
    7445           8 :                     else if (EQUAL(pszVarType, "int"))
    7446             :                     {
    7447           0 :                         nc_datatype = NC_INT;
    7448           0 :                         nDataTypeSize = 4;
    7449             :                     }
    7450           8 :                     else if (EQUAL(pszVarType, "float"))
    7451             :                     {
    7452           0 :                         nc_datatype = NC_FLOAT;
    7453           0 :                         nDataTypeSize = 4;
    7454             :                     }
    7455           8 :                     else if (EQUAL(pszVarType, "double"))
    7456             :                     {
    7457           8 :                         nc_datatype = NC_DOUBLE;
    7458           8 :                         nDataTypeSize = 8;
    7459             :                     }
    7460           0 :                     else if (EQUAL(pszVarType, "ubyte"))
    7461             :                     {
    7462           0 :                         nc_datatype = NC_UBYTE;
    7463           0 :                         nDataTypeSize = 1;
    7464             :                     }
    7465           0 :                     else if (EQUAL(pszVarType, "ushort"))
    7466             :                     {
    7467           0 :                         nc_datatype = NC_USHORT;
    7468           0 :                         nDataTypeSize = 2;
    7469             :                     }
    7470           0 :                     else if (EQUAL(pszVarType, "uint"))
    7471             :                     {
    7472           0 :                         nc_datatype = NC_UINT;
    7473           0 :                         nDataTypeSize = 4;
    7474             :                     }
    7475           0 :                     else if (EQUAL(pszVarType, "int64"))
    7476             :                     {
    7477           0 :                         nc_datatype = NC_INT64;
    7478           0 :                         nDataTypeSize = 8;
    7479             :                     }
    7480           0 :                     else if (EQUAL(pszVarType, "uint64"))
    7481             :                     {
    7482           0 :                         nc_datatype = NC_UINT64;
    7483           0 :                         nDataTypeSize = 8;
    7484             :                     }
    7485             : 
    7486          17 :                     int nDims = CSLCount(papszTokens) - 2;
    7487          17 :                     if (nDims >= 32)
    7488             :                     {
    7489             :                         // The number of dimensions in a netCDFv4 file is
    7490             :                         // limited by #define H5S_MAX_RANK    32
    7491             :                         // but libnetcdf doesn't check that...
    7492           0 :                         CPLDebug("netCDF",
    7493             :                                  "nc_def_var(%s) failed: too many dimensions",
    7494             :                                  pszVarName);
    7495           0 :                         CSLDestroy(papszTokens);
    7496           0 :                         continue;
    7497             :                     }
    7498          17 :                     std::vector<int> aoDimIds;
    7499          17 :                     bool bFailed = false;
    7500          17 :                     size_t nSize = 1;
    7501          35 :                     for (int i = 0; i < nDims; i++)
    7502             :                     {
    7503          18 :                         const char *pszDimName = papszTokens[2 + i];
    7504          18 :                         if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
    7505             :                         {
    7506           0 :                             bFailed = true;
    7507           0 :                             break;
    7508             :                         }
    7509          18 :                         const int nDimId = oMapDimToId[pszDimName];
    7510          18 :                         aoDimIds.push_back(nDimId);
    7511             : 
    7512          18 :                         const size_t nDimSize = oMapDimIdToDimLen[nDimId];
    7513          18 :                         if (nDimSize != 0)
    7514             :                         {
    7515          18 :                             if (nSize >
    7516          18 :                                 std::numeric_limits<size_t>::max() / nDimSize)
    7517             :                             {
    7518           0 :                                 bFailed = true;
    7519           0 :                                 break;
    7520             :                             }
    7521             :                             else
    7522             :                             {
    7523          18 :                                 nSize *= nDimSize;
    7524             :                             }
    7525             :                         }
    7526             :                     }
    7527          17 :                     if (bFailed)
    7528             :                     {
    7529           0 :                         CPLDebug("netCDF",
    7530             :                                  "nc_def_var(%s) failed: unknown dimension(s)",
    7531             :                                  pszVarName);
    7532           0 :                         CSLDestroy(papszTokens);
    7533           0 :                         continue;
    7534             :                     }
    7535          17 :                     if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
    7536             :                     {
    7537           0 :                         CPLDebug("netCDF",
    7538             :                                  "nc_def_var(%s) failed: too large data",
    7539             :                                  pszVarName);
    7540           0 :                         CSLDestroy(papszTokens);
    7541           0 :                         continue;
    7542             :                     }
    7543          17 :                     if (nTotalVarSize >
    7544          34 :                             std::numeric_limits<size_t>::max() - nSize ||
    7545          17 :                         nTotalVarSize + nSize > 100 * 1024 * 1024)
    7546             :                     {
    7547           0 :                         CPLDebug("netCDF",
    7548             :                                  "nc_def_var(%s) failed: too large data",
    7549             :                                  pszVarName);
    7550           0 :                         CSLDestroy(papszTokens);
    7551           0 :                         continue;
    7552             :                     }
    7553          17 :                     nTotalVarSize += nSize;
    7554             : 
    7555          17 :                     int nVarId = -1;
    7556             :                     status =
    7557          30 :                         nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
    7558          13 :                                    (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
    7559          17 :                     if (status != NC_NOERR)
    7560             :                     {
    7561           0 :                         CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
    7562             :                                  pszVarName, nc_strerror(status));
    7563             :                     }
    7564             :                     else
    7565             :                     {
    7566             : #ifdef DEBUG_VERBOSE
    7567             :                         CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
    7568             :                                  pszVarName, pszLine);
    7569             : #endif
    7570          17 :                         oMapVarToId[pszVarName] = nVarId;
    7571          17 :                         oMapVarIdToType[nVarId] = nc_datatype;
    7572          17 :                         oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
    7573             :                     }
    7574             :                 }
    7575          21 :                 CSLDestroy(papszTokens);
    7576             :             }
    7577         120 :             else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
    7578             :             {
    7579         116 :                 CPLString osVarName(pszLine, pszColumn - pszLine);
    7580         116 :                 CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
    7581         116 :                 osAttrName.Trim();
    7582         116 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7583             :                 {
    7584           0 :                     CPLDebug("netCDF",
    7585             :                              "nc_put_att(%s:%s) failed: "
    7586             :                              "no corresponding variable",
    7587             :                              osVarName.c_str(), osAttrName.c_str());
    7588           0 :                     continue;
    7589             :                 }
    7590         116 :                 bool bValidName = true;
    7591        1743 :                 for (size_t i = 0; i < osAttrName.size(); i++)
    7592             :                 {
    7593        1865 :                     if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
    7594         238 :                           (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
    7595         158 :                           (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
    7596         158 :                           osAttrName[i] == '_'))
    7597             :                     {
    7598           0 :                         bValidName = false;
    7599             :                     }
    7600             :                 }
    7601         116 :                 if (!bValidName)
    7602             :                 {
    7603           0 :                     CPLDebug(
    7604             :                         "netCDF",
    7605             :                         "nc_put_att(%s:%s) failed: illegal character found",
    7606             :                         osVarName.c_str(), osAttrName.c_str());
    7607           0 :                     continue;
    7608             :                 }
    7609         116 :                 if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
    7610         232 :                     oSetAttrDefined.end())
    7611             :                 {
    7612           0 :                     CPLDebug("netCDF",
    7613             :                              "nc_put_att(%s:%s) failed: already defined",
    7614             :                              osVarName.c_str(), osAttrName.c_str());
    7615           0 :                     continue;
    7616             :                 }
    7617             : 
    7618         116 :                 const int nVarId = oMapVarToId[osVarName];
    7619         116 :                 const char *pszValue = pszEqual + 1;
    7620         232 :                 while (*pszValue == ' ')
    7621         116 :                     pszValue++;
    7622             : 
    7623         116 :                 status = NC_EBADTYPE;
    7624         116 :                 if (*pszValue == '"')
    7625             :                 {
    7626             :                     // For _FillValue, the attribute type should match
    7627             :                     // the variable type. Leaks memory with NC4 otherwise
    7628          74 :                     if (osAttrName == "_FillValue")
    7629             :                     {
    7630           0 :                         CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7631             :                                  osVarName.c_str(), osAttrName.c_str(),
    7632             :                                  nc_strerror(status));
    7633           0 :                         continue;
    7634             :                     }
    7635             : 
    7636             :                     // Unquote and unescape string value
    7637          74 :                     CPLString osVal(pszValue + 1);
    7638         222 :                     while (!osVal.empty())
    7639             :                     {
    7640         222 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7641             :                         {
    7642         148 :                             osVal.pop_back();
    7643             :                         }
    7644          74 :                         else if (osVal.back() == '"')
    7645             :                         {
    7646          74 :                             osVal.pop_back();
    7647          74 :                             break;
    7648             :                         }
    7649             :                         else
    7650             :                         {
    7651           0 :                             break;
    7652             :                         }
    7653             :                     }
    7654          74 :                     osVal.replaceAll("\\\"", '"');
    7655          74 :                     status = nc_put_att_text(nCdfId, nVarId, osAttrName,
    7656             :                                              osVal.size(), osVal.c_str());
    7657             :                 }
    7658             :                 else
    7659             :                 {
    7660          84 :                     CPLString osVal(pszValue);
    7661         126 :                     while (!osVal.empty())
    7662             :                     {
    7663         126 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7664             :                         {
    7665          84 :                             osVal.pop_back();
    7666             :                         }
    7667             :                         else
    7668             :                         {
    7669          42 :                             break;
    7670             :                         }
    7671             :                     }
    7672          42 :                     int nc_datatype = -1;
    7673          42 :                     if (!osVal.empty() && osVal.back() == 'b')
    7674             :                     {
    7675           3 :                         nc_datatype = NC_BYTE;
    7676           3 :                         osVal.pop_back();
    7677             :                     }
    7678          39 :                     else if (!osVal.empty() && osVal.back() == 's')
    7679             :                     {
    7680           3 :                         nc_datatype = NC_SHORT;
    7681           3 :                         osVal.pop_back();
    7682             :                     }
    7683          42 :                     if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
    7684             :                     {
    7685           7 :                         if (nc_datatype < 0)
    7686           4 :                             nc_datatype = NC_INT;
    7687             :                     }
    7688          35 :                     else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
    7689             :                     {
    7690          32 :                         nc_datatype = NC_DOUBLE;
    7691             :                     }
    7692             :                     else
    7693             :                     {
    7694           3 :                         nc_datatype = -1;
    7695             :                     }
    7696             : 
    7697             :                     // For _FillValue, check that the attribute type matches
    7698             :                     // the variable type. Leaks memory with NC4 otherwise
    7699          42 :                     if (osAttrName == "_FillValue")
    7700             :                     {
    7701           6 :                         if (nVarId < 0 ||
    7702           3 :                             nc_datatype != oMapVarIdToType[nVarId])
    7703             :                         {
    7704           0 :                             nc_datatype = -1;
    7705             :                         }
    7706             :                     }
    7707             : 
    7708          42 :                     if (nc_datatype == NC_BYTE)
    7709             :                     {
    7710             :                         signed char chVal =
    7711           3 :                             static_cast<signed char>(atoi(osVal));
    7712           3 :                         status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
    7713             :                                                   NC_BYTE, 1, &chVal);
    7714             :                     }
    7715          39 :                     else if (nc_datatype == NC_SHORT)
    7716             :                     {
    7717           0 :                         short nVal = static_cast<short>(atoi(osVal));
    7718           0 :                         status = nc_put_att_short(nCdfId, nVarId, osAttrName,
    7719             :                                                   NC_SHORT, 1, &nVal);
    7720             :                     }
    7721          39 :                     else if (nc_datatype == NC_INT)
    7722             :                     {
    7723           4 :                         int nVal = static_cast<int>(atoi(osVal));
    7724           4 :                         status = nc_put_att_int(nCdfId, nVarId, osAttrName,
    7725             :                                                 NC_INT, 1, &nVal);
    7726             :                     }
    7727          35 :                     else if (nc_datatype == NC_DOUBLE)
    7728             :                     {
    7729          32 :                         double dfVal = CPLAtof(osVal);
    7730          32 :                         status = nc_put_att_double(nCdfId, nVarId, osAttrName,
    7731             :                                                    NC_DOUBLE, 1, &dfVal);
    7732             :                     }
    7733             :                 }
    7734         116 :                 if (status != NC_NOERR)
    7735             :                 {
    7736           3 :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7737             :                              osVarName.c_str(), osAttrName.c_str(),
    7738             :                              nc_strerror(status));
    7739             :                 }
    7740             :                 else
    7741             :                 {
    7742         113 :                     oSetAttrDefined.insert(osVarName + ":" + osAttrName);
    7743             : #ifdef DEBUG_VERBOSE
    7744             :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
    7745             :                              osVarName.c_str(), osAttrName.c_str(), pszLine);
    7746             : #endif
    7747             :                 }
    7748             :             }
    7749             :         }
    7750          42 :         else if (nActiveSection == SECTION_DATA)
    7751             :         {
    7752          55 :             while (*pszLine == ' ' || *pszLine == '\t')
    7753          17 :                 pszLine++;
    7754          38 :             const char *pszEqual = strchr(pszLine, '=');
    7755          38 :             if (pszEqual)
    7756             :             {
    7757          17 :                 CPLString osVarName(pszLine, pszEqual - pszLine);
    7758          17 :                 osVarName.Trim();
    7759          17 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7760           0 :                     continue;
    7761          17 :                 const int nVarId = oMapVarToId[osVarName];
    7762          17 :                 CPLString osAccVal(pszEqual + 1);
    7763          17 :                 osAccVal.Trim();
    7764         153 :                 while (osAccVal.empty() || osAccVal.back() != ';')
    7765             :                 {
    7766         136 :                     pszLine = CPLReadLineL(fpSrc);
    7767         136 :                     if (pszLine == nullptr)
    7768           0 :                         break;
    7769         272 :                     CPLString osVal(pszLine);
    7770         136 :                     osVal.Trim();
    7771         136 :                     osAccVal += osVal;
    7772             :                 }
    7773          17 :                 if (pszLine == nullptr)
    7774           0 :                     break;
    7775          17 :                 osAccVal.pop_back();
    7776             : 
    7777             :                 const std::vector<int> aoDimIds =
    7778          34 :                     oMapVarIdToVectorOfDimId[nVarId];
    7779          17 :                 size_t nSize = 1;
    7780          34 :                 std::vector<size_t> aoStart, aoEdge;
    7781          17 :                 aoStart.resize(aoDimIds.size());
    7782          17 :                 aoEdge.resize(aoDimIds.size());
    7783          35 :                 for (size_t i = 0; i < aoDimIds.size(); ++i)
    7784             :                 {
    7785          18 :                     const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
    7786          36 :                     if (nDimSize != 0 &&
    7787          18 :                         nSize > std::numeric_limits<size_t>::max() / nDimSize)
    7788             :                     {
    7789           0 :                         nSize = 0;
    7790             :                     }
    7791             :                     else
    7792             :                     {
    7793          18 :                         nSize *= nDimSize;
    7794             :                     }
    7795          18 :                     aoStart[i] = 0;
    7796          18 :                     aoEdge[i] = nDimSize;
    7797             :                 }
    7798             : 
    7799          17 :                 status = NC_EBADTYPE;
    7800          17 :                 if (nSize == 0)
    7801             :                 {
    7802             :                     // Might happen with a unlimited dimension
    7803             :                 }
    7804          17 :                 else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
    7805             :                 {
    7806           8 :                     if (!aoStart.empty())
    7807             :                     {
    7808             :                         char **papszTokens =
    7809           8 :                             CSLTokenizeString2(osAccVal, " ,;", 0);
    7810           8 :                         size_t nTokens = CSLCount(papszTokens);
    7811           8 :                         if (nTokens >= nSize)
    7812             :                         {
    7813             :                             double *padfVals = static_cast<double *>(
    7814           8 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
    7815           8 :                             if (padfVals)
    7816             :                             {
    7817         132 :                                 for (size_t i = 0; i < nSize; i++)
    7818             :                                 {
    7819         124 :                                     padfVals[i] = CPLAtof(papszTokens[i]);
    7820             :                                 }
    7821           8 :                                 status = nc_put_vara_double(
    7822           8 :                                     nCdfId, nVarId, &aoStart[0], &aoEdge[0],
    7823             :                                     padfVals);
    7824           8 :                                 VSIFree(padfVals);
    7825             :                             }
    7826             :                         }
    7827           8 :                         CSLDestroy(papszTokens);
    7828             :                     }
    7829             :                 }
    7830           9 :                 else if (oMapVarIdToType[nVarId] == NC_BYTE)
    7831             :                 {
    7832           3 :                     if (!aoStart.empty())
    7833             :                     {
    7834             :                         char **papszTokens =
    7835           3 :                             CSLTokenizeString2(osAccVal, " ,;", 0);
    7836           3 :                         size_t nTokens = CSLCount(papszTokens);
    7837           3 :                         if (nTokens >= nSize)
    7838             :                         {
    7839             :                             signed char *panVals = static_cast<signed char *>(
    7840           3 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
    7841           3 :                             if (panVals)
    7842             :                             {
    7843        1203 :                                 for (size_t i = 0; i < nSize; i++)
    7844             :                                 {
    7845        1200 :                                     panVals[i] = static_cast<signed char>(
    7846        1200 :                                         atoi(papszTokens[i]));
    7847             :                                 }
    7848           3 :                                 status = nc_put_vara_schar(nCdfId, nVarId,
    7849           3 :                                                            &aoStart[0],
    7850           3 :                                                            &aoEdge[0], panVals);
    7851           3 :                                 VSIFree(panVals);
    7852             :                             }
    7853             :                         }
    7854           3 :                         CSLDestroy(papszTokens);
    7855             :                     }
    7856             :                 }
    7857           6 :                 else if (oMapVarIdToType[nVarId] == NC_CHAR)
    7858             :                 {
    7859           6 :                     if (aoStart.size() == 2)
    7860             :                     {
    7861           4 :                         std::vector<CPLString> aoStrings;
    7862           2 :                         bool bInString = false;
    7863           4 :                         CPLString osCurString;
    7864         935 :                         for (size_t i = 0; i < osAccVal.size();)
    7865             :                         {
    7866         933 :                             if (!bInString)
    7867             :                             {
    7868           8 :                                 if (osAccVal[i] == '"')
    7869             :                                 {
    7870           4 :                                     bInString = true;
    7871           4 :                                     osCurString.clear();
    7872             :                                 }
    7873           8 :                                 i++;
    7874             :                             }
    7875         926 :                             else if (osAccVal[i] == '\\' &&
    7876         926 :                                      i + 1 < osAccVal.size() &&
    7877           1 :                                      osAccVal[i + 1] == '"')
    7878             :                             {
    7879           1 :                                 osCurString += '"';
    7880           1 :                                 i += 2;
    7881             :                             }
    7882         924 :                             else if (osAccVal[i] == '"')
    7883             :                             {
    7884           4 :                                 aoStrings.push_back(osCurString);
    7885           4 :                                 osCurString.clear();
    7886           4 :                                 bInString = false;
    7887           4 :                                 i++;
    7888             :                             }
    7889             :                             else
    7890             :                             {
    7891         920 :                                 osCurString += osAccVal[i];
    7892         920 :                                 i++;
    7893             :                             }
    7894             :                         }
    7895           2 :                         const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
    7896           2 :                         const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
    7897           2 :                         size_t nIters = aoStrings.size();
    7898           2 :                         if (nIters > nRecords)
    7899           0 :                             nIters = nRecords;
    7900           6 :                         for (size_t i = 0; i < nIters; i++)
    7901             :                         {
    7902             :                             size_t anIndex[2];
    7903           4 :                             anIndex[0] = i;
    7904           4 :                             anIndex[1] = 0;
    7905             :                             size_t anCount[2];
    7906           4 :                             anCount[0] = 1;
    7907           4 :                             anCount[1] = aoStrings[i].size();
    7908           4 :                             if (anCount[1] > nWidth)
    7909           0 :                                 anCount[1] = nWidth;
    7910             :                             status =
    7911           4 :                                 nc_put_vara_text(nCdfId, nVarId, anIndex,
    7912           4 :                                                  anCount, aoStrings[i].c_str());
    7913           4 :                             if (status != NC_NOERR)
    7914           0 :                                 break;
    7915             :                         }
    7916             :                     }
    7917             :                 }
    7918          17 :                 if (status != NC_NOERR)
    7919             :                 {
    7920           4 :                     CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
    7921             :                              osVarName.c_str(), nc_strerror(status));
    7922             :                 }
    7923             :             }
    7924             :         }
    7925             :     }
    7926             : 
    7927           4 :     GDAL_nc_close(nCdfId);
    7928           4 :     return true;
    7929             : }
    7930             : 
    7931             : #endif  // ENABLE_NCDUMP
    7932             : 
    7933             : /************************************************************************/
    7934             : /*                                Open()                                */
    7935             : /************************************************************************/
    7936             : 
    7937         656 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
    7938             : 
    7939             : {
    7940             : #ifdef NCDF_DEBUG
    7941             :     CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
    7942             :              poOpenInfo->pszFilename);
    7943             : #endif
    7944             : 
    7945             :     // Does this appear to be a netcdf file?
    7946         656 :     NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
    7947         656 :     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    7948             :     {
    7949         598 :         eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
    7950             : #ifdef NCDF_DEBUG
    7951             :         CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
    7952             : #endif
    7953             :         // Note: not calling Identify() directly, because we want the file type.
    7954             :         // Only support NCDF_FORMAT* formats.
    7955         598 :         if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
    7956           2 :             NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
    7957             :         {
    7958             :             // ok
    7959             :         }
    7960           2 :         else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
    7961           0 :                  poOpenInfo->IsSingleAllowedDriver("netCDF"))
    7962             :         {
    7963             :             // ok
    7964             :         }
    7965             :         else
    7966             :         {
    7967           2 :             return nullptr;
    7968             :         }
    7969             :     }
    7970             :     else
    7971             :     {
    7972             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    7973             :         // We don't necessarily want to catch bugs in libnetcdf ...
    7974             :         if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
    7975             :         {
    7976             :             return nullptr;
    7977             :         }
    7978             : #endif
    7979             :     }
    7980             : 
    7981         654 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
    7982             :     {
    7983         173 :         return OpenMultiDim(poOpenInfo);
    7984             :     }
    7985             : 
    7986         962 :     CPLMutexHolderD(&hNCMutex);
    7987             : 
    7988         481 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    7989             :         // GDALDataset own mutex.
    7990         481 :     netCDFDataset *poDS = new netCDFDataset();
    7991         481 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    7992         481 :     CPLAcquireMutex(hNCMutex, 1000.0);
    7993             : 
    7994         481 :     poDS->SetDescription(poOpenInfo->pszFilename);
    7995             : 
    7996             :     // Check if filename start with NETCDF: tag.
    7997         481 :     bool bTreatAsSubdataset = false;
    7998         962 :     CPLString osSubdatasetName;
    7999             : 
    8000             : #ifdef ENABLE_NCDUMP
    8001         481 :     const char *pszHeader =
    8002             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    8003         481 :     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
    8004           3 :         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
    8005             :     {
    8006             :         // By default create a temporary file that will be destroyed,
    8007             :         // unless NETCDF_TMP_FILE is defined. Can be useful to see which
    8008             :         // netCDF file has been generated from a potential fuzzed input.
    8009           3 :         poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
    8010           3 :         if (poDS->osFilename.empty())
    8011             :         {
    8012           3 :             poDS->bFileToDestroyAtClosing = true;
    8013           3 :             poDS->osFilename = CPLGenerateTempFilename("netcdf_tmp");
    8014             :         }
    8015           3 :         if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
    8016             :                                          poOpenInfo->fpL))
    8017             :         {
    8018           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8019             :                 // deadlock with GDALDataset own mutex.
    8020           0 :             delete poDS;
    8021           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8022           0 :             return nullptr;
    8023             :         }
    8024           3 :         bTreatAsSubdataset = false;
    8025           3 :         poDS->eFormat = eTmpFormat;
    8026             :     }
    8027             :     else
    8028             : #endif
    8029             : 
    8030         478 :         if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8031             :     {
    8032             :         char **papszName =
    8033          58 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    8034             :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
    8035             : 
    8036         116 :         if (CSLCount(papszName) >= 3 &&
    8037          58 :             ((strlen(papszName[1]) == 1 && /* D:\\bla */
    8038           0 :               (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
    8039          58 :              EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
    8040          58 :              EQUAL(papszName[1], "/vsicurl/http") ||
    8041          58 :              EQUAL(papszName[1], "/vsicurl/https") ||
    8042          58 :              EQUAL(papszName[1], "/vsicurl_streaming/http") ||
    8043          58 :              EQUAL(papszName[1], "/vsicurl_streaming/https")))
    8044             :         {
    8045           0 :             const int nCountBefore = CSLCount(papszName);
    8046           0 :             CPLString osTmp = papszName[1];
    8047           0 :             osTmp += ':';
    8048           0 :             osTmp += papszName[2];
    8049           0 :             CPLFree(papszName[1]);
    8050           0 :             CPLFree(papszName[2]);
    8051           0 :             papszName[1] = CPLStrdup(osTmp);
    8052           0 :             memmove(papszName + 2, papszName + 3,
    8053           0 :                     (nCountBefore - 2) * sizeof(char *));
    8054             :         }
    8055             : 
    8056          58 :         if (CSLCount(papszName) == 3)
    8057             :         {
    8058          58 :             poDS->osFilename = papszName[1];
    8059          58 :             osSubdatasetName = papszName[2];
    8060          58 :             bTreatAsSubdataset = true;
    8061          58 :             CSLDestroy(papszName);
    8062             :         }
    8063           0 :         else if (CSLCount(papszName) == 2)
    8064             :         {
    8065           0 :             poDS->osFilename = papszName[1];
    8066           0 :             osSubdatasetName = "";
    8067           0 :             bTreatAsSubdataset = false;
    8068           0 :             CSLDestroy(papszName);
    8069             :         }
    8070             :         else
    8071             :         {
    8072           0 :             CSLDestroy(papszName);
    8073           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8074             :                 // deadlock with GDALDataset own mutex.
    8075           0 :             delete poDS;
    8076           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8077           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8078             :                      "Failed to parse NETCDF: prefix string into expected 2, 3 "
    8079             :                      "or 4 fields.");
    8080           0 :             return nullptr;
    8081             :         }
    8082             : 
    8083         116 :         if (!STARTS_WITH(poDS->osFilename, "http://") &&
    8084          58 :             !STARTS_WITH(poDS->osFilename, "https://"))
    8085             :         {
    8086             :             // Identify Format from real file, with bCheckExt=FALSE.
    8087             :             GDALOpenInfo *poOpenInfo2 =
    8088          58 :                 new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
    8089          58 :             poDS->eFormat =
    8090          58 :                 netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
    8091          58 :             delete poOpenInfo2;
    8092          58 :             if (NCDF_FORMAT_NONE == poDS->eFormat ||
    8093          58 :                 NCDF_FORMAT_UNKNOWN == poDS->eFormat)
    8094             :             {
    8095           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8096             :                     // deadlock with GDALDataset own mutex.
    8097           0 :                 delete poDS;
    8098           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    8099           0 :                 return nullptr;
    8100             :             }
    8101             :         }
    8102             :     }
    8103             :     else
    8104             :     {
    8105         420 :         poDS->osFilename = poOpenInfo->pszFilename;
    8106         420 :         bTreatAsSubdataset = false;
    8107         420 :         poDS->eFormat = eTmpFormat;
    8108             :     }
    8109             : 
    8110             : // Try opening the dataset.
    8111             : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
    8112             :     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
    8113             :              poDS->osFilename.c_str());
    8114             : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
    8115             :     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
    8116             : #endif
    8117         481 :     int cdfid = -1;
    8118         481 :     const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
    8119             :                           ? NC_WRITE
    8120             :                           : NC_NOWRITE;
    8121         962 :     CPLString osFilenameForNCOpen(poDS->osFilename);
    8122             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    8123             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    8124             :     {
    8125             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    8126             :         osFilenameForNCOpen = pszTemp;
    8127             :         CPLFree(pszTemp);
    8128             :     }
    8129             : #endif
    8130         481 :     int status2 = -1;
    8131             : 
    8132             : #ifdef ENABLE_UFFD
    8133         481 :     cpl_uffd_context *pCtx = nullptr;
    8134             : #endif
    8135             : 
    8136         496 :     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
    8137          15 :         poOpenInfo->eAccess == GA_ReadOnly)
    8138             :     {
    8139          15 :         vsi_l_offset nLength = 0;
    8140          15 :         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
    8141          15 :         if (poDS->fpVSIMEM)
    8142             :         {
    8143             :             // We assume that the file will not be modified. If it is, then
    8144             :             // pabyBuffer might become invalid.
    8145             :             GByte *pabyBuffer =
    8146          15 :                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
    8147          15 :             if (pabyBuffer)
    8148             :             {
    8149          15 :                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
    8150             :                                       nMode, static_cast<size_t>(nLength),
    8151             :                                       pabyBuffer, &cdfid);
    8152             :             }
    8153             :         }
    8154             :     }
    8155             :     else
    8156             :     {
    8157             :         const bool bVsiFile =
    8158         466 :             !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
    8159             : #ifdef ENABLE_UFFD
    8160         466 :         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
    8161         466 :         void *pVma = nullptr;
    8162         466 :         uint64_t nVmaSize = 0;
    8163             : 
    8164         466 :         if (bVsiFile)
    8165             :         {
    8166           1 :             if (bReadOnly)
    8167             :             {
    8168           1 :                 if (CPLIsUserFaultMappingSupported())
    8169             :                 {
    8170           1 :                     pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
    8171             :                                                      &nVmaSize);
    8172             :                 }
    8173             :                 else
    8174             :                 {
    8175           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    8176             :                              "Opening a /vsi file with the netCDF driver "
    8177             :                              "requires Linux userfaultfd to be available. "
    8178             :                              "If running from Docker, "
    8179             :                              "--security-opt seccomp=unconfined might be "
    8180             :                              "needed.%s",
    8181           0 :                              ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8182           0 :                                poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8183           0 :                               GDALGetDriverByName("HDF5"))
    8184             :                                  ? " Or you may set the GDAL_SKIP=netCDF "
    8185             :                                    "configuration option to force the use of "
    8186             :                                    "the HDF5 driver."
    8187             :                                  : "");
    8188             :                 }
    8189             :             }
    8190             :             else
    8191             :             {
    8192           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    8193             :                          "Opening a /vsi file with the netCDF driver is only "
    8194             :                          "supported in read-only mode");
    8195             :             }
    8196             :         }
    8197         466 :         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
    8198             :         {
    8199             :             // netCDF code, at least for netCDF 4.7.0, is confused by filenames
    8200             :             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
    8201             :             // final part
    8202           1 :             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
    8203             :                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);
    8204             :         }
    8205             :         else
    8206         465 :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8207             : #else
    8208             :         if (bVsiFile)
    8209             :         {
    8210             :             CPLError(
    8211             :                 CE_Failure, CPLE_AppDefined,
    8212             :                 "Opening a /vsi file with the netCDF driver requires Linux "
    8213             :                 "userfaultfd to be available.%s",
    8214             :                 ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8215             :                   poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8216             :                  GDALGetDriverByName("HDF5"))
    8217             :                     ? " Or you may set the GDAL_SKIP=netCDF "
    8218             :                       "configuration option to force the use of the HDF5 "
    8219             :                       "driver."
    8220             :                     : "");
    8221             :             status2 = NC_EIO;
    8222             :         }
    8223             :         else
    8224             :         {
    8225             :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8226             :         }
    8227             : #endif
    8228             :     }
    8229         481 :     if (status2 != NC_NOERR)
    8230             :     {
    8231             : #ifdef NCDF_DEBUG
    8232             :         CPLDebug("GDAL_netCDF", "error opening");
    8233             : #endif
    8234           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8235             :             // with GDALDataset own mutex.
    8236           0 :         delete poDS;
    8237           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8238           0 :         return nullptr;
    8239             :     }
    8240             : #ifdef NCDF_DEBUG
    8241             :     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
    8242             : #endif
    8243             : 
    8244             : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
    8245             :     // Try to destroy the temporary file right now on Unix
    8246         481 :     if (poDS->bFileToDestroyAtClosing)
    8247             :     {
    8248           3 :         if (VSIUnlink(poDS->osFilename) == 0)
    8249             :         {
    8250           3 :             poDS->bFileToDestroyAtClosing = false;
    8251             :         }
    8252             :     }
    8253             : #endif
    8254             : 
    8255             :     // Is this a real netCDF file?
    8256             :     int ndims;
    8257             :     int ngatts;
    8258             :     int nvars;
    8259             :     int unlimdimid;
    8260         481 :     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
    8261         481 :     if (status != NC_NOERR)
    8262             :     {
    8263           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8264             :             // with GDALDataset own mutex.
    8265           0 :         delete poDS;
    8266           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8267           0 :         return nullptr;
    8268             :     }
    8269             : 
    8270             :     // Get file type from netcdf.
    8271         481 :     int nTmpFormat = NCDF_FORMAT_NONE;
    8272         481 :     status = nc_inq_format(cdfid, &nTmpFormat);
    8273         481 :     if (status != NC_NOERR)
    8274             :     {
    8275           0 :         NCDF_ERR(status);
    8276             :     }
    8277             :     else
    8278             :     {
    8279         481 :         CPLDebug("GDAL_netCDF",
    8280             :                  "driver detected file type=%d, libnetcdf detected type=%d",
    8281         481 :                  poDS->eFormat, nTmpFormat);
    8282         481 :         if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
    8283             :         {
    8284             :             // Warn if file detection conflicts with that from libnetcdf
    8285             :             // except for NC4C, which we have no way of detecting initially.
    8286          24 :             if (nTmpFormat != NCDF_FORMAT_NC4C &&
    8287          12 :                 !STARTS_WITH(poDS->osFilename, "http://") &&
    8288           0 :                 !STARTS_WITH(poDS->osFilename, "https://"))
    8289             :             {
    8290           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    8291             :                          "NetCDF driver detected file type=%d, but libnetcdf "
    8292             :                          "detected type=%d",
    8293           0 :                          poDS->eFormat, nTmpFormat);
    8294             :             }
    8295          12 :             CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
    8296          12 :                      nTmpFormat, poDS->eFormat);
    8297          12 :             poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
    8298             :         }
    8299             :     }
    8300             : 
    8301             :     // Does the request variable exist?
    8302         481 :     if (bTreatAsSubdataset)
    8303             :     {
    8304             :         int dummy;
    8305          58 :         if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
    8306          58 :                                &dummy) != CE_None)
    8307             :         {
    8308           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    8309             :                      "%s is a netCDF file, but %s is not a variable.",
    8310             :                      poOpenInfo->pszFilename, osSubdatasetName.c_str());
    8311             : 
    8312           0 :             GDAL_nc_close(cdfid);
    8313             : #ifdef ENABLE_UFFD
    8314           0 :             NETCDF_UFFD_UNMAP(pCtx);
    8315             : #endif
    8316           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8317             :                 // deadlock with GDALDataset own mutex.
    8318           0 :             delete poDS;
    8319           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8320           0 :             return nullptr;
    8321             :         }
    8322             :     }
    8323             : 
    8324             :     // Figure out whether or not the listed dataset has support for simple
    8325             :     // geometries (CF-1.8)
    8326         481 :     poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
    8327         481 :     bool bHasSimpleGeometries = false;  // but not necessarily valid
    8328         481 :     if (poDS->nCFVersion >= 1.8)
    8329             :     {
    8330          75 :         bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
    8331          75 :         if (bHasSimpleGeometries)
    8332             :         {
    8333          67 :             poDS->bSGSupport = true;
    8334          67 :             poDS->vcdf.enableFullVirtualMode();
    8335             :         }
    8336             :     }
    8337             : 
    8338             :     char szConventions[NC_MAX_NAME + 1];
    8339         481 :     szConventions[0] = '\0';
    8340         481 :     nc_type nAttype = NC_NAT;
    8341         481 :     size_t nAttlen = 0;
    8342         481 :     nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
    8343         962 :     if (nAttlen >= sizeof(szConventions) ||
    8344         481 :         nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
    8345             :             NC_NOERR)
    8346             :     {
    8347          56 :         CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
    8348             :         // Note that 'Conventions' is always capital 'C' in CF spec.
    8349             :     }
    8350             :     else
    8351             :     {
    8352         425 :         szConventions[nAttlen] = '\0';
    8353             :     }
    8354             : 
    8355             :     // Create band information objects.
    8356         481 :     CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
    8357             : 
    8358             :     // Create a corresponding GDALDataset.
    8359             :     // Create Netcdf Subdataset if filename as NETCDF tag.
    8360         481 :     poDS->cdfid = cdfid;
    8361             : #ifdef ENABLE_UFFD
    8362         481 :     poDS->pCtx = pCtx;
    8363             : #endif
    8364         481 :     poDS->eAccess = poOpenInfo->eAccess;
    8365         481 :     poDS->bDefineMode = false;
    8366             : 
    8367         481 :     poDS->ReadAttributes(cdfid, NC_GLOBAL);
    8368             : 
    8369             :     // Identify coordinate and boundary variables that we should
    8370             :     // ignore as Raster Bands.
    8371         481 :     char **papszIgnoreVars = nullptr;
    8372         481 :     NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
    8373             :     // Filter variables to keep only valid 2+D raster bands and vector fields.
    8374         481 :     int nRasterVars = 0;
    8375         481 :     int nIgnoredVars = 0;
    8376         481 :     int nGroupID = -1;
    8377         481 :     int nVarID = -1;
    8378             : 
    8379             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
    8380         962 :         oMap2DDimsToGroupAndVar;
    8381        1111 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8382         149 :         STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
    8383             :                                          "NC_GLOBAL#mission_name", ""),
    8384           1 :                     "Sentinel 3") &&
    8385           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8386             :                                    "NC_GLOBAL#altimeter_sensor_name", ""),
    8387         630 :               "SRAL") &&
    8388           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8389             :                                    "NC_GLOBAL#radiometer_sensor_name", ""),
    8390             :               "MWR"))
    8391             :     {
    8392           1 :         if (poDS->eAccess == GA_Update)
    8393             :         {
    8394           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8395             :                 // deadlock with GDALDataset own mutex.
    8396           0 :             delete poDS;
    8397           0 :             return nullptr;
    8398             :         }
    8399           1 :         poDS->ProcessSentinel3_SRAL_MWR();
    8400             :     }
    8401             :     else
    8402             :     {
    8403         480 :         poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
    8404         628 :                          (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8405         148 :                              !bHasSimpleGeometries,
    8406             :                          papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
    8407             :                          &nIgnoredVars, oMap2DDimsToGroupAndVar);
    8408             :     }
    8409         481 :     CSLDestroy(papszIgnoreVars);
    8410             : 
    8411             :     // Case where there is no raster variable
    8412         481 :     if (nRasterVars == 0 && !bTreatAsSubdataset)
    8413             :     {
    8414         119 :         poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8415         119 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8416             :             // with GDALDataset own mutex.
    8417         119 :         poDS->TryLoadXML();
    8418             :         // If the dataset has been opened in raster mode only, exit
    8419         119 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    8420           9 :             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    8421             :         {
    8422           4 :             delete poDS;
    8423           4 :             poDS = nullptr;
    8424             :         }
    8425             :         // Otherwise if the dataset is opened in vector mode, that there is
    8426             :         // no vector layer and we are in read-only, exit too.
    8427         115 :         else if (poDS->GetLayerCount() == 0 &&
    8428         123 :                  (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8429           8 :                  poOpenInfo->eAccess == GA_ReadOnly)
    8430             :         {
    8431           8 :             delete poDS;
    8432           8 :             poDS = nullptr;
    8433             :         }
    8434         119 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8435         119 :         return poDS;
    8436             :     }
    8437             : 
    8438             :     // We have more than one variable with 2 dimensions in the
    8439             :     // file, then treat this as a subdataset container dataset.
    8440         362 :     bool bSeveralVariablesAsBands = false;
    8441         362 :     if ((nRasterVars > 1) && !bTreatAsSubdataset)
    8442             :     {
    8443          27 :         if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
    8444          33 :                          false) &&
    8445           6 :             oMap2DDimsToGroupAndVar.size() == 1)
    8446             :         {
    8447           6 :             std::tie(nGroupID, nVarID) =
    8448          12 :                 oMap2DDimsToGroupAndVar.begin()->second.front();
    8449           6 :             bSeveralVariablesAsBands = true;
    8450             :         }
    8451             :         else
    8452             :         {
    8453          21 :             poDS->CreateSubDatasetList(cdfid);
    8454          21 :             poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8455          21 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8456             :                 // deadlock with GDALDataset own mutex.
    8457          21 :             poDS->TryLoadXML();
    8458          21 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8459          21 :             return poDS;
    8460             :         }
    8461             :     }
    8462             : 
    8463             :     // If we are not treating things as a subdataset, then capture
    8464             :     // the name of the single available variable as the subdataset.
    8465         341 :     if (!bTreatAsSubdataset)
    8466             :     {
    8467         283 :         char *pszVarName = nullptr;
    8468         283 :         NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
    8469         283 :         osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
    8470         283 :         CPLFree(pszVarName);
    8471             :     }
    8472             : 
    8473             :     // We have ignored at least one variable, so we should report them
    8474             :     // as subdatasets for reference.
    8475         341 :     if (nIgnoredVars > 0 && !bTreatAsSubdataset)
    8476             :     {
    8477          24 :         CPLDebug("GDAL_netCDF",
    8478             :                  "As %d variables were ignored, creating subdataset list "
    8479             :                  "for reference. Variable #%d [%s] is the main variable",
    8480             :                  nIgnoredVars, nVarID, osSubdatasetName.c_str());
    8481          24 :         poDS->CreateSubDatasetList(cdfid);
    8482             :     }
    8483             : 
    8484             :     // Open the NETCDF subdataset NETCDF:"filename":subdataset.
    8485         341 :     int var = -1;
    8486         341 :     NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
    8487             :     // Now we can forget the root cdfid and only use the selected group.
    8488         341 :     cdfid = nGroupID;
    8489         341 :     int nd = 0;
    8490         341 :     nc_inq_varndims(cdfid, var, &nd);
    8491             : 
    8492         341 :     poDS->m_anDimIds.resize(nd);
    8493             : 
    8494             :     // X, Y, Z position in array
    8495         682 :     std::vector<int> anBandDimPos(nd);
    8496             : 
    8497         341 :     nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
    8498             : 
    8499             :     // Check if somebody tried to pass a variable with less than 1D.
    8500         341 :     if (nd < 1)
    8501             :     {
    8502           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8503             :                  "Variable has %d dimension(s) - not supported.", nd);
    8504           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8505             :             // with GDALDataset own mutex.
    8506           0 :         delete poDS;
    8507           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8508           0 :         return nullptr;
    8509             :     }
    8510             : 
    8511             :     // CF-1 Convention
    8512             :     //
    8513             :     // Dimensions to appear in the relative order T, then Z, then Y,
    8514             :     // then X  to the file. All other dimensions should, whenever
    8515             :     // possible, be placed to the left of the spatiotemporal
    8516             :     // dimensions.
    8517             : 
    8518             :     // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
    8519             :     // Ideally we should detect for other ordering and act accordingly
    8520             :     // Only done if file has Conventions=CF-* and only prints warning
    8521             :     // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
    8522             :     // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
    8523             :     const bool bCheckDims =
    8524         682 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
    8525         341 :         STARTS_WITH_CI(szConventions, "CF");
    8526             : 
    8527         341 :     if (nd >= 2 && bCheckDims)
    8528             :     {
    8529         258 :         char szDimName1[NC_MAX_NAME + 1] = {};
    8530         258 :         char szDimName2[NC_MAX_NAME + 1] = {};
    8531         258 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
    8532         258 :         NCDF_ERR(status);
    8533         258 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
    8534         258 :         NCDF_ERR(status);
    8535         414 :         if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
    8536         156 :             NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
    8537             :         {
    8538           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8539             :                      "dimension #%d (%s) is not a Longitude/X dimension.",
    8540             :                      nd - 1, szDimName1);
    8541             :         }
    8542         414 :         if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
    8543         156 :             NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
    8544             :         {
    8545           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8546             :                      "dimension #%d (%s) is not a Latitude/Y dimension.",
    8547             :                      nd - 2, szDimName2);
    8548             :         }
    8549         258 :         if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
    8550         260 :              NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
    8551           2 :             (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
    8552           0 :              NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
    8553             :         {
    8554           2 :             poDS->bSwitchedXY = true;
    8555             :         }
    8556         258 :         if (nd >= 3)
    8557             :         {
    8558          48 :             char szDimName3[NC_MAX_NAME + 1] = {};
    8559             :             status =
    8560          48 :                 nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
    8561          48 :             NCDF_ERR(status);
    8562          48 :             if (nd >= 4)
    8563             :             {
    8564          13 :                 char szDimName4[NC_MAX_NAME + 1] = {};
    8565             :                 status =
    8566          13 :                     nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
    8567          13 :                 NCDF_ERR(status);
    8568          13 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
    8569             :                 {
    8570           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8571             :                              "dimension #%d (%s) is not a Time dimension.",
    8572             :                              nd - 3, szDimName3);
    8573             :                 }
    8574          13 :                 if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
    8575             :                 {
    8576           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8577             :                              "dimension #%d (%s) is not a Time dimension.",
    8578             :                              nd - 4, szDimName4);
    8579             :                 }
    8580             :             }
    8581             :             else
    8582             :             {
    8583          67 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
    8584          32 :                     NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
    8585             :                 {
    8586           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8587             :                              "dimension #%d (%s) is not a "
    8588             :                              "Time or Vertical dimension.",
    8589             :                              nd - 3, szDimName3);
    8590             :                 }
    8591             :             }
    8592             :         }
    8593             :     }
    8594             : 
    8595             :     // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
    8596             :     // dimension order is downtrack, crosstrack, bands
    8597         341 :     bool bYXBandOrder = false;
    8598         341 :     if (nd == 3)
    8599             :     {
    8600          40 :         char szDimName[NC_MAX_NAME + 1] = {};
    8601          40 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
    8602          40 :         NCDF_ERR(status);
    8603          40 :         bYXBandOrder =
    8604          40 :             strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
    8605             :     }
    8606             : 
    8607             :     // Get X dimensions information.
    8608             :     size_t xdim;
    8609         341 :     poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
    8610         341 :     nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
    8611             : 
    8612             :     // Get Y dimension information.
    8613             :     size_t ydim;
    8614         341 :     if (nd >= 2)
    8615             :     {
    8616         337 :         poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
    8617         337 :         nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
    8618             :     }
    8619             :     else
    8620             :     {
    8621           4 :         poDS->nYDimID = -1;
    8622           4 :         ydim = 1;
    8623             :     }
    8624             : 
    8625         341 :     if (xdim > INT_MAX || ydim > INT_MAX)
    8626             :     {
    8627           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8628             :                  "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
    8629             :                  static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
    8630           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8631             :             // with GDALDataset own mutex.
    8632           0 :         delete poDS;
    8633           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8634           0 :         return nullptr;
    8635             :     }
    8636             : 
    8637         341 :     poDS->nRasterXSize = static_cast<int>(xdim);
    8638         341 :     poDS->nRasterYSize = static_cast<int>(ydim);
    8639             : 
    8640         341 :     unsigned int k = 0;
    8641        1092 :     for (int j = 0; j < nd; j++)
    8642             :     {
    8643         751 :         if (poDS->m_anDimIds[j] == poDS->nXDimID)
    8644             :         {
    8645         341 :             anBandDimPos[0] = j;  // Save Position of XDim
    8646         341 :             k++;
    8647             :         }
    8648         751 :         if (poDS->m_anDimIds[j] == poDS->nYDimID)
    8649             :         {
    8650         337 :             anBandDimPos[1] = j;  // Save Position of YDim
    8651         337 :             k++;
    8652             :         }
    8653             :     }
    8654             :     // X and Y Dimension Ids were not found!
    8655         341 :     if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
    8656             :     {
    8657           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8658             :             // with GDALDataset own mutex.
    8659           0 :         delete poDS;
    8660           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8661           0 :         return nullptr;
    8662             :     }
    8663             : 
    8664             :     // Read Metadata for this variable.
    8665             : 
    8666             :     // Should disable as is also done at band level, except driver needs the
    8667             :     // variables as metadata (e.g. projection).
    8668         341 :     poDS->ReadAttributes(cdfid, var);
    8669             : 
    8670             :     // Read Metadata for each dimension.
    8671         341 :     int *panDimIds = nullptr;
    8672         341 :     NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
    8673             :     // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
    8674             :     // in NetCDF-3 because we see only the dimensions of the selected group
    8675             :     // and its parents.
    8676             :     // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
    8677             :     // [0..max(panDimIds)], but they are not all useful so we fill names
    8678             :     // of useless dims with empty string.
    8679         341 :     if (panDimIds)
    8680             :     {
    8681         341 :         const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
    8682         341 :         std::set<int> oSetExistingDimIds;
    8683        1128 :         for (int i = 0; i < ndims; i++)
    8684             :         {
    8685         787 :             oSetExistingDimIds.insert(panDimIds[i]);
    8686             :         }
    8687         341 :         std::set<int> oSetDimIdsUsedByVar;
    8688        1092 :         for (int i = 0; i < nd; i++)
    8689             :         {
    8690         751 :             oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
    8691             :         }
    8692        1130 :         for (int j = 0; j <= nMaxDimId; j++)
    8693             :         {
    8694             :             // Is j dim used?
    8695         789 :             if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
    8696             :             {
    8697             :                 // Useful dim.
    8698         787 :                 char szTemp[NC_MAX_NAME + 1] = {};
    8699         787 :                 status = nc_inq_dimname(cdfid, j, szTemp);
    8700         787 :                 if (status != NC_NOERR)
    8701             :                 {
    8702           0 :                     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8703             :                         // deadlock with GDALDataset own
    8704             :                         // mutex.
    8705           0 :                     delete poDS;
    8706           0 :                     CPLAcquireMutex(hNCMutex, 1000.0);
    8707           0 :                     return nullptr;
    8708             :                 }
    8709         787 :                 poDS->papszDimName.AddString(szTemp);
    8710             : 
    8711         787 :                 if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
    8712             :                 {
    8713         751 :                     int nDimGroupId = -1;
    8714         751 :                     int nDimVarId = -1;
    8715         751 :                     if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
    8716         751 :                                        &nDimGroupId, &nDimVarId) == CE_None)
    8717             :                     {
    8718         555 :                         poDS->ReadAttributes(nDimGroupId, nDimVarId);
    8719             :                     }
    8720             :                 }
    8721             :             }
    8722             :             else
    8723             :             {
    8724             :                 // Useless dim.
    8725           2 :                 poDS->papszDimName.AddString("");
    8726             :             }
    8727             :         }
    8728         341 :         CPLFree(panDimIds);
    8729             :     }
    8730             : 
    8731             :     // Set projection info.
    8732         682 :     std::vector<std::string> aosRemovedMDItems;
    8733         341 :     if (nd > 1)
    8734             :     {
    8735         337 :         poDS->SetProjectionFromVar(cdfid, var,
    8736             :                                    /*bReadSRSOnly=*/false,
    8737             :                                    /* pszGivenGM = */ nullptr,
    8738             :                                    /* returnProjStr = */ nullptr,
    8739             :                                    /* sg = */ nullptr, &aosRemovedMDItems);
    8740             :     }
    8741             : 
    8742             :     // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
    8743         341 :     const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
    8744         341 :     if (pszValue)
    8745             :     {
    8746          24 :         poDS->bBottomUp = CPLTestBool(pszValue);
    8747          24 :         CPLDebug("GDAL_netCDF",
    8748             :                  "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
    8749          24 :                  static_cast<int>(poDS->bBottomUp), pszValue);
    8750             :     }
    8751             : 
    8752             :     // Save non-spatial dimension info.
    8753             : 
    8754         341 :     int *panBandZLev = nullptr;
    8755         341 :     int nDim = (nd >= 2) ? 2 : 1;
    8756             :     size_t lev_count;
    8757         341 :     size_t nTotLevCount = 1;
    8758         341 :     nc_type nType = NC_NAT;
    8759             : 
    8760         682 :     CPLString osExtraDimNames;
    8761             : 
    8762         341 :     if (nd > 2)
    8763             :     {
    8764          55 :         nDim = 2;
    8765          55 :         panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
    8766             : 
    8767          55 :         osExtraDimNames = "{";
    8768             : 
    8769          55 :         char szDimName[NC_MAX_NAME + 1] = {};
    8770             : 
    8771         238 :         for (int j = 0; j < nd; j++)
    8772             :         {
    8773         311 :             if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
    8774         128 :                 (poDS->m_anDimIds[j] != poDS->nYDimID))
    8775             :             {
    8776          73 :                 nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
    8777          73 :                 nTotLevCount *= lev_count;
    8778          73 :                 panBandZLev[nDim - 2] = static_cast<int>(lev_count);
    8779          73 :                 anBandDimPos[nDim] = j;  // Save Position of ZDim
    8780             :                 // Save non-spatial dimension names.
    8781          73 :                 if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
    8782             :                     NC_NOERR)
    8783             :                 {
    8784          73 :                     osExtraDimNames += szDimName;
    8785          73 :                     if (j < nd - 3)
    8786             :                     {
    8787          18 :                         osExtraDimNames += ",";
    8788             :                     }
    8789             : 
    8790          73 :                     int nIdxGroupID = -1;
    8791          73 :                     int nIdxVarID = Get1DVariableIndexedByDimension(
    8792          73 :                         cdfid, poDS->m_anDimIds[j], szDimName, true,
    8793          73 :                         &nIdxGroupID);
    8794          73 :                     poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
    8795          73 :                     poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
    8796             : 
    8797          73 :                     if (nIdxVarID >= 0)
    8798             :                     {
    8799          64 :                         nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
    8800             :                         char szExtraDimDef[NC_MAX_NAME + 1];
    8801          64 :                         snprintf(szExtraDimDef, sizeof(szExtraDimDef),
    8802             :                                  "{%ld,%d}", (long)lev_count, nType);
    8803             :                         char szTemp[NC_MAX_NAME + 32 + 1];
    8804          64 :                         snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    8805             :                                  szDimName);
    8806          64 :                         poDS->papszMetadata = CSLSetNameValue(
    8807             :                             poDS->papszMetadata, szTemp, szExtraDimDef);
    8808             : 
    8809             :                         // Retrieving data for unlimited dimensions might be
    8810             :                         // costly on network storage, so don't do it.
    8811             :                         // Each band will capture the value along the extra
    8812             :                         // dimension in its NETCDF_DIM_xxxx band metadata item
    8813             :                         // Addresses use case of
    8814             :                         // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
    8815          64 :                         if (VSIIsLocal(osFilenameForNCOpen.c_str()) ||
    8816           0 :                             !NCDFIsUnlimitedDim(poDS->eFormat ==
    8817             :                                                     NCDF_FORMAT_NC4,
    8818           0 :                                                 cdfid, poDS->m_anDimIds[j]))
    8819             :                         {
    8820          64 :                             char *pszTemp = nullptr;
    8821          64 :                             if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
    8822          64 :                                              &pszTemp) == CE_None)
    8823             :                             {
    8824          64 :                                 snprintf(szTemp, sizeof(szTemp),
    8825             :                                          "NETCDF_DIM_%s_VALUES", szDimName);
    8826          64 :                                 poDS->papszMetadata = CSLSetNameValue(
    8827             :                                     poDS->papszMetadata, szTemp, pszTemp);
    8828          64 :                                 CPLFree(pszTemp);
    8829             :                             }
    8830             :                         }
    8831             :                     }
    8832             :                 }
    8833             :                 else
    8834             :                 {
    8835           0 :                     poDS->m_anExtraDimGroupIds.push_back(-1);
    8836           0 :                     poDS->m_anExtraDimVarIds.push_back(-1);
    8837             :                 }
    8838             : 
    8839          73 :                 nDim++;
    8840             :             }
    8841             :         }
    8842          55 :         osExtraDimNames += "}";
    8843          55 :         poDS->papszMetadata = CSLSetNameValue(
    8844             :             poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
    8845             :     }
    8846             : 
    8847             :     // Store Metadata.
    8848         351 :     for (const auto &osStr : aosRemovedMDItems)
    8849          10 :         poDS->papszMetadata =
    8850          10 :             CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
    8851             : 
    8852         341 :     poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8853             : 
    8854             :     // Create bands.
    8855             : 
    8856             :     // Arbitrary threshold.
    8857             :     int nMaxBandCount =
    8858         341 :         atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
    8859         341 :     if (nMaxBandCount <= 0)
    8860           0 :         nMaxBandCount = 32768;
    8861         341 :     if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
    8862             :     {
    8863           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8864             :                  "Limiting number of bands to %d instead of %u", nMaxBandCount,
    8865             :                  static_cast<unsigned int>(nTotLevCount));
    8866           0 :         nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
    8867             :     }
    8868         341 :     if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
    8869             :     {
    8870           0 :         poDS->nRasterXSize = 0;
    8871           0 :         poDS->nRasterYSize = 0;
    8872           0 :         nTotLevCount = 0;
    8873           0 :         if (poDS->GetLayerCount() == 0)
    8874             :         {
    8875           0 :             CPLFree(panBandZLev);
    8876           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8877             :                 // deadlock with GDALDataset own mutex.
    8878           0 :             delete poDS;
    8879           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8880           0 :             return nullptr;
    8881             :         }
    8882             :     }
    8883         341 :     if (bSeveralVariablesAsBands)
    8884             :     {
    8885           6 :         const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
    8886          24 :         for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
    8887             :              ++iBand)
    8888             :         {
    8889          18 :             int bandVarGroupId = listVariables[iBand].first;
    8890          18 :             int bandVarId = listVariables[iBand].second;
    8891             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    8892           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
    8893          18 :                 bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
    8894          18 :             poDS->SetBand(iBand + 1, poBand);
    8895             :         }
    8896             :     }
    8897             :     else
    8898             :     {
    8899         774 :         for (unsigned int lev = 0; lev < nTotLevCount; lev++)
    8900             :         {
    8901             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    8902           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
    8903         439 :                 lev, panBandZLev, anBandDimPos.data(), lev + 1);
    8904         439 :             poDS->SetBand(lev + 1, poBand);
    8905             :         }
    8906             :     }
    8907             : 
    8908         341 :     if (panBandZLev)
    8909          55 :         CPLFree(panBandZLev);
    8910             :     // Handle angular geographic coordinates here
    8911             : 
    8912             :     // Initialize any PAM information.
    8913         341 :     if (bTreatAsSubdataset)
    8914             :     {
    8915          58 :         poDS->SetPhysicalFilename(poDS->osFilename);
    8916          58 :         poDS->SetSubdatasetName(osSubdatasetName);
    8917             :     }
    8918             : 
    8919         341 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    8920             :         // GDALDataset own mutex.
    8921         341 :     poDS->TryLoadXML();
    8922             : 
    8923         341 :     if (bTreatAsSubdataset)
    8924          58 :         poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
    8925             :     else
    8926         283 :         poDS->oOvManager.Initialize(poDS, poDS->osFilename);
    8927             : 
    8928         341 :     CPLAcquireMutex(hNCMutex, 1000.0);
    8929             : 
    8930         341 :     return poDS;
    8931             : }
    8932             : 
    8933             : /************************************************************************/
    8934             : /*                            CopyMetadata()                            */
    8935             : /*                                                                      */
    8936             : /*      Create a copy of metadata for NC_GLOBAL or a variable           */
    8937             : /************************************************************************/
    8938             : 
    8939         151 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
    8940             :                          GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
    8941             :                          const char *pszPrefix)
    8942             : {
    8943             :     // Remove the following band meta but set them later from band data.
    8944         151 :     const char *const papszIgnoreBand[] = {
    8945             :         CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    8946             :         NCDF_FillValue, "coordinates",   nullptr};
    8947         151 :     const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
    8948             : 
    8949         151 :     CSLConstList papszMetadata = nullptr;
    8950         151 :     if (poSrcDS)
    8951             :     {
    8952          63 :         papszMetadata = poSrcDS->GetMetadata();
    8953             :     }
    8954          88 :     else if (poSrcBand)
    8955             :     {
    8956          88 :         papszMetadata = poSrcBand->GetMetadata();
    8957             :     }
    8958             : 
    8959         630 :     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
    8960             :     {
    8961             : #ifdef NCDF_DEBUG
    8962             :         CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
    8963             : #endif
    8964             : 
    8965         479 :         CPLString osMetaName(pszKey);
    8966             : 
    8967             :         // Check for items that match pszPrefix if applicable.
    8968         479 :         if (pszPrefix && !EQUAL(pszPrefix, ""))
    8969             :         {
    8970             :             // Remove prefix.
    8971         115 :             if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
    8972             :             {
    8973          17 :                 osMetaName = osMetaName.substr(strlen(pszPrefix));
    8974             :             }
    8975             :             // Only copy items that match prefix.
    8976             :             else
    8977             :             {
    8978          98 :                 continue;
    8979             :             }
    8980             :         }
    8981             : 
    8982             :         // Fix various issues with metadata translation.
    8983         381 :         if (CDFVarID == NC_GLOBAL)
    8984             :         {
    8985             :             // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
    8986         479 :             if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
    8987         237 :                 (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
    8988          21 :                 continue;
    8989             :             // Remove NC_GLOBAL prefix for netcdf global Metadata.
    8990         221 :             else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
    8991             :             {
    8992          33 :                 osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
    8993             :             }
    8994             :             // GDAL Metadata renamed as GDAL-[meta].
    8995         188 :             else if (strstr(osMetaName, "#") == nullptr)
    8996             :             {
    8997          15 :                 osMetaName = "GDAL_" + osMetaName;
    8998             :             }
    8999             :             // Keep time, lev and depth information for safe-keeping.
    9000             :             // Time and vertical coordinate handling need improvements.
    9001             :             /*
    9002             :             else if( STARTS_WITH(szMetaName, "time#") )
    9003             :             {
    9004             :                 szMetaName[4] = '-';
    9005             :             }
    9006             :             else if( STARTS_WITH(szMetaName, "lev#") )
    9007             :             {
    9008             :                 szMetaName[3] = '-';
    9009             :             }
    9010             :             else if( STARTS_WITH(szMetaName, "depth#") )
    9011             :             {
    9012             :                 szMetaName[5] = '-';
    9013             :             }
    9014             :             */
    9015             :             // Only copy data without # (previously all data was copied).
    9016         221 :             if (strstr(osMetaName, "#") != nullptr)
    9017         173 :                 continue;
    9018             :             // netCDF attributes do not like the '#' character.
    9019             :             // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
    9020             :             //     if( szMetaName[h] == '#') szMetaName[h] = '-';
    9021             :             // }
    9022             :         }
    9023             :         else
    9024             :         {
    9025             :             // Do not copy varname, stats, NETCDF_DIM_*, nodata
    9026             :             // and items in papszIgnoreBand.
    9027         139 :             if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
    9028         107 :                 STARTS_WITH(osMetaName, "STATISTICS_") ||
    9029         107 :                 STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
    9030          74 :                 STARTS_WITH(osMetaName, "missing_value") ||
    9031         293 :                 STARTS_WITH(osMetaName, "_FillValue") ||
    9032          47 :                 CSLFindString(papszIgnoreBand, osMetaName) != -1)
    9033          97 :                 continue;
    9034             :         }
    9035             : 
    9036             : #ifdef NCDF_DEBUG
    9037             :         CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
    9038             :                  pszValue);
    9039             : #endif
    9040          90 :         if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
    9041             :         {
    9042           0 :             CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
    9043             :                      nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
    9044             :         }
    9045             :     }
    9046             : 
    9047             :     // Set add_offset and scale_factor here if present.
    9048         151 :     if (poSrcBand && poDstBand)
    9049             :     {
    9050             : 
    9051          88 :         int bGotAddOffset = FALSE;
    9052          88 :         const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
    9053          88 :         int bGotScale = FALSE;
    9054          88 :         const double dfScale = poSrcBand->GetScale(&bGotScale);
    9055             : 
    9056          88 :         if (bGotAddOffset && dfAddOffset != 0.0)
    9057           1 :             poDstBand->SetOffset(dfAddOffset);
    9058          88 :         if (bGotScale && dfScale != 1.0)
    9059           1 :             poDstBand->SetScale(dfScale);
    9060             :     }
    9061         151 : }
    9062             : 
    9063             : /************************************************************************/
    9064             : /*                            CreateLL()                                */
    9065             : /*                                                                      */
    9066             : /*      Shared functionality between netCDFDataset::Create() and        */
    9067             : /*      netCDF::CreateCopy() for creating netcdf file based on a set of */
    9068             : /*      options and a configuration.                                    */
    9069             : /************************************************************************/
    9070             : 
    9071         195 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
    9072             :                                        int nYSize, int nBandsIn,
    9073             :                                        char **papszOptions)
    9074             : {
    9075         195 :     if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
    9076         123 :           (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
    9077             :     {
    9078           1 :         return nullptr;
    9079             :     }
    9080             : 
    9081         194 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9082             :         // GDALDataset own mutex.
    9083         194 :     netCDFDataset *poDS = new netCDFDataset();
    9084         194 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9085             : 
    9086         194 :     poDS->nRasterXSize = nXSize;
    9087         194 :     poDS->nRasterYSize = nYSize;
    9088         194 :     poDS->eAccess = GA_Update;
    9089         194 :     poDS->osFilename = pszFilename;
    9090             : 
    9091             :     // From gtiff driver, is this ok?
    9092             :     /*
    9093             :     poDS->nBlockXSize = nXSize;
    9094             :     poDS->nBlockYSize = 1;
    9095             :     poDS->nBlocksPerBand =
    9096             :         ((nYSize + poDS->nBlockYSize - 1) / poDS->nBlockYSize)
    9097             :         * ((nXSize + poDS->nBlockXSize - 1) / poDS->nBlockXSize);
    9098             :         */
    9099             : 
    9100             :     // process options.
    9101         194 :     poDS->papszCreationOptions = CSLDuplicate(papszOptions);
    9102         194 :     poDS->ProcessCreationOptions();
    9103             : 
    9104         194 :     if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
    9105             :     {
    9106             :         VSIStatBuf sStat;
    9107           2 :         if (VSIStat(pszFilename, &sStat) == 0)
    9108             :         {
    9109           0 :             if (!VSI_ISDIR(sStat.st_mode))
    9110             :             {
    9111           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    9112             :                          "%s is an existing file, but not a directory",
    9113             :                          pszFilename);
    9114           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9115             :                     // deadlock with GDALDataset own
    9116             :                     // mutex.
    9117           0 :                 delete poDS;
    9118           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    9119           0 :                 return nullptr;
    9120             :             }
    9121             :         }
    9122           2 :         else if (VSIMkdir(pszFilename, 0755) != 0)
    9123             :         {
    9124           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
    9125             :                      pszFilename);
    9126           1 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9127             :                 // deadlock with GDALDataset own mutex.
    9128           1 :             delete poDS;
    9129           1 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9130           1 :             return nullptr;
    9131             :         }
    9132             : 
    9133           1 :         return poDS;
    9134             :     }
    9135             :     // Create the dataset.
    9136         384 :     CPLString osFilenameForNCCreate(pszFilename);
    9137             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    9138             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    9139             :     {
    9140             :         char *pszTemp =
    9141             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    9142             :         osFilenameForNCCreate = pszTemp;
    9143             :         CPLFree(pszTemp);
    9144             :     }
    9145             : #endif
    9146             : 
    9147             : #if defined(_WIN32)
    9148             :     {
    9149             :         // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
    9150             :         // crashes
    9151             :         VSIStatBuf sStat;
    9152             :         const char *pszDir = CPLGetDirname(osFilenameForNCCreate.c_str());
    9153             :         if (VSIStat(pszDir, &sStat) != 0)
    9154             :         {
    9155             :             CPLError(CE_Failure, CPLE_OpenFailed,
    9156             :                      "Unable to create netCDF file %s: non existing output "
    9157             :                      "directory",
    9158             :                      pszFilename);
    9159             :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9160             :                 // deadlock with GDALDataset own mutex.
    9161             :             delete poDS;
    9162             :             CPLAcquireMutex(hNCMutex, 1000.0);
    9163             :             return nullptr;
    9164             :         }
    9165             :     }
    9166             : #endif
    9167             : 
    9168             :     int status =
    9169         192 :         nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
    9170             : 
    9171             :     // Put into define mode.
    9172         192 :     poDS->SetDefineMode(true);
    9173             : 
    9174         192 :     if (status != NC_NOERR)
    9175             :     {
    9176          30 :         CPLError(CE_Failure, CPLE_OpenFailed,
    9177             :                  "Unable to create netCDF file %s (Error code %d): %s .",
    9178             :                  pszFilename, status, nc_strerror(status));
    9179          30 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    9180             :             // with GDALDataset own mutex.
    9181          30 :         delete poDS;
    9182          30 :         CPLAcquireMutex(hNCMutex, 1000.0);
    9183          30 :         return nullptr;
    9184             :     }
    9185             : 
    9186             :     // Define dimensions.
    9187         162 :     if (nXSize > 0 && nYSize > 0)
    9188             :     {
    9189         109 :         poDS->papszDimName.AddString(NCDF_DIMNAME_X);
    9190             :         status =
    9191         109 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
    9192         109 :         NCDF_ERR(status);
    9193         109 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9194             :                  poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
    9195             : 
    9196         109 :         poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
    9197             :         status =
    9198         109 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
    9199         109 :         NCDF_ERR(status);
    9200         109 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9201             :                  poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
    9202             :     }
    9203             : 
    9204         162 :     return poDS;
    9205             : }
    9206             : 
    9207             : /************************************************************************/
    9208             : /*                            Create()                                  */
    9209             : /************************************************************************/
    9210             : 
    9211         126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
    9212             :                                    int nYSize, int nBandsIn, GDALDataType eType,
    9213             :                                    char **papszOptions)
    9214             : {
    9215         126 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
    9216             :              pszFilename);
    9217             : 
    9218             :     const char *legacyCreationOp =
    9219         126 :         CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9220         252 :     std::string legacyCreationOp_s = std::string(legacyCreationOp);
    9221             : 
    9222             :     // Check legacy creation op FIRST
    9223             : 
    9224         126 :     bool legacyCreateMode = false;
    9225             : 
    9226         126 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
    9227             :     {
    9228          56 :         legacyCreateMode = true;
    9229             :     }
    9230          70 :     else if (legacyCreationOp_s == "CF_1.8")
    9231             :     {
    9232          54 :         legacyCreateMode = false;
    9233             :     }
    9234             : 
    9235          16 :     else if (legacyCreationOp_s == "WKT")
    9236             :     {
    9237          16 :         legacyCreateMode = true;
    9238             :     }
    9239             : 
    9240             :     else
    9241             :     {
    9242           0 :         CPLError(
    9243             :             CE_Failure, CPLE_NotSupported,
    9244             :             "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
    9245             :             legacyCreationOp_s.c_str());
    9246           0 :         return nullptr;
    9247             :     }
    9248             : 
    9249         252 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9250         238 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9251         112 :         (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
    9252             :          eType == GDT_Int64))
    9253             :     {
    9254          10 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9255          10 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9256             :     }
    9257             : 
    9258         252 :     CPLStringList aosBandNames;
    9259         126 :     if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
    9260             :     {
    9261             :         aosBandNames =
    9262           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9263             : 
    9264           2 :         if (aosBandNames.Count() != nBandsIn)
    9265             :         {
    9266           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9267             :                      "Attempted to create netCDF with %d bands but %d names "
    9268             :                      "provided in BAND_NAMES.",
    9269             :                      nBandsIn, aosBandNames.Count());
    9270             : 
    9271           1 :             return nullptr;
    9272             :         }
    9273             :     }
    9274             : 
    9275         250 :     CPLMutexHolderD(&hNCMutex);
    9276             : 
    9277         125 :     auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
    9278             :                                         aosOptions.List());
    9279             : 
    9280         125 :     if (!poDS)
    9281          19 :         return nullptr;
    9282             : 
    9283         106 :     if (!legacyCreateMode)
    9284             :     {
    9285          37 :         poDS->bSGSupport = true;
    9286          37 :         poDS->vcdf.enableFullVirtualMode();
    9287             :     }
    9288             : 
    9289             :     else
    9290             :     {
    9291          69 :         poDS->bSGSupport = false;
    9292             :     }
    9293             : 
    9294             :     // Should we write signed or unsigned byte?
    9295             :     // TODO should this only be done in Create()
    9296         106 :     poDS->bSignedData = true;
    9297         106 :     const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
    9298         106 :     if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
    9299          15 :         poDS->bSignedData = false;
    9300             : 
    9301             :     // Add Conventions, GDAL info and history.
    9302         106 :     if (poDS->cdfid >= 0)
    9303             :     {
    9304             :         const char *CF_Vector_Conv =
    9305         173 :             poDS->bSGSupport ||
    9306             :                     // Use of variable length strings require CF-1.8
    9307          68 :                     EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
    9308             :                 ? NCDF_CONVENTIONS_CF_V1_8
    9309         173 :                 : NCDF_CONVENTIONS_CF_V1_6;
    9310         105 :         poDS->bWriteGDALVersion = CPLTestBool(
    9311             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9312         105 :         poDS->bWriteGDALHistory = CPLTestBool(
    9313             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9314         105 :         NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
    9315         105 :                            poDS->bWriteGDALHistory, "", "Create",
    9316             :                            (nBandsIn == 0) ? CF_Vector_Conv
    9317             :                                            : GDAL_DEFAULT_NCDF_CONVENTIONS);
    9318             :     }
    9319             : 
    9320             :     // Define bands.
    9321         197 :     for (int iBand = 1; iBand <= nBandsIn; iBand++)
    9322             :     {
    9323             :         const char *pszBandName =
    9324          91 :             aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
    9325             : 
    9326          91 :         poDS->SetBand(iBand, new netCDFRasterBand(
    9327          91 :                                  netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
    9328          91 :                                  eType, iBand, poDS->bSignedData, pszBandName));
    9329             :     }
    9330             : 
    9331         106 :     CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
    9332             :     // Return same dataset.
    9333         106 :     return poDS;
    9334             : }
    9335             : 
    9336             : template <class T>
    9337          88 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
    9338             :                            int nXSize, int nYSize, GDALProgressFunc pfnProgress,
    9339             :                            void *pProgressData)
    9340             : {
    9341          88 :     GDALDataType eDT = poSrcBand->GetRasterDataType();
    9342          88 :     CPLErr eErr = CE_None;
    9343          88 :     T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
    9344             : 
    9345        2557 :     for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
    9346             :     {
    9347        2469 :         eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
    9348             :                                    nXSize, 1, eDT, 0, 0, nullptr);
    9349        2469 :         if (eErr != CE_None)
    9350             :         {
    9351           0 :             CPLDebug(
    9352             :                 "GDAL_netCDF",
    9353             :                 "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
    9354             :                 eErr);
    9355             :         }
    9356             :         else
    9357             :         {
    9358        2469 :             eErr =
    9359             :                 poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
    9360             :                                     nXSize, 1, eDT, 0, 0, nullptr);
    9361        2469 :             if (eErr != CE_None)
    9362           0 :                 CPLDebug("GDAL_netCDF",
    9363             :                          "NCDFCopyBand(), poDstBand->RasterIO() returned error "
    9364             :                          "code %d",
    9365             :                          eErr);
    9366             :         }
    9367             : 
    9368        2469 :         if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
    9369             :         {
    9370         246 :             if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
    9371             :             {
    9372           0 :                 eErr = CE_Failure;
    9373           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt,
    9374             :                          "User terminated CreateCopy()");
    9375             :             }
    9376             :         }
    9377             :     }
    9378             : 
    9379          88 :     CPLFree(patScanline);
    9380             : 
    9381          88 :     pfnProgress(1.0, nullptr, pProgressData);
    9382             : 
    9383          88 :     return eErr;
    9384             : }
    9385             : 
    9386             : /************************************************************************/
    9387             : /*                            CreateCopy()                              */
    9388             : /************************************************************************/
    9389             : 
    9390             : GDALDataset *
    9391          79 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
    9392             :                           CPL_UNUSED int bStrict, char **papszOptions,
    9393             :                           GDALProgressFunc pfnProgress, void *pProgressData)
    9394             : {
    9395         158 :     CPLMutexHolderD(&hNCMutex);
    9396             : 
    9397          79 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
    9398             :              pszFilename);
    9399             : 
    9400          79 :     if (poSrcDS->GetRootGroup())
    9401             :     {
    9402           5 :         auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
    9403           5 :         if (poDrv)
    9404             :         {
    9405           5 :             return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    9406             :                                             papszOptions, pfnProgress,
    9407           5 :                                             pProgressData);
    9408             :         }
    9409             :     }
    9410             : 
    9411          74 :     const int nBands = poSrcDS->GetRasterCount();
    9412          74 :     const int nXSize = poSrcDS->GetRasterXSize();
    9413          74 :     const int nYSize = poSrcDS->GetRasterYSize();
    9414          74 :     const char *pszWKT = poSrcDS->GetProjectionRef();
    9415             : 
    9416             :     // Check input bands for errors.
    9417          74 :     if (nBands == 0)
    9418             :     {
    9419           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    9420             :                  "NetCDF driver does not support "
    9421             :                  "source dataset with zero band.");
    9422           1 :         return nullptr;
    9423             :     }
    9424             : 
    9425          73 :     GDALDataType eDT = GDT_Unknown;
    9426          73 :     GDALRasterBand *poSrcBand = nullptr;
    9427         175 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9428             :     {
    9429         106 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9430         106 :         eDT = poSrcBand->GetRasterDataType();
    9431         106 :         if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
    9432             :         {
    9433           4 :             CPLError(CE_Failure, CPLE_NotSupported,
    9434             :                      "NetCDF driver does not support source dataset with band "
    9435             :                      "of complex type.");
    9436           4 :             return nullptr;
    9437             :         }
    9438             :     }
    9439             : 
    9440         138 :     CPLStringList aosBandNames;
    9441          69 :     if (const char *pszBandNames =
    9442          69 :             CSLFetchNameValue(papszOptions, "BAND_NAMES"))
    9443             :     {
    9444             :         aosBandNames =
    9445           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9446             : 
    9447           2 :         if (aosBandNames.Count() != nBands)
    9448             :         {
    9449           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9450             :                      "Attempted to create netCDF with %d bands but %d names "
    9451             :                      "provided in BAND_NAMES.",
    9452             :                      nBands, aosBandNames.Count());
    9453             : 
    9454           1 :             return nullptr;
    9455             :         }
    9456             :     }
    9457             : 
    9458          68 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    9459           0 :         return nullptr;
    9460             : 
    9461             :     // Same as in Create().
    9462         136 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9463         127 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9464          59 :         (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
    9465             :          eDT == GDT_Int64))
    9466             :     {
    9467           6 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9468           6 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9469             :     }
    9470          68 :     netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
    9471             :                                                   nBands, aosOptions.List());
    9472          68 :     if (!poDS)
    9473          13 :         return nullptr;
    9474             : 
    9475             :     // Copy global metadata.
    9476             :     // Add Conventions, GDAL info and history.
    9477          55 :     CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
    9478          55 :     const bool bWriteGDALVersion = CPLTestBool(
    9479             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9480          55 :     const bool bWriteGDALHistory = CPLTestBool(
    9481             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9482          55 :     NCDFAddGDALHistory(
    9483             :         poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
    9484          55 :         poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
    9485          55 :         poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
    9486             : 
    9487          55 :     pfnProgress(0.1, nullptr, pProgressData);
    9488             : 
    9489             :     // Check for extra dimensions.
    9490          55 :     int nDim = 2;
    9491             :     char **papszExtraDimNames =
    9492          55 :         NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9493          55 :     char **papszExtraDimValues = nullptr;
    9494             : 
    9495          55 :     if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
    9496             :     {
    9497           5 :         size_t nDimSizeTot = 1;
    9498             :         // first make sure dimensions lengths compatible with band count
    9499             :         // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
    9500          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9501             :         {
    9502             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9503           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9504           8 :                      papszExtraDimNames[i]);
    9505             :             papszExtraDimValues =
    9506           8 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9507           8 :             const size_t nDimSize = atol(papszExtraDimValues[0]);
    9508           8 :             CSLDestroy(papszExtraDimValues);
    9509           8 :             nDimSizeTot *= nDimSize;
    9510             :         }
    9511           5 :         if (nDimSizeTot == (size_t)nBands)
    9512             :         {
    9513           5 :             nDim = 2 + CSLCount(papszExtraDimNames);
    9514             :         }
    9515             :         else
    9516             :         {
    9517             :             // if nBands != #bands computed raise a warning
    9518             :             // just issue a debug message, because it was probably intentional
    9519           0 :             CPLDebug("GDAL_netCDF",
    9520             :                      "Warning: Number of bands (%d) is not compatible with "
    9521             :                      "dimensions "
    9522             :                      "(total=%ld names=%s)",
    9523             :                      nBands, (long)nDimSizeTot,
    9524           0 :                      poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9525           0 :             CSLDestroy(papszExtraDimNames);
    9526           0 :             papszExtraDimNames = nullptr;
    9527             :         }
    9528             :     }
    9529             : 
    9530          55 :     int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9531          55 :     int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9532             : 
    9533             :     nc_type nVarType;
    9534          55 :     int *panBandZLev = nullptr;
    9535          55 :     int *panDimVarIds = nullptr;
    9536             : 
    9537          55 :     if (nDim > 2)
    9538             :     {
    9539           5 :         panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9540           5 :         panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9541             : 
    9542             :         // Define all dims.
    9543          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9544             :         {
    9545           8 :             poDS->papszDimName.AddString(papszExtraDimNames[i]);
    9546             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9547           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9548           8 :                      papszExtraDimNames[i]);
    9549             :             papszExtraDimValues =
    9550           8 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9551           8 :             const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
    9552          16 :                                      ? atoi(papszExtraDimValues[0])
    9553             :                                      : 0;
    9554             :             // nc_type is an enum in netcdf-3, needs casting.
    9555           8 :             nVarType = static_cast<nc_type>(papszExtraDimValues &&
    9556           8 :                                                     papszExtraDimValues[0] &&
    9557           8 :                                                     papszExtraDimValues[1]
    9558           8 :                                                 ? atol(papszExtraDimValues[1])
    9559             :                                                 : 0);
    9560           8 :             CSLDestroy(papszExtraDimValues);
    9561           8 :             panBandZLev[i] = nDimSize;
    9562           8 :             panBandDimPos[i + 2] = i;  // Save Position of ZDim.
    9563             : 
    9564             :             // Define dim.
    9565          16 :             int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
    9566           8 :                                     nDimSize, &(panDimIds[i]));
    9567           8 :             NCDF_ERR(status);
    9568             : 
    9569             :             // Define dim var.
    9570           8 :             int anDim[1] = {panDimIds[i]};
    9571          16 :             status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
    9572           8 :                                 anDim, &(panDimVarIds[i]));
    9573           8 :             NCDF_ERR(status);
    9574             : 
    9575             :             // Add dim metadata, using global var# items.
    9576           8 :             snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
    9577           8 :             CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
    9578           8 :                          panDimVarIds[i], szTemp);
    9579             :         }
    9580             :     }
    9581             : 
    9582             :     // Copy GeoTransform and Projection.
    9583             : 
    9584             :     // Copy geolocation info.
    9585          55 :     char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
    9586          55 :     if (papszGeolocationInfo != nullptr)
    9587           5 :         poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
    9588             : 
    9589             :     // Copy geotransform.
    9590          55 :     bool bGotGeoTransform = false;
    9591             :     double adfGeoTransform[6];
    9592          55 :     CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
    9593          55 :     if (eErr == CE_None)
    9594             :     {
    9595          37 :         poDS->SetGeoTransform(adfGeoTransform);
    9596             :         // Disable AddProjectionVars() from being called.
    9597          37 :         bGotGeoTransform = true;
    9598          37 :         poDS->m_bHasGeoTransform = false;
    9599             :     }
    9600             : 
    9601             :     // Copy projection.
    9602          55 :     void *pScaledProgress = nullptr;
    9603          55 :     if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
    9604             :     {
    9605          38 :         poDS->SetProjection(pszWKT ? pszWKT : "");
    9606             : 
    9607             :         // Now we can call AddProjectionVars() directly.
    9608          38 :         poDS->m_bHasGeoTransform = bGotGeoTransform;
    9609          38 :         poDS->AddProjectionVars(true, nullptr, nullptr);
    9610             :         pScaledProgress =
    9611          38 :             GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
    9612          38 :         poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
    9613          38 :         GDALDestroyScaledProgress(pScaledProgress);
    9614             :     }
    9615             :     else
    9616             :     {
    9617          17 :         poDS->bBottomUp =
    9618          17 :             CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
    9619          17 :         if (papszGeolocationInfo)
    9620             :         {
    9621           4 :             poDS->AddProjectionVars(true, nullptr, nullptr);
    9622           4 :             poDS->AddProjectionVars(false, nullptr, nullptr);
    9623             :         }
    9624             :     }
    9625             : 
    9626             :     // Save X,Y dim positions.
    9627          55 :     panDimIds[nDim - 1] = poDS->nXDimID;
    9628          55 :     panBandDimPos[0] = nDim - 1;
    9629          55 :     panDimIds[nDim - 2] = poDS->nYDimID;
    9630          55 :     panBandDimPos[1] = nDim - 2;
    9631             : 
    9632             :     // Write extra dim values - after projection for optimization.
    9633          55 :     if (nDim > 2)
    9634             :     {
    9635             :         // Make sure we are in data mode.
    9636           5 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    9637          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9638             :         {
    9639             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9640           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
    9641           8 :                      papszExtraDimNames[i]);
    9642           8 :             if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
    9643             :             {
    9644           8 :                 NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
    9645           8 :                              poSrcDS->GetMetadataItem(szTemp));
    9646             :             }
    9647             :         }
    9648             :     }
    9649             : 
    9650          55 :     pfnProgress(0.25, nullptr, pProgressData);
    9651             : 
    9652             :     // Define Bands.
    9653          55 :     netCDFRasterBand *poBand = nullptr;
    9654          55 :     int nBandID = -1;
    9655             : 
    9656         143 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9657             :     {
    9658          88 :         CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
    9659             :                  nBands, nDim);
    9660             : 
    9661          88 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9662          88 :         eDT = poSrcBand->GetRasterDataType();
    9663             : 
    9664             :         // Get var name from NETCDF_VARNAME.
    9665             :         const char *pszNETCDF_VARNAME =
    9666          88 :             poSrcBand->GetMetadataItem("NETCDF_VARNAME");
    9667             :         char szBandName[NC_MAX_NAME + 1];
    9668          88 :         if (!aosBandNames.empty())
    9669             :         {
    9670           2 :             snprintf(szBandName, sizeof(szBandName), "%s",
    9671             :                      aosBandNames[iBand - 1]);
    9672             :         }
    9673          86 :         else if (pszNETCDF_VARNAME)
    9674             :         {
    9675          32 :             if (nBands > 1 && papszExtraDimNames == nullptr)
    9676           0 :                 snprintf(szBandName, sizeof(szBandName), "%s%d",
    9677             :                          pszNETCDF_VARNAME, iBand);
    9678             :             else
    9679          32 :                 snprintf(szBandName, sizeof(szBandName), "%s",
    9680             :                          pszNETCDF_VARNAME);
    9681             :         }
    9682             :         else
    9683             :         {
    9684          54 :             szBandName[0] = '\0';
    9685             :         }
    9686             : 
    9687             :         // Get long_name from <var>#long_name.
    9688          88 :         const char *pszLongName = "";
    9689          88 :         if (pszNETCDF_VARNAME)
    9690             :         {
    9691             :             pszLongName =
    9692          64 :                 poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
    9693          32 :                                              .append("#")
    9694          32 :                                              .append(CF_LNG_NAME)
    9695          32 :                                              .c_str());
    9696          32 :             if (!pszLongName)
    9697          25 :                 pszLongName = "";
    9698             :         }
    9699             : 
    9700          88 :         constexpr bool bSignedData = false;
    9701             : 
    9702          88 :         if (nDim > 2)
    9703          27 :             poBand = new netCDFRasterBand(
    9704          27 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9705             :                 bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
    9706          27 :                 panBandZLev, panBandDimPos, panDimIds);
    9707             :         else
    9708          61 :             poBand = new netCDFRasterBand(
    9709          61 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9710          61 :                 bSignedData, szBandName, pszLongName);
    9711             : 
    9712          88 :         poDS->SetBand(iBand, poBand);
    9713             : 
    9714             :         // Set nodata value, if any.
    9715          88 :         GDALCopyNoDataValue(poBand, poSrcBand);
    9716             : 
    9717             :         // Copy Metadata for band.
    9718          88 :         CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
    9719             :                      poDS->cdfid, poBand->nZId);
    9720             : 
    9721             :         // If more than 2D pass the first band's netcdf var ID to subsequent
    9722             :         // bands.
    9723          88 :         if (nDim > 2)
    9724          27 :             nBandID = poBand->nZId;
    9725             :     }
    9726             : 
    9727             :     // Write projection variable to band variable.
    9728          55 :     poDS->AddGridMappingRef();
    9729             : 
    9730          55 :     pfnProgress(0.5, nullptr, pProgressData);
    9731             : 
    9732             :     // Write bands.
    9733             : 
    9734             :     // Make sure we are in data mode.
    9735          55 :     poDS->SetDefineMode(false);
    9736             : 
    9737          55 :     double dfTemp = 0.5;
    9738             : 
    9739          55 :     eErr = CE_None;
    9740             : 
    9741         143 :     for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
    9742             :     {
    9743          88 :         const double dfTemp2 = dfTemp + 0.4 / nBands;
    9744          88 :         pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
    9745             :                                                    pProgressData);
    9746          88 :         dfTemp = dfTemp2;
    9747             : 
    9748          88 :         CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
    9749             : 
    9750          88 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9751          88 :         eDT = poSrcBand->GetRasterDataType();
    9752             : 
    9753          88 :         GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
    9754             : 
    9755             :         // Copy band data.
    9756          88 :         if (eDT == GDT_Byte)
    9757             :         {
    9758          48 :             CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
    9759          48 :             eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
    9760             :                                        GDALScaledProgress, pScaledProgress);
    9761             :         }
    9762          40 :         else if (eDT == GDT_Int8)
    9763             :         {
    9764           1 :             CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
    9765           1 :             eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
    9766             :                                        GDALScaledProgress, pScaledProgress);
    9767             :         }
    9768          39 :         else if (eDT == GDT_UInt16)
    9769             :         {
    9770           2 :             CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
    9771           2 :             eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9772             :                                         GDALScaledProgress, pScaledProgress);
    9773             :         }
    9774          37 :         else if (eDT == GDT_Int16)
    9775             :         {
    9776           5 :             CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
    9777           5 :             eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9778             :                                          GDALScaledProgress, pScaledProgress);
    9779             :         }
    9780          32 :         else if (eDT == GDT_UInt32)
    9781             :         {
    9782           2 :             CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
    9783           2 :             eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9784             :                                          GDALScaledProgress, pScaledProgress);
    9785             :         }
    9786          30 :         else if (eDT == GDT_Int32)
    9787             :         {
    9788          18 :             CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
    9789          18 :             eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9790             :                                         GDALScaledProgress, pScaledProgress);
    9791             :         }
    9792          12 :         else if (eDT == GDT_UInt64)
    9793             :         {
    9794           2 :             CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
    9795           2 :             eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
    9796             :                                                nYSize, GDALScaledProgress,
    9797             :                                                pScaledProgress);
    9798             :         }
    9799          10 :         else if (eDT == GDT_Int64)
    9800             :         {
    9801           2 :             CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
    9802             :             eErr =
    9803           2 :                 NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
    9804             :                                            GDALScaledProgress, pScaledProgress);
    9805             :         }
    9806           8 :         else if (eDT == GDT_Float32)
    9807             :         {
    9808           6 :             CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
    9809           6 :             eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
    9810             :                                        GDALScaledProgress, pScaledProgress);
    9811             :         }
    9812           2 :         else if (eDT == GDT_Float64)
    9813             :         {
    9814           2 :             CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
    9815           2 :             eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
    9816             :                                         GDALScaledProgress, pScaledProgress);
    9817             :         }
    9818             :         else
    9819             :         {
    9820           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9821             :                      "The NetCDF driver does not support GDAL data type %d",
    9822             :                      eDT);
    9823             :         }
    9824             : 
    9825          88 :         GDALDestroyScaledProgress(pScaledProgress);
    9826             :     }
    9827             : 
    9828          55 :     delete (poDS);
    9829             : 
    9830          55 :     CPLFree(panDimIds);
    9831          55 :     CPLFree(panBandDimPos);
    9832          55 :     CPLFree(panBandZLev);
    9833          55 :     CPLFree(panDimVarIds);
    9834          55 :     if (papszExtraDimNames)
    9835           5 :         CSLDestroy(papszExtraDimNames);
    9836             : 
    9837          55 :     if (eErr != CE_None)
    9838           0 :         return nullptr;
    9839             : 
    9840          55 :     pfnProgress(0.95, nullptr, pProgressData);
    9841             : 
    9842             :     // Re-open dataset so we can return it.
    9843         110 :     CPLStringList aosOpenOptions;
    9844          55 :     aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
    9845          55 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
    9846          55 :     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
    9847          55 :     oOpenInfo.papszOpenOptions = aosOpenOptions.List();
    9848          55 :     auto poRetDS = Open(&oOpenInfo);
    9849             : 
    9850             :     // PAM cloning is disabled. See bug #4244.
    9851             :     // if( poDS )
    9852             :     //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    9853             : 
    9854          55 :     pfnProgress(1.0, nullptr, pProgressData);
    9855             : 
    9856          55 :     return poRetDS;
    9857             : }
    9858             : 
    9859             : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
    9860             : // May not be known when Create() is called, see AddProjectionVars().
    9861         251 : void netCDFDataset::ProcessCreationOptions()
    9862             : {
    9863             :     const char *pszConfig =
    9864         251 :         CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
    9865         251 :     if (pszConfig != nullptr)
    9866             :     {
    9867           4 :         if (oWriterConfig.Parse(pszConfig))
    9868             :         {
    9869             :             // Override dataset creation options from the config file
    9870           2 :             std::map<CPLString, CPLString>::iterator oIter;
    9871           3 :             for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
    9872           3 :                  oIter != oWriterConfig.m_oDatasetCreationOptions.end();
    9873           1 :                  ++oIter)
    9874             :             {
    9875           2 :                 papszCreationOptions = CSLSetNameValue(
    9876           2 :                     papszCreationOptions, oIter->first, oIter->second);
    9877             :             }
    9878             :         }
    9879             :     }
    9880             : 
    9881             :     // File format.
    9882         251 :     eFormat = NCDF_FORMAT_NC;
    9883         251 :     const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
    9884         251 :     if (pszValue != nullptr)
    9885             :     {
    9886          93 :         if (EQUAL(pszValue, "NC"))
    9887             :         {
    9888           3 :             eFormat = NCDF_FORMAT_NC;
    9889             :         }
    9890             : #ifdef NETCDF_HAS_NC2
    9891          90 :         else if (EQUAL(pszValue, "NC2"))
    9892             :         {
    9893           1 :             eFormat = NCDF_FORMAT_NC2;
    9894             :         }
    9895             : #endif
    9896          89 :         else if (EQUAL(pszValue, "NC4"))
    9897             :         {
    9898          85 :             eFormat = NCDF_FORMAT_NC4;
    9899             :         }
    9900           4 :         else if (EQUAL(pszValue, "NC4C"))
    9901             :         {
    9902           4 :             eFormat = NCDF_FORMAT_NC4C;
    9903             :         }
    9904             :         else
    9905             :         {
    9906           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9907             :                      "FORMAT=%s in not supported, using the default NC format.",
    9908             :                      pszValue);
    9909             :         }
    9910             :     }
    9911             : 
    9912             :     // COMPRESS option.
    9913         251 :     pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
    9914         251 :     if (pszValue != nullptr)
    9915             :     {
    9916           2 :         if (EQUAL(pszValue, "NONE"))
    9917             :         {
    9918           1 :             eCompress = NCDF_COMPRESS_NONE;
    9919             :         }
    9920           1 :         else if (EQUAL(pszValue, "DEFLATE"))
    9921             :         {
    9922           1 :             eCompress = NCDF_COMPRESS_DEFLATE;
    9923           1 :             if (!((eFormat == NCDF_FORMAT_NC4) ||
    9924           1 :                   (eFormat == NCDF_FORMAT_NC4C)))
    9925             :             {
    9926           0 :                 CPLError(CE_Warning, CPLE_IllegalArg,
    9927             :                          "NOTICE: Format set to NC4C because compression is "
    9928             :                          "set to DEFLATE.");
    9929           0 :                 eFormat = NCDF_FORMAT_NC4C;
    9930             :             }
    9931             :         }
    9932             :         else
    9933             :         {
    9934           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9935             :                      "COMPRESS=%s is not supported.", pszValue);
    9936             :         }
    9937             :     }
    9938             : 
    9939             :     // ZLEVEL option.
    9940         251 :     pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
    9941         251 :     if (pszValue != nullptr)
    9942             :     {
    9943           1 :         nZLevel = atoi(pszValue);
    9944           1 :         if (!(nZLevel >= 1 && nZLevel <= 9))
    9945             :         {
    9946           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
    9947             :                      "ZLEVEL=%s value not recognised, ignoring.", pszValue);
    9948           0 :             nZLevel = NCDF_DEFLATE_LEVEL;
    9949             :         }
    9950             :     }
    9951             : 
    9952             :     // CHUNKING option.
    9953         251 :     bChunking =
    9954         251 :         CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
    9955             : 
    9956             :     // MULTIPLE_LAYERS option.
    9957             :     const char *pszMultipleLayerBehavior =
    9958         251 :         CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
    9959         502 :     const char *pszGeometryEnc = CSLFetchNameValueDef(
    9960         251 :         papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9961         251 :     if (EQUAL(pszMultipleLayerBehavior, "NO") ||
    9962           3 :         EQUAL(pszGeometryEnc, "CF_1.8"))
    9963             :     {
    9964         248 :         eMultipleLayerBehavior = SINGLE_LAYER;
    9965             :     }
    9966           3 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
    9967             :     {
    9968           2 :         eMultipleLayerBehavior = SEPARATE_FILES;
    9969             :     }
    9970           1 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
    9971             :     {
    9972           1 :         if (eFormat == NCDF_FORMAT_NC4)
    9973             :         {
    9974           1 :             eMultipleLayerBehavior = SEPARATE_GROUPS;
    9975             :         }
    9976             :         else
    9977             :         {
    9978           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
    9979             :                      "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
    9980             :                      pszMultipleLayerBehavior);
    9981             :         }
    9982             :     }
    9983             :     else
    9984             :     {
    9985           0 :         CPLError(CE_Warning, CPLE_IllegalArg,
    9986             :                  "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
    9987             :     }
    9988             : 
    9989             :     // Set nCreateMode based on eFormat.
    9990         251 :     switch (eFormat)
    9991             :     {
    9992             : #ifdef NETCDF_HAS_NC2
    9993           1 :         case NCDF_FORMAT_NC2:
    9994           1 :             nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
    9995           1 :             break;
    9996             : #endif
    9997          85 :         case NCDF_FORMAT_NC4:
    9998          85 :             nCreateMode = NC_CLOBBER | NC_NETCDF4;
    9999          85 :             break;
   10000           4 :         case NCDF_FORMAT_NC4C:
   10001           4 :             nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
   10002           4 :             break;
   10003         161 :         case NCDF_FORMAT_NC:
   10004             :         default:
   10005         161 :             nCreateMode = NC_CLOBBER;
   10006         161 :             break;
   10007             :     }
   10008             : 
   10009         251 :     CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
   10010         251 :              eFormat, eCompress, nZLevel);
   10011         251 : }
   10012             : 
   10013         271 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
   10014             : {
   10015         271 :     if (eCompress == NCDF_COMPRESS_DEFLATE)
   10016             :     {
   10017             :         // Must set chunk size to avoid huge performance hit (set
   10018             :         // bChunkingArg=TRUE)
   10019             :         // perhaps another solution it to change the chunk cache?
   10020             :         // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
   10021             :         // TODO: make sure this is okay.
   10022           1 :         CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
   10023             :                  static_cast<int>(bChunkingArg), nZLevel);
   10024             : 
   10025           1 :         int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
   10026           1 :         NCDF_ERR(status);
   10027             : 
   10028           1 :         if (status == NC_NOERR && bChunkingArg && bChunking)
   10029             :         {
   10030             :             // set chunking to be 1 for all dims, except X dim
   10031             :             // size_t chunksize[] = { 1, (size_t)nRasterXSize };
   10032             :             size_t chunksize[MAX_NC_DIMS];
   10033             :             int nd;
   10034           1 :             nc_inq_varndims(cdfid, nVarId, &nd);
   10035           1 :             chunksize[0] = (size_t)1;
   10036           1 :             chunksize[1] = (size_t)1;
   10037           1 :             for (int i = 2; i < nd; i++)
   10038           0 :                 chunksize[i] = (size_t)1;
   10039           1 :             chunksize[nd - 1] = (size_t)nRasterXSize;
   10040             : 
   10041             :             // Config options just for testing purposes
   10042             :             const char *pszBlockXSize =
   10043           1 :                 CPLGetConfigOption("BLOCKXSIZE", nullptr);
   10044           1 :             if (pszBlockXSize)
   10045           0 :                 chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
   10046             : 
   10047             :             const char *pszBlockYSize =
   10048           1 :                 CPLGetConfigOption("BLOCKYSIZE", nullptr);
   10049           1 :             if (nd >= 2 && pszBlockYSize)
   10050           0 :                 chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
   10051             : 
   10052           1 :             CPLDebug("GDAL_netCDF",
   10053             :                      "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
   10054           1 :                      (long)chunksize[0], (long)chunksize[1],
   10055           1 :                      (long)chunksize[nd - 1], nd);
   10056             : #ifdef NCDF_DEBUG
   10057             :             for (int i = 0; i < nd; i++)
   10058             :                 CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
   10059             :                          chunksize[i]);
   10060             : #endif
   10061             : 
   10062           1 :             status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
   10063           1 :             NCDF_ERR(status);
   10064             :         }
   10065             :         else
   10066             :         {
   10067           0 :             CPLDebug("GDAL_netCDF", "chunksize not set");
   10068             :         }
   10069           1 :         return status;
   10070             :     }
   10071         270 :     return NC_NOERR;
   10072             : }
   10073             : 
   10074             : /************************************************************************/
   10075             : /*                           NCDFUnloadDriver()                         */
   10076             : /************************************************************************/
   10077             : 
   10078           8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
   10079             : {
   10080           8 :     if (hNCMutex != nullptr)
   10081           4 :         CPLDestroyMutex(hNCMutex);
   10082           8 :     hNCMutex = nullptr;
   10083           8 : }
   10084             : 
   10085             : /************************************************************************/
   10086             : /*                          GDALRegister_netCDF()                       */
   10087             : /************************************************************************/
   10088             : 
   10089             : class GDALnetCDFDriver final : public GDALDriver
   10090             : {
   10091             :   public:
   10092          14 :     GDALnetCDFDriver() = default;
   10093             : 
   10094        1052 :     const char *GetMetadataItem(const char *pszName,
   10095             :                                 const char *pszDomain) override
   10096             :     {
   10097        1052 :         if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
   10098             :         {
   10099          13 :             InitializeDCAPVirtualIO();
   10100             :         }
   10101        1052 :         return GDALDriver::GetMetadataItem(pszName, pszDomain);
   10102             :     }
   10103             : 
   10104          79 :     char **GetMetadata(const char *pszDomain) override
   10105             :     {
   10106          79 :         InitializeDCAPVirtualIO();
   10107          79 :         return GDALDriver::GetMetadata(pszDomain);
   10108             :     }
   10109             : 
   10110             :   private:
   10111             :     bool m_bInitialized = false;
   10112             : 
   10113          92 :     void InitializeDCAPVirtualIO()
   10114             :     {
   10115          92 :         if (!m_bInitialized)
   10116             :         {
   10117          11 :             m_bInitialized = true;
   10118             : 
   10119             : #ifdef ENABLE_UFFD
   10120          11 :             if (CPLIsUserFaultMappingSupported())
   10121             :             {
   10122          11 :                 SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
   10123             :             }
   10124             : #endif
   10125             :         }
   10126          92 :     }
   10127             : };
   10128             : 
   10129          14 : void GDALRegister_netCDF()
   10130             : 
   10131             : {
   10132          14 :     if (!GDAL_CHECK_VERSION("netCDF driver"))
   10133           0 :         return;
   10134             : 
   10135          14 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
   10136           0 :         return;
   10137             : 
   10138          14 :     GDALDriver *poDriver = new GDALnetCDFDriver();
   10139          14 :     netCDFDriverSetCommonMetadata(poDriver);
   10140             : 
   10141          14 :     poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
   10142          14 :                               GDAL_DEFAULT_NCDF_CONVENTIONS);
   10143          14 :     poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
   10144             : 
   10145             :     // Set pfns and register driver.
   10146          14 :     poDriver->pfnOpen = netCDFDataset::Open;
   10147          14 :     poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
   10148          14 :     poDriver->pfnCreate = netCDFDataset::Create;
   10149          14 :     poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
   10150          14 :     poDriver->pfnUnloadDriver = NCDFUnloadDriver;
   10151             : 
   10152          14 :     GetGDALDriverManager()->RegisterDriver(poDriver);
   10153             : }
   10154             : 
   10155             : /************************************************************************/
   10156             : /*                          New functions                               */
   10157             : /************************************************************************/
   10158             : 
   10159             : /* Test for GDAL version string >= target */
   10160         236 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
   10161             : {
   10162             : 
   10163             :     // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
   10164         236 :     if (pszVersion == nullptr || EQUAL(pszVersion, ""))
   10165           0 :         return false;
   10166         236 :     else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
   10167           0 :         return false;
   10168             :     // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
   10169         236 :     else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
   10170           0 :         return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
   10171         236 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
   10172           2 :         return nTarget <= 1900;
   10173         234 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
   10174           0 :         return nTarget <= 1800;
   10175             : 
   10176         234 :     char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
   10177             : 
   10178         234 :     int nVersions[] = {0, 0, 0, 0};
   10179         936 :     for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
   10180             :          iToken++)
   10181             :     {
   10182         702 :         nVersions[iToken] = atoi(papszTokens[iToken]);
   10183         702 :         if (nVersions[iToken] < 0)
   10184           0 :             nVersions[iToken] = 0;
   10185         702 :         else if (nVersions[iToken] > 99)
   10186           0 :             nVersions[iToken] = 99;
   10187             :     }
   10188             : 
   10189         234 :     int nVersion = 0;
   10190         234 :     if (nVersions[0] > 1 || nVersions[1] >= 10)
   10191         234 :         nVersion =
   10192         234 :             GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
   10193             :     else
   10194           0 :         nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
   10195           0 :                    nVersions[2] * 10 + nVersions[3];
   10196             : 
   10197         234 :     CSLDestroy(papszTokens);
   10198         234 :     return nTarget <= nVersion;
   10199             : }
   10200             : 
   10201             : // Add Conventions, GDAL version and history.
   10202         164 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
   10203             :                                bool bWriteGDALVersion, bool bWriteGDALHistory,
   10204             :                                const char *pszOldHist,
   10205             :                                const char *pszFunctionName,
   10206             :                                const char *pszCFVersion)
   10207             : {
   10208         164 :     if (pszCFVersion == nullptr)
   10209             :     {
   10210          39 :         pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
   10211             :     }
   10212         164 :     int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
   10213             :                                  strlen(pszCFVersion), pszCFVersion);
   10214         164 :     NCDF_ERR(status);
   10215             : 
   10216         164 :     if (bWriteGDALVersion)
   10217             :     {
   10218         162 :         const char *pszNCDF_GDAL = GDALVersionInfo("--version");
   10219         162 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
   10220             :                                  strlen(pszNCDF_GDAL), pszNCDF_GDAL);
   10221         162 :         NCDF_ERR(status);
   10222             :     }
   10223             : 
   10224         164 :     if (bWriteGDALHistory)
   10225             :     {
   10226             :         // Add history.
   10227         324 :         CPLString osTmp;
   10228             : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
   10229             :         if (!EQUAL(GDALGetCmdLine(), ""))
   10230             :             osTmp = GDALGetCmdLine();
   10231             :         else
   10232             :             osTmp =
   10233             :                 CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10234             : #else
   10235         162 :         osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10236             : #endif
   10237             : 
   10238         162 :         NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
   10239             :     }
   10240           2 :     else if (pszOldHist != nullptr)
   10241             :     {
   10242           0 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10243             :                                  strlen(pszOldHist), pszOldHist);
   10244           0 :         NCDF_ERR(status);
   10245             :     }
   10246         164 : }
   10247             : 
   10248             : // Code taken from cdo and libcdi, used for writing the history attribute.
   10249             : 
   10250             : // void cdoDefHistory(int fileID, char *histstring)
   10251         162 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
   10252             :                            const char *pszOldHist)
   10253             : {
   10254             :     // Check pszOldHist - as if there was no previous history, it will be
   10255             :     // a null pointer - if so set as empty.
   10256         162 :     if (nullptr == pszOldHist)
   10257             :     {
   10258          50 :         pszOldHist = "";
   10259             :     }
   10260             : 
   10261             :     char strtime[32];
   10262         162 :     strtime[0] = '\0';
   10263             : 
   10264         162 :     time_t tp = time(nullptr);
   10265         162 :     if (tp != -1)
   10266             :     {
   10267             :         struct tm ltime;
   10268         162 :         VSILocalTime(&tp, &ltime);
   10269         162 :         (void)strftime(strtime, sizeof(strtime),
   10270             :                        "%a %b %d %H:%M:%S %Y: ", &ltime);
   10271             :     }
   10272             : 
   10273             :     // status = nc_get_att_text(fpImage, NC_GLOBAL,
   10274             :     //                           "history", pszOldHist);
   10275             :     // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
   10276             : 
   10277         162 :     size_t nNewHistSize =
   10278         162 :         strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
   10279             :     char *pszNewHist =
   10280         162 :         static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
   10281             : 
   10282         162 :     strcpy(pszNewHist, strtime);
   10283         162 :     strcat(pszNewHist, pszAddHist);
   10284             : 
   10285             :     // int disableHistory = FALSE;
   10286             :     // if( !disableHistory )
   10287             :     {
   10288         162 :         if (!EQUAL(pszOldHist, ""))
   10289           3 :             strcat(pszNewHist, "\n");
   10290         162 :         strcat(pszNewHist, pszOldHist);
   10291             :     }
   10292             : 
   10293         162 :     const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10294             :                                        strlen(pszNewHist), pszNewHist);
   10295         162 :     NCDF_ERR(status);
   10296             : 
   10297         162 :     CPLFree(pszNewHist);
   10298         162 : }
   10299             : 
   10300        5894 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
   10301             :                              size_t *nDestSize)
   10302             : {
   10303             :     /* Reallocate the data string until the content fits */
   10304        5894 :     while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
   10305             :     {
   10306         394 :         (*nDestSize) *= 2;
   10307         394 :         *ppszDest = static_cast<char *>(
   10308         394 :             CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
   10309             : #ifdef NCDF_DEBUG
   10310             :         CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
   10311             :                  (*nDestSize) / 2, *nDestSize);
   10312             : #endif
   10313             :     }
   10314        5500 :     strcat(*ppszDest, pszSrc);
   10315             : 
   10316        5500 :     return CE_None;
   10317             : }
   10318             : 
   10319             : /* helper function for NCDFGetAttr() */
   10320             : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
   10321             : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
   10322             : /* *ppszValue is the responsibility of the caller and must be freed */
   10323       61446 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
   10324             :                            double *pdfValue, char **ppszValue)
   10325             : {
   10326       61446 :     nc_type nAttrType = NC_NAT;
   10327       61446 :     size_t nAttrLen = 0;
   10328             : 
   10329       61446 :     if (ppszValue)
   10330       60317 :         *ppszValue = nullptr;
   10331             : 
   10332       61446 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10333       61446 :     if (status != NC_NOERR)
   10334       33370 :         return CE_Failure;
   10335             : 
   10336             : #ifdef NCDF_DEBUG
   10337             :     CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
   10338             :              nAttrLen, nAttrType);
   10339             : #endif
   10340       28076 :     if (nAttrLen == 0 && nAttrType != NC_CHAR)
   10341           1 :         return CE_Failure;
   10342             : 
   10343             :     /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
   10344       28075 :     size_t nAttrValueSize = nAttrLen + 1;
   10345       28075 :     if (nAttrType != NC_CHAR && nAttrValueSize < 10)
   10346        3069 :         nAttrValueSize = 10;
   10347       28075 :     if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
   10348        1509 :         nAttrValueSize = 20;
   10349       28075 :     if (nAttrType == NC_INT64 && nAttrValueSize < 20)
   10350          20 :         nAttrValueSize = 22;
   10351             :     char *pszAttrValue =
   10352       28075 :         static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
   10353       28075 :     *pszAttrValue = '\0';
   10354             : 
   10355       28075 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10356         560 :         NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
   10357             : 
   10358       28075 :     double dfValue = 0.0;
   10359       28075 :     size_t m = 0;
   10360             :     char szTemp[256];
   10361       28075 :     bool bSetDoubleFromStr = false;
   10362             : 
   10363       28075 :     switch (nAttrType)
   10364             :     {
   10365       25004 :         case NC_CHAR:
   10366       25004 :             CPL_IGNORE_RET_VAL(
   10367       25004 :                 nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
   10368       25004 :             pszAttrValue[nAttrLen] = '\0';
   10369       25004 :             bSetDoubleFromStr = true;
   10370       25004 :             dfValue = 0.0;
   10371       25004 :             break;
   10372          90 :         case NC_BYTE:
   10373             :         {
   10374             :             signed char *pscTemp = static_cast<signed char *>(
   10375          90 :                 CPLCalloc(nAttrLen, sizeof(signed char)));
   10376          90 :             nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
   10377          90 :             dfValue = static_cast<double>(pscTemp[0]);
   10378          90 :             if (nAttrLen > 1)
   10379             :             {
   10380          20 :                 for (m = 0; m < nAttrLen - 1; m++)
   10381             :                 {
   10382          11 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10383          11 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10384             :                 }
   10385             :             }
   10386          90 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10387          90 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10388          90 :             CPLFree(pscTemp);
   10389          90 :             break;
   10390             :         }
   10391         463 :         case NC_SHORT:
   10392             :         {
   10393             :             short *psTemp =
   10394         463 :                 static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
   10395         463 :             nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
   10396         463 :             dfValue = static_cast<double>(psTemp[0]);
   10397         463 :             if (nAttrLen > 1)
   10398             :             {
   10399         724 :                 for (m = 0; m < nAttrLen - 1; m++)
   10400             :                 {
   10401         362 :                     snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10402         362 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10403             :                 }
   10404             :             }
   10405         463 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10406         463 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10407         463 :             CPLFree(psTemp);
   10408         463 :             break;
   10409             :         }
   10410         524 :         case NC_INT:
   10411             :         {
   10412         524 :             int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10413         524 :             nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
   10414         524 :             dfValue = static_cast<double>(pnTemp[0]);
   10415         524 :             if (nAttrLen > 1)
   10416             :             {
   10417         214 :                 for (m = 0; m < nAttrLen - 1; m++)
   10418             :                 {
   10419         137 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10420         137 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10421             :                 }
   10422             :             }
   10423         524 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10424         524 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10425         524 :             CPLFree(pnTemp);
   10426         524 :             break;
   10427             :         }
   10428         371 :         case NC_FLOAT:
   10429             :         {
   10430             :             float *pfTemp =
   10431         371 :                 static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10432         371 :             nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
   10433         371 :             dfValue = static_cast<double>(pfTemp[0]);
   10434         371 :             if (nAttrLen > 1)
   10435             :             {
   10436          56 :                 for (m = 0; m < nAttrLen - 1; m++)
   10437             :                 {
   10438          28 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10439          28 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10440             :                 }
   10441             :             }
   10442         371 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10443         371 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10444         371 :             CPLFree(pfTemp);
   10445         371 :             break;
   10446             :         }
   10447        1509 :         case NC_DOUBLE:
   10448             :         {
   10449             :             double *pdfTemp =
   10450        1509 :                 static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10451        1509 :             nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
   10452        1509 :             dfValue = pdfTemp[0];
   10453        1509 :             if (nAttrLen > 1)
   10454             :             {
   10455         162 :                 for (m = 0; m < nAttrLen - 1; m++)
   10456             :                 {
   10457          88 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10458          88 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10459             :                 }
   10460             :             }
   10461        1509 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10462        1509 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10463        1509 :             CPLFree(pdfTemp);
   10464        1509 :             break;
   10465             :         }
   10466           8 :         case NC_STRING:
   10467             :         {
   10468             :             char **ppszTemp =
   10469           8 :                 static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
   10470           8 :             nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
   10471           8 :             bSetDoubleFromStr = true;
   10472           8 :             dfValue = 0.0;
   10473           8 :             if (nAttrLen > 1)
   10474             :             {
   10475          15 :                 for (m = 0; m < nAttrLen - 1; m++)
   10476             :                 {
   10477          10 :                     NCDFSafeStrcat(&pszAttrValue,
   10478          10 :                                    ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10479             :                                    &nAttrValueSize);
   10480          10 :                     NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
   10481             :                 }
   10482             :             }
   10483           8 :             NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10484             :                            &nAttrValueSize);
   10485           8 :             nc_free_string(nAttrLen, ppszTemp);
   10486           8 :             CPLFree(ppszTemp);
   10487           8 :             break;
   10488             :         }
   10489          26 :         case NC_UBYTE:
   10490             :         {
   10491             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   10492          26 :                 CPLCalloc(nAttrLen, sizeof(unsigned char)));
   10493          26 :             nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
   10494          26 :             dfValue = static_cast<double>(pucTemp[0]);
   10495          26 :             if (nAttrLen > 1)
   10496             :             {
   10497           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10498             :                 {
   10499           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10500           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10501             :                 }
   10502             :             }
   10503          26 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10504          26 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10505          26 :             CPLFree(pucTemp);
   10506          26 :             break;
   10507             :         }
   10508          24 :         case NC_USHORT:
   10509             :         {
   10510             :             unsigned short *pusTemp;
   10511             :             pusTemp = static_cast<unsigned short *>(
   10512          24 :                 CPLCalloc(nAttrLen, sizeof(unsigned short)));
   10513          24 :             nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
   10514          24 :             dfValue = static_cast<double>(pusTemp[0]);
   10515          24 :             if (nAttrLen > 1)
   10516             :             {
   10517          10 :                 for (m = 0; m < nAttrLen - 1; m++)
   10518             :                 {
   10519           5 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10520           5 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10521             :                 }
   10522             :             }
   10523          24 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10524          24 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10525          24 :             CPLFree(pusTemp);
   10526          24 :             break;
   10527             :         }
   10528          16 :         case NC_UINT:
   10529             :         {
   10530             :             unsigned int *punTemp =
   10531          16 :                 static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10532          16 :             nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
   10533          16 :             dfValue = static_cast<double>(punTemp[0]);
   10534          16 :             if (nAttrLen > 1)
   10535             :             {
   10536           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10537             :                 {
   10538           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   10539           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10540             :                 }
   10541             :             }
   10542          16 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   10543          16 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10544          16 :             CPLFree(punTemp);
   10545          16 :             break;
   10546             :         }
   10547          20 :         case NC_INT64:
   10548             :         {
   10549             :             GIntBig *panTemp =
   10550          20 :                 static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
   10551          20 :             nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
   10552          20 :             dfValue = static_cast<double>(panTemp[0]);
   10553          20 :             if (nAttrLen > 1)
   10554             :             {
   10555           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10556             :                 {
   10557           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
   10558           0 :                                 panTemp[m]);
   10559           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10560             :                 }
   10561             :             }
   10562          20 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
   10563          20 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10564          20 :             CPLFree(panTemp);
   10565          20 :             break;
   10566             :         }
   10567          20 :         case NC_UINT64:
   10568             :         {
   10569             :             GUIntBig *panTemp =
   10570          20 :                 static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
   10571          20 :             nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
   10572          20 :             dfValue = static_cast<double>(panTemp[0]);
   10573          20 :             if (nAttrLen > 1)
   10574             :             {
   10575           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10576             :                 {
   10577           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
   10578           0 :                                 panTemp[m]);
   10579           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10580             :                 }
   10581             :             }
   10582          20 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
   10583          20 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10584          20 :             CPLFree(panTemp);
   10585          20 :             break;
   10586             :         }
   10587           0 :         default:
   10588           0 :             CPLDebug("GDAL_netCDF",
   10589             :                      "NCDFGetAttr unsupported type %d for attribute %s",
   10590             :                      nAttrType, pszAttrName);
   10591           0 :             break;
   10592             :     }
   10593             : 
   10594       28075 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10595         560 :         NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
   10596             : 
   10597       28075 :     if (bSetDoubleFromStr)
   10598             :     {
   10599       25012 :         if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
   10600             :         {
   10601       24830 :             if (ppszValue == nullptr && pdfValue != nullptr)
   10602             :             {
   10603           1 :                 CPLFree(pszAttrValue);
   10604           1 :                 return CE_Failure;
   10605             :             }
   10606             :         }
   10607       25011 :         dfValue = CPLAtof(pszAttrValue);
   10608             :     }
   10609             : 
   10610             :     /* set return values */
   10611       28074 :     if (ppszValue)
   10612       27770 :         *ppszValue = pszAttrValue;
   10613             :     else
   10614         304 :         CPLFree(pszAttrValue);
   10615             : 
   10616       28074 :     if (pdfValue)
   10617         304 :         *pdfValue = dfValue;
   10618             : 
   10619       28074 :     return CE_None;
   10620             : }
   10621             : 
   10622             : /* sets pdfValue to first value found */
   10623        1129 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10624             :                    double *pdfValue)
   10625             : {
   10626        1129 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
   10627             : }
   10628             : 
   10629             : /* pszValue is the responsibility of the caller and must be freed */
   10630       60317 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10631             :                    char **pszValue)
   10632             : {
   10633       60317 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
   10634             : }
   10635             : 
   10636             : /* By default write NC_CHAR, but detect for int/float/double and */
   10637             : /* NC4 string arrays */
   10638         103 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10639             :                           const char *pszValue)
   10640             : {
   10641         103 :     int status = 0;
   10642         103 :     char *pszTemp = nullptr;
   10643             : 
   10644             :     /* get the attribute values as tokens */
   10645         103 :     char **papszValues = NCDFTokenizeArray(pszValue);
   10646         103 :     if (papszValues == nullptr)
   10647           0 :         return CE_Failure;
   10648             : 
   10649         103 :     size_t nAttrLen = CSLCount(papszValues);
   10650             : 
   10651             :     /* first detect type */
   10652         103 :     nc_type nAttrType = NC_CHAR;
   10653         103 :     nc_type nTmpAttrType = NC_CHAR;
   10654         219 :     for (size_t i = 0; i < nAttrLen; i++)
   10655             :     {
   10656         116 :         nTmpAttrType = NC_CHAR;
   10657         116 :         bool bFoundType = false;
   10658         116 :         errno = 0;
   10659         116 :         int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   10660             :         /* test for int */
   10661             :         /* TODO test for Byte and short - can this be done safely? */
   10662         116 :         if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
   10663             :         {
   10664             :             char szTemp[256];
   10665          19 :             CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
   10666          19 :             if (EQUAL(szTemp, papszValues[i]))
   10667             :             {
   10668          19 :                 bFoundType = true;
   10669          19 :                 nTmpAttrType = NC_INT;
   10670             :             }
   10671             :             else
   10672             :             {
   10673             :                 unsigned int unValue = static_cast<unsigned int>(
   10674           0 :                     strtoul(papszValues[i], &pszTemp, 10));
   10675           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
   10676           0 :                 if (EQUAL(szTemp, papszValues[i]))
   10677             :                 {
   10678           0 :                     bFoundType = true;
   10679           0 :                     nTmpAttrType = NC_UINT;
   10680             :                 }
   10681             :             }
   10682             :         }
   10683         116 :         if (!bFoundType)
   10684             :         {
   10685             :             /* test for double */
   10686          97 :             errno = 0;
   10687          97 :             double dfValue = CPLStrtod(papszValues[i], &pszTemp);
   10688          97 :             if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
   10689             :             {
   10690             :                 // Test for float instead of double.
   10691             :                 // strtof() is C89, which is not available in MSVC.
   10692             :                 // See if we loose precision if we cast to float and write to
   10693             :                 // char*.
   10694          14 :                 float fValue = float(dfValue);
   10695             :                 char szTemp[256];
   10696          14 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
   10697          14 :                 if (EQUAL(szTemp, papszValues[i]))
   10698           8 :                     nTmpAttrType = NC_FLOAT;
   10699             :                 else
   10700           6 :                     nTmpAttrType = NC_DOUBLE;
   10701             :             }
   10702             :         }
   10703         116 :         if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
   10704          96 :              nTmpAttrType > nAttrType) ||
   10705          96 :             (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
   10706           5 :             (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
   10707          20 :             nAttrType = nTmpAttrType;
   10708             :     }
   10709             : 
   10710             : #ifdef DEBUG
   10711         103 :     if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
   10712             :     {
   10713           0 :         nAttrType = NC_DOUBLE;
   10714           0 :         nAttrLen = 0;
   10715             :     }
   10716             : #endif
   10717             : 
   10718             :     /* now write the data */
   10719         103 :     if (nAttrType == NC_CHAR)
   10720             :     {
   10721          83 :         int nTmpFormat = 0;
   10722          83 :         if (nAttrLen > 1)
   10723             :         {
   10724           0 :             status = nc_inq_format(nCdfId, &nTmpFormat);
   10725           0 :             NCDF_ERR(status);
   10726             :         }
   10727          83 :         if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
   10728           0 :             status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
   10729             :                                        const_cast<const char **>(papszValues));
   10730             :         else
   10731          83 :             status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
   10732             :                                      strlen(pszValue), pszValue);
   10733          83 :         NCDF_ERR(status);
   10734             :     }
   10735             :     else
   10736             :     {
   10737          20 :         switch (nAttrType)
   10738             :         {
   10739          11 :             case NC_INT:
   10740             :             {
   10741             :                 int *pnTemp =
   10742          11 :                     static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10743          30 :                 for (size_t i = 0; i < nAttrLen; i++)
   10744             :                 {
   10745          19 :                     pnTemp[i] =
   10746          19 :                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   10747             :                 }
   10748          11 :                 status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
   10749             :                                         nAttrLen, pnTemp);
   10750          11 :                 NCDF_ERR(status);
   10751          11 :                 CPLFree(pnTemp);
   10752          11 :                 break;
   10753             :             }
   10754           0 :             case NC_UINT:
   10755             :             {
   10756             :                 unsigned int *punTemp = static_cast<unsigned int *>(
   10757           0 :                     CPLCalloc(nAttrLen, sizeof(unsigned int)));
   10758           0 :                 for (size_t i = 0; i < nAttrLen; i++)
   10759             :                 {
   10760           0 :                     punTemp[i] = static_cast<unsigned int>(
   10761           0 :                         strtol(papszValues[i], &pszTemp, 10));
   10762             :                 }
   10763           0 :                 status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
   10764             :                                          nAttrLen, punTemp);
   10765           0 :                 NCDF_ERR(status);
   10766           0 :                 CPLFree(punTemp);
   10767           0 :                 break;
   10768             :             }
   10769           6 :             case NC_FLOAT:
   10770             :             {
   10771             :                 float *pfTemp =
   10772           6 :                     static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10773          14 :                 for (size_t i = 0; i < nAttrLen; i++)
   10774             :                 {
   10775           8 :                     pfTemp[i] =
   10776           8 :                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
   10777             :                 }
   10778           6 :                 status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
   10779             :                                           nAttrLen, pfTemp);
   10780           6 :                 NCDF_ERR(status);
   10781           6 :                 CPLFree(pfTemp);
   10782           6 :                 break;
   10783             :             }
   10784           3 :             case NC_DOUBLE:
   10785             :             {
   10786             :                 double *pdfTemp =
   10787           3 :                     static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10788           9 :                 for (size_t i = 0; i < nAttrLen; i++)
   10789             :                 {
   10790           6 :                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
   10791             :                 }
   10792           3 :                 status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
   10793             :                                            NC_DOUBLE, nAttrLen, pdfTemp);
   10794           3 :                 NCDF_ERR(status);
   10795           3 :                 CPLFree(pdfTemp);
   10796           3 :                 break;
   10797             :             }
   10798           0 :             default:
   10799           0 :                 if (papszValues)
   10800           0 :                     CSLDestroy(papszValues);
   10801           0 :                 return CE_Failure;
   10802             :                 break;
   10803             :         }
   10804             :     }
   10805             : 
   10806         103 :     if (papszValues)
   10807         103 :         CSLDestroy(papszValues);
   10808             : 
   10809         103 :     return CE_None;
   10810             : }
   10811             : 
   10812          72 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
   10813             : {
   10814             :     /* get var information */
   10815          72 :     int nVarDimId = -1;
   10816          72 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   10817          72 :     if (status != NC_NOERR || nVarDimId != 1)
   10818           0 :         return CE_Failure;
   10819             : 
   10820          72 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   10821          72 :     if (status != NC_NOERR)
   10822           0 :         return CE_Failure;
   10823             : 
   10824          72 :     nc_type nVarType = NC_NAT;
   10825          72 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   10826          72 :     if (status != NC_NOERR)
   10827           0 :         return CE_Failure;
   10828             : 
   10829          72 :     size_t nVarLen = 0;
   10830          72 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   10831          72 :     if (status != NC_NOERR)
   10832           0 :         return CE_Failure;
   10833             : 
   10834          72 :     size_t start[1] = {0};
   10835          72 :     size_t count[1] = {nVarLen};
   10836             : 
   10837             :     /* Allocate guaranteed minimum size */
   10838          72 :     size_t nVarValueSize = NCDF_MAX_STR_LEN;
   10839             :     char *pszVarValue =
   10840          72 :         static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
   10841          72 :     *pszVarValue = '\0';
   10842             : 
   10843          72 :     if (nVarLen == 0)
   10844             :     {
   10845             :         /* set return values */
   10846           1 :         *pszValue = pszVarValue;
   10847             : 
   10848           1 :         return CE_None;
   10849             :     }
   10850             : 
   10851          71 :     if (nVarLen > 1 && nVarType != NC_CHAR)
   10852          38 :         NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
   10853             : 
   10854          71 :     switch (nVarType)
   10855             :     {
   10856           0 :         case NC_CHAR:
   10857           0 :             nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
   10858           0 :             pszVarValue[nVarLen] = '\0';
   10859           0 :             break;
   10860           0 :         case NC_BYTE:
   10861             :         {
   10862             :             signed char *pscTemp = static_cast<signed char *>(
   10863           0 :                 CPLCalloc(nVarLen, sizeof(signed char)));
   10864           0 :             nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   10865             :             char szTemp[256];
   10866           0 :             size_t m = 0;
   10867           0 :             for (; m < nVarLen - 1; m++)
   10868             :             {
   10869           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10870           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10871             :             }
   10872           0 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10873           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10874           0 :             CPLFree(pscTemp);
   10875           0 :             break;
   10876             :         }
   10877           0 :         case NC_SHORT:
   10878             :         {
   10879             :             short *psTemp =
   10880           0 :                 static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   10881           0 :             nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
   10882             :             char szTemp[256];
   10883           0 :             size_t m = 0;
   10884           0 :             for (; m < nVarLen - 1; m++)
   10885             :             {
   10886           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10887           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10888             :             }
   10889           0 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10890           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10891           0 :             CPLFree(psTemp);
   10892           0 :             break;
   10893             :         }
   10894          19 :         case NC_INT:
   10895             :         {
   10896          19 :             int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   10897          19 :             nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
   10898             :             char szTemp[256];
   10899          19 :             size_t m = 0;
   10900          36 :             for (; m < nVarLen - 1; m++)
   10901             :             {
   10902          17 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10903          17 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10904             :             }
   10905          19 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10906          19 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10907          19 :             CPLFree(pnTemp);
   10908          19 :             break;
   10909             :         }
   10910           7 :         case NC_FLOAT:
   10911             :         {
   10912             :             float *pfTemp =
   10913           7 :                 static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   10914           7 :             nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
   10915             :             char szTemp[256];
   10916           7 :             size_t m = 0;
   10917         324 :             for (; m < nVarLen - 1; m++)
   10918             :             {
   10919         317 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10920         317 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10921             :             }
   10922           7 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10923           7 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10924           7 :             CPLFree(pfTemp);
   10925           7 :             break;
   10926             :         }
   10927          44 :         case NC_DOUBLE:
   10928             :         {
   10929             :             double *pdfTemp =
   10930          44 :                 static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   10931          44 :             nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   10932             :             char szTemp[256];
   10933          44 :             size_t m = 0;
   10934         220 :             for (; m < nVarLen - 1; m++)
   10935             :             {
   10936         176 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10937         176 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10938             :             }
   10939          44 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10940          44 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10941          44 :             CPLFree(pdfTemp);
   10942          44 :             break;
   10943             :         }
   10944           0 :         case NC_STRING:
   10945             :         {
   10946             :             char **ppszTemp =
   10947           0 :                 static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
   10948           0 :             nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
   10949           0 :             size_t m = 0;
   10950           0 :             for (; m < nVarLen - 1; m++)
   10951             :             {
   10952           0 :                 NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   10953           0 :                 NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
   10954             :             }
   10955           0 :             NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   10956           0 :             nc_free_string(nVarLen, ppszTemp);
   10957           0 :             CPLFree(ppszTemp);
   10958           0 :             break;
   10959             :         }
   10960           0 :         case NC_UBYTE:
   10961             :         {
   10962             :             unsigned char *pucTemp;
   10963             :             pucTemp = static_cast<unsigned char *>(
   10964           0 :                 CPLCalloc(nVarLen, sizeof(unsigned char)));
   10965           0 :             nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
   10966             :             char szTemp[256];
   10967           0 :             size_t m = 0;
   10968           0 :             for (; m < nVarLen - 1; m++)
   10969             :             {
   10970           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10971           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10972             :             }
   10973           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10974           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10975           0 :             CPLFree(pucTemp);
   10976           0 :             break;
   10977             :         }
   10978           0 :         case NC_USHORT:
   10979             :         {
   10980             :             unsigned short *pusTemp;
   10981             :             pusTemp = static_cast<unsigned short *>(
   10982           0 :                 CPLCalloc(nVarLen, sizeof(unsigned short)));
   10983           0 :             nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
   10984             :             char szTemp[256];
   10985           0 :             size_t m = 0;
   10986           0 :             for (; m < nVarLen - 1; m++)
   10987             :             {
   10988           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10989           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10990             :             }
   10991           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10992           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10993           0 :             CPLFree(pusTemp);
   10994           0 :             break;
   10995             :         }
   10996           0 :         case NC_UINT:
   10997             :         {
   10998             :             unsigned int *punTemp;
   10999             :             punTemp = static_cast<unsigned int *>(
   11000           0 :                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11001           0 :             nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
   11002             :             char szTemp[256];
   11003           0 :             size_t m = 0;
   11004           0 :             for (; m < nVarLen - 1; m++)
   11005             :             {
   11006           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   11007           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11008             :             }
   11009           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   11010           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11011           0 :             CPLFree(punTemp);
   11012           0 :             break;
   11013             :         }
   11014           1 :         case NC_INT64:
   11015             :         {
   11016             :             long long *pnTemp =
   11017           1 :                 static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
   11018           1 :             nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
   11019             :             char szTemp[256];
   11020           1 :             size_t m = 0;
   11021           2 :             for (; m < nVarLen - 1; m++)
   11022             :             {
   11023           1 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
   11024           1 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11025             :             }
   11026           1 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
   11027           1 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11028           1 :             CPLFree(pnTemp);
   11029           1 :             break;
   11030             :         }
   11031           0 :         case NC_UINT64:
   11032             :         {
   11033             :             unsigned long long *pnTemp = static_cast<unsigned long long *>(
   11034           0 :                 CPLCalloc(nVarLen, sizeof(unsigned long long)));
   11035           0 :             nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
   11036             :             char szTemp[256];
   11037           0 :             size_t m = 0;
   11038           0 :             for (; m < nVarLen - 1; m++)
   11039             :             {
   11040           0 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
   11041           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11042             :             }
   11043           0 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
   11044           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11045           0 :             CPLFree(pnTemp);
   11046           0 :             break;
   11047             :         }
   11048           0 :         default:
   11049           0 :             CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
   11050             :                      nVarType);
   11051           0 :             CPLFree(pszVarValue);
   11052           0 :             pszVarValue = nullptr;
   11053           0 :             break;
   11054             :     }
   11055             : 
   11056          71 :     if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
   11057          38 :         NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
   11058             : 
   11059             :     /* set return values */
   11060          71 :     *pszValue = pszVarValue;
   11061             : 
   11062          71 :     return CE_None;
   11063             : }
   11064             : 
   11065           8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
   11066             : {
   11067           8 :     if (EQUAL(pszValue, ""))
   11068           0 :         return CE_Failure;
   11069             : 
   11070             :     /* get var information */
   11071           8 :     int nVarDimId = -1;
   11072           8 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   11073           8 :     if (status != NC_NOERR || nVarDimId != 1)
   11074           0 :         return CE_Failure;
   11075             : 
   11076           8 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11077           8 :     if (status != NC_NOERR)
   11078           0 :         return CE_Failure;
   11079             : 
   11080           8 :     nc_type nVarType = NC_CHAR;
   11081           8 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11082           8 :     if (status != NC_NOERR)
   11083           0 :         return CE_Failure;
   11084             : 
   11085           8 :     size_t nVarLen = 0;
   11086           8 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11087           8 :     if (status != NC_NOERR)
   11088           0 :         return CE_Failure;
   11089             : 
   11090           8 :     size_t start[1] = {0};
   11091           8 :     size_t count[1] = {nVarLen};
   11092             : 
   11093             :     /* get the values as tokens */
   11094           8 :     char **papszValues = NCDFTokenizeArray(pszValue);
   11095           8 :     if (papszValues == nullptr)
   11096           0 :         return CE_Failure;
   11097             : 
   11098           8 :     nVarLen = CSLCount(papszValues);
   11099             : 
   11100             :     /* now write the data */
   11101           8 :     if (nVarType == NC_CHAR)
   11102             :     {
   11103           0 :         status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
   11104           0 :         NCDF_ERR(status);
   11105             :     }
   11106             :     else
   11107             :     {
   11108           8 :         switch (nVarType)
   11109             :         {
   11110           0 :             case NC_BYTE:
   11111             :             {
   11112             :                 signed char *pscTemp = static_cast<signed char *>(
   11113           0 :                     CPLCalloc(nVarLen, sizeof(signed char)));
   11114           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11115             :                 {
   11116           0 :                     char *pszTemp = nullptr;
   11117           0 :                     pscTemp[i] = static_cast<signed char>(
   11118           0 :                         strtol(papszValues[i], &pszTemp, 10));
   11119             :                 }
   11120             :                 status =
   11121           0 :                     nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11122           0 :                 NCDF_ERR(status);
   11123           0 :                 CPLFree(pscTemp);
   11124           0 :                 break;
   11125             :             }
   11126           0 :             case NC_SHORT:
   11127             :             {
   11128             :                 short *psTemp =
   11129           0 :                     static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11130           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11131             :                 {
   11132           0 :                     char *pszTemp = nullptr;
   11133           0 :                     psTemp[i] = static_cast<short>(
   11134           0 :                         strtol(papszValues[i], &pszTemp, 10));
   11135             :                 }
   11136             :                 status =
   11137           0 :                     nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
   11138           0 :                 NCDF_ERR(status);
   11139           0 :                 CPLFree(psTemp);
   11140           0 :                 break;
   11141             :             }
   11142           3 :             case NC_INT:
   11143             :             {
   11144             :                 int *pnTemp =
   11145           3 :                     static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11146          11 :                 for (size_t i = 0; i < nVarLen; i++)
   11147             :                 {
   11148           8 :                     char *pszTemp = nullptr;
   11149           8 :                     pnTemp[i] =
   11150           8 :                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   11151             :                 }
   11152           3 :                 status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11153           3 :                 NCDF_ERR(status);
   11154           3 :                 CPLFree(pnTemp);
   11155           3 :                 break;
   11156             :             }
   11157           0 :             case NC_FLOAT:
   11158             :             {
   11159             :                 float *pfTemp =
   11160           0 :                     static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11161           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11162             :                 {
   11163           0 :                     char *pszTemp = nullptr;
   11164           0 :                     pfTemp[i] =
   11165           0 :                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
   11166             :                 }
   11167             :                 status =
   11168           0 :                     nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11169           0 :                 NCDF_ERR(status);
   11170           0 :                 CPLFree(pfTemp);
   11171           0 :                 break;
   11172             :             }
   11173           5 :             case NC_DOUBLE:
   11174             :             {
   11175             :                 double *pdfTemp =
   11176           5 :                     static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11177          19 :                 for (size_t i = 0; i < nVarLen; i++)
   11178             :                 {
   11179          14 :                     char *pszTemp = nullptr;
   11180          14 :                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
   11181             :                 }
   11182             :                 status =
   11183           5 :                     nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11184           5 :                 NCDF_ERR(status);
   11185           5 :                 CPLFree(pdfTemp);
   11186           5 :                 break;
   11187             :             }
   11188           0 :             default:
   11189             :             {
   11190           0 :                 int nTmpFormat = 0;
   11191           0 :                 status = nc_inq_format(nCdfId, &nTmpFormat);
   11192           0 :                 NCDF_ERR(status);
   11193           0 :                 if (nTmpFormat == NCDF_FORMAT_NC4)
   11194             :                 {
   11195           0 :                     switch (nVarType)
   11196             :                     {
   11197           0 :                         case NC_STRING:
   11198             :                         {
   11199             :                             status =
   11200           0 :                                 nc_put_vara_string(nCdfId, nVarId, start, count,
   11201             :                                                    (const char **)papszValues);
   11202           0 :                             NCDF_ERR(status);
   11203           0 :                             break;
   11204             :                         }
   11205           0 :                         case NC_UBYTE:
   11206             :                         {
   11207             :                             unsigned char *pucTemp =
   11208             :                                 static_cast<unsigned char *>(
   11209           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned char)));
   11210           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11211             :                             {
   11212           0 :                                 char *pszTemp = nullptr;
   11213           0 :                                 pucTemp[i] = static_cast<unsigned char>(
   11214           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11215             :                             }
   11216           0 :                             status = nc_put_vara_uchar(nCdfId, nVarId, start,
   11217             :                                                        count, pucTemp);
   11218           0 :                             NCDF_ERR(status);
   11219           0 :                             CPLFree(pucTemp);
   11220           0 :                             break;
   11221             :                         }
   11222           0 :                         case NC_USHORT:
   11223             :                         {
   11224             :                             unsigned short *pusTemp =
   11225             :                                 static_cast<unsigned short *>(
   11226           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned short)));
   11227           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11228             :                             {
   11229           0 :                                 char *pszTemp = nullptr;
   11230           0 :                                 pusTemp[i] = static_cast<unsigned short>(
   11231           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11232             :                             }
   11233           0 :                             status = nc_put_vara_ushort(nCdfId, nVarId, start,
   11234             :                                                         count, pusTemp);
   11235           0 :                             NCDF_ERR(status);
   11236           0 :                             CPLFree(pusTemp);
   11237           0 :                             break;
   11238             :                         }
   11239           0 :                         case NC_UINT:
   11240             :                         {
   11241             :                             unsigned int *punTemp = static_cast<unsigned int *>(
   11242           0 :                                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11243           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11244             :                             {
   11245           0 :                                 char *pszTemp = nullptr;
   11246           0 :                                 punTemp[i] = static_cast<unsigned int>(
   11247           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11248             :                             }
   11249           0 :                             status = nc_put_vara_uint(nCdfId, nVarId, start,
   11250             :                                                       count, punTemp);
   11251           0 :                             NCDF_ERR(status);
   11252           0 :                             CPLFree(punTemp);
   11253           0 :                             break;
   11254             :                         }
   11255           0 :                         default:
   11256           0 :                             if (papszValues)
   11257           0 :                                 CSLDestroy(papszValues);
   11258           0 :                             return CE_Failure;
   11259             :                             break;
   11260             :                     }
   11261             :                 }
   11262           0 :                 break;
   11263             :             }
   11264             :         }
   11265             :     }
   11266             : 
   11267           8 :     if (papszValues)
   11268           8 :         CSLDestroy(papszValues);
   11269             : 
   11270           8 :     return CE_None;
   11271             : }
   11272             : 
   11273             : /************************************************************************/
   11274             : /*                           GetDefaultNoDataValue()                    */
   11275             : /************************************************************************/
   11276             : 
   11277         187 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
   11278             :                                  bool &bGotNoData)
   11279             : 
   11280             : {
   11281         187 :     int nNoFill = 0;
   11282         187 :     double dfNoData = 0.0;
   11283             : 
   11284         187 :     switch (nVarType)
   11285             :     {
   11286           0 :         case NC_CHAR:
   11287             :         case NC_BYTE:
   11288             :         case NC_UBYTE:
   11289             :             // Don't do default fill-values for bytes, too risky.
   11290             :             // This function should not be called in those cases.
   11291           0 :             CPLAssert(false);
   11292             :             break;
   11293          24 :         case NC_SHORT:
   11294             :         {
   11295          24 :             short nFillVal = 0;
   11296          24 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11297             :                 NC_NOERR)
   11298             :             {
   11299          24 :                 if (!nNoFill)
   11300             :                 {
   11301          23 :                     bGotNoData = true;
   11302          23 :                     dfNoData = nFillVal;
   11303             :                 }
   11304             :             }
   11305             :             else
   11306           0 :                 dfNoData = NC_FILL_SHORT;
   11307          24 :             break;
   11308             :         }
   11309          26 :         case NC_INT:
   11310             :         {
   11311          26 :             int nFillVal = 0;
   11312          26 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11313             :                 NC_NOERR)
   11314             :             {
   11315          26 :                 if (!nNoFill)
   11316             :                 {
   11317          25 :                     bGotNoData = true;
   11318          25 :                     dfNoData = nFillVal;
   11319             :                 }
   11320             :             }
   11321             :             else
   11322           0 :                 dfNoData = NC_FILL_INT;
   11323          26 :             break;
   11324             :         }
   11325          70 :         case NC_FLOAT:
   11326             :         {
   11327          70 :             float fFillVal = 0;
   11328          70 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
   11329             :                 NC_NOERR)
   11330             :             {
   11331          70 :                 if (!nNoFill)
   11332             :                 {
   11333          66 :                     bGotNoData = true;
   11334          66 :                     dfNoData = fFillVal;
   11335             :                 }
   11336             :             }
   11337             :             else
   11338           0 :                 dfNoData = NC_FILL_FLOAT;
   11339          70 :             break;
   11340             :         }
   11341          34 :         case NC_DOUBLE:
   11342             :         {
   11343          34 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
   11344             :                 NC_NOERR)
   11345             :             {
   11346          34 :                 if (!nNoFill)
   11347             :                 {
   11348          34 :                     bGotNoData = true;
   11349             :                 }
   11350             :             }
   11351             :             else
   11352           0 :                 dfNoData = NC_FILL_DOUBLE;
   11353          34 :             break;
   11354             :         }
   11355           7 :         case NC_USHORT:
   11356             :         {
   11357           7 :             unsigned short nFillVal = 0;
   11358           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11359             :                 NC_NOERR)
   11360             :             {
   11361           7 :                 if (!nNoFill)
   11362             :                 {
   11363           7 :                     bGotNoData = true;
   11364           7 :                     dfNoData = nFillVal;
   11365             :                 }
   11366             :             }
   11367             :             else
   11368           0 :                 dfNoData = NC_FILL_USHORT;
   11369           7 :             break;
   11370             :         }
   11371           7 :         case NC_UINT:
   11372             :         {
   11373           7 :             unsigned int nFillVal = 0;
   11374           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11375             :                 NC_NOERR)
   11376             :             {
   11377           7 :                 if (!nNoFill)
   11378             :                 {
   11379           7 :                     bGotNoData = true;
   11380           7 :                     dfNoData = nFillVal;
   11381             :                 }
   11382             :             }
   11383             :             else
   11384           0 :                 dfNoData = NC_FILL_UINT;
   11385           7 :             break;
   11386             :         }
   11387          19 :         default:
   11388          19 :             dfNoData = 0.0;
   11389          19 :             break;
   11390             :     }
   11391             : 
   11392         187 :     return dfNoData;
   11393             : }
   11394             : 
   11395             : /************************************************************************/
   11396             : /*                      NCDFGetDefaultNoDataValueAsInt64()              */
   11397             : /************************************************************************/
   11398             : 
   11399           2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
   11400             :                                          bool &bGotNoData)
   11401             : 
   11402             : {
   11403           2 :     int nNoFill = 0;
   11404           2 :     long long nFillVal = 0;
   11405           2 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11406             :     {
   11407           2 :         if (!nNoFill)
   11408             :         {
   11409           2 :             bGotNoData = true;
   11410           2 :             return static_cast<int64_t>(nFillVal);
   11411             :         }
   11412             :     }
   11413             :     else
   11414           0 :         return static_cast<int64_t>(NC_FILL_INT64);
   11415           0 :     return 0;
   11416             : }
   11417             : 
   11418             : /************************************************************************/
   11419             : /*                     NCDFGetDefaultNoDataValueAsUInt64()              */
   11420             : /************************************************************************/
   11421             : 
   11422           1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
   11423             :                                            bool &bGotNoData)
   11424             : 
   11425             : {
   11426           1 :     int nNoFill = 0;
   11427           1 :     unsigned long long nFillVal = 0;
   11428           1 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11429             :     {
   11430           1 :         if (!nNoFill)
   11431             :         {
   11432           1 :             bGotNoData = true;
   11433           1 :             return static_cast<uint64_t>(nFillVal);
   11434             :         }
   11435             :     }
   11436             :     else
   11437           0 :         return static_cast<uint64_t>(NC_FILL_UINT64);
   11438           0 :     return 0;
   11439             : }
   11440             : 
   11441       10784 : static int NCDFDoesVarContainAttribVal(int nCdfId,
   11442             :                                        const char *const *papszAttribNames,
   11443             :                                        const char *const *papszAttribValues,
   11444             :                                        int nVarId, const char *pszVarName,
   11445             :                                        bool bStrict = true)
   11446             : {
   11447       10784 :     if (nVarId == -1 && pszVarName != nullptr)
   11448        7894 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11449             : 
   11450       10784 :     if (nVarId == -1)
   11451         878 :         return -1;
   11452             : 
   11453        9906 :     bool bFound = false;
   11454       46332 :     for (int i = 0; !bFound && papszAttribNames != nullptr &&
   11455       44192 :                     papszAttribNames[i] != nullptr;
   11456             :          i++)
   11457             :     {
   11458       36426 :         char *pszTemp = nullptr;
   11459       36426 :         if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
   11460       52080 :                 CE_None &&
   11461       15654 :             pszTemp != nullptr)
   11462             :         {
   11463       15654 :             if (bStrict)
   11464             :             {
   11465       15654 :                 if (EQUAL(pszTemp, papszAttribValues[i]))
   11466        2140 :                     bFound = true;
   11467             :             }
   11468             :             else
   11469             :             {
   11470           0 :                 if (EQUALN(pszTemp, papszAttribValues[i],
   11471             :                            strlen(papszAttribValues[i])))
   11472           0 :                     bFound = true;
   11473             :             }
   11474       15654 :             CPLFree(pszTemp);
   11475             :         }
   11476             :     }
   11477        9906 :     return bFound;
   11478             : }
   11479             : 
   11480        1933 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
   11481             :                                         const char *const *papszAttribValues,
   11482             :                                         int nVarId, const char *pszVarName,
   11483             :                                         int bStrict = true)
   11484             : {
   11485        1933 :     if (nVarId == -1 && pszVarName != nullptr)
   11486        1550 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11487             : 
   11488        1933 :     if (nVarId == -1)
   11489           0 :         return -1;
   11490             : 
   11491        1933 :     bool bFound = false;
   11492        1933 :     char *pszTemp = nullptr;
   11493        2294 :     if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
   11494         361 :         pszTemp == nullptr)
   11495        1572 :         return FALSE;
   11496             : 
   11497        7323 :     for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
   11498             :     {
   11499        6962 :         if (bStrict)
   11500             :         {
   11501        6934 :             if (EQUAL(pszTemp, papszAttribValues[i]))
   11502          30 :                 bFound = true;
   11503             :         }
   11504             :         else
   11505             :         {
   11506          28 :             if (EQUALN(pszTemp, papszAttribValues[i],
   11507             :                        strlen(papszAttribValues[i])))
   11508           0 :                 bFound = true;
   11509             :         }
   11510             :     }
   11511             : 
   11512         361 :     CPLFree(pszTemp);
   11513             : 
   11514         361 :     return bFound;
   11515             : }
   11516             : 
   11517         876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
   11518             : {
   11519         876 :     if (papszName == nullptr || EQUAL(papszName, ""))
   11520           0 :         return false;
   11521             : 
   11522        2392 :     for (int i = 0; papszValues && papszValues[i]; ++i)
   11523             :     {
   11524        1636 :         if (EQUAL(papszName, papszValues[i]))
   11525         120 :             return true;
   11526             :     }
   11527             : 
   11528         756 :     return false;
   11529             : }
   11530             : 
   11531             : // Test that a variable is longitude/latitude coordinate,
   11532             : // following CF 4.1 and 4.2.
   11533        3638 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
   11534             : {
   11535             :     // Check for matching attributes.
   11536        3638 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
   11537             :                                            papszCFLongitudeAttribValues, nVarId,
   11538             :                                            pszVarName);
   11539             :     // If not found using attributes then check using var name
   11540             :     // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
   11541        3638 :     if (bVal == -1)
   11542             :     {
   11543         280 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11544             :                    "STRICT"))
   11545         280 :             bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
   11546             :         else
   11547           0 :             bVal = FALSE;
   11548             :     }
   11549        3358 :     else if (bVal)
   11550             :     {
   11551             :         // Check that the units is not 'm' or '1'. See #6759
   11552         737 :         char *pszTemp = nullptr;
   11553        1096 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11554         359 :             pszTemp != nullptr)
   11555             :         {
   11556         359 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11557          97 :                 bVal = false;
   11558         359 :             CPLFree(pszTemp);
   11559             :         }
   11560             :     }
   11561             : 
   11562        3638 :     return CPL_TO_BOOL(bVal);
   11563             : }
   11564             : 
   11565        2095 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
   11566             : {
   11567        2095 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
   11568             :                                            papszCFLatitudeAttribValues, nVarId,
   11569             :                                            pszVarName);
   11570        2095 :     if (bVal == -1)
   11571             :     {
   11572         163 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11573             :                    "STRICT"))
   11574         163 :             bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
   11575             :         else
   11576           0 :             bVal = FALSE;
   11577             :     }
   11578        1932 :     else if (bVal)
   11579             :     {
   11580             :         // Check that the units is not 'm' or '1'. See #6759
   11581         505 :         char *pszTemp = nullptr;
   11582         640 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11583         135 :             pszTemp != nullptr)
   11584             :         {
   11585         135 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11586          36 :                 bVal = false;
   11587         135 :             CPLFree(pszTemp);
   11588             :         }
   11589             :     }
   11590             : 
   11591        2095 :     return CPL_TO_BOOL(bVal);
   11592             : }
   11593             : 
   11594        2249 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
   11595             : {
   11596        2249 :     int bVal = NCDFDoesVarContainAttribVal(
   11597             :         nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
   11598             :         nVarId, pszVarName);
   11599        2249 :     if (bVal == -1)
   11600             :     {
   11601         274 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11602             :                    "STRICT"))
   11603         274 :             bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
   11604             :         else
   11605           0 :             bVal = FALSE;
   11606             :     }
   11607        1975 :     else if (bVal)
   11608             :     {
   11609             :         // Check that the units is not '1'
   11610         364 :         char *pszTemp = nullptr;
   11611         518 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11612         154 :             pszTemp != nullptr)
   11613             :         {
   11614         154 :             if (EQUAL(pszTemp, "1"))
   11615           5 :                 bVal = false;
   11616         154 :             CPLFree(pszTemp);
   11617             :         }
   11618             :     }
   11619             : 
   11620        2249 :     return CPL_TO_BOOL(bVal);
   11621             : }
   11622             : 
   11623        1601 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
   11624             : {
   11625        1601 :     int bVal = NCDFDoesVarContainAttribVal(
   11626             :         nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
   11627             :         nVarId, pszVarName);
   11628        1601 :     if (bVal == -1)
   11629             :     {
   11630         159 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11631             :                    "STRICT"))
   11632         159 :             bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
   11633             :         else
   11634           0 :             bVal = FALSE;
   11635             :     }
   11636        1442 :     else if (bVal)
   11637             :     {
   11638             :         // Check that the units is not '1'
   11639         363 :         char *pszTemp = nullptr;
   11640         516 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11641         153 :             pszTemp != nullptr)
   11642             :         {
   11643         153 :             if (EQUAL(pszTemp, "1"))
   11644           5 :                 bVal = false;
   11645         153 :             CPLFree(pszTemp);
   11646             :         }
   11647             :     }
   11648             : 
   11649        1601 :     return CPL_TO_BOOL(bVal);
   11650             : }
   11651             : 
   11652             : /* test that a variable is a vertical coordinate, following CF 4.3 */
   11653        1007 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
   11654             : {
   11655             :     /* check for matching attributes */
   11656        1007 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
   11657             :                                     papszCFVerticalAttribValues, nVarId,
   11658        1007 :                                     pszVarName))
   11659          72 :         return true;
   11660             :     /* check for matching units */
   11661         935 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11662             :                                           papszCFVerticalUnitsValues, nVarId,
   11663         935 :                                           pszVarName))
   11664          30 :         return true;
   11665             :     /* check for matching standard name */
   11666         905 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
   11667             :                                           papszCFVerticalStandardNameValues,
   11668         905 :                                           nVarId, pszVarName))
   11669           0 :         return true;
   11670             :     else
   11671         905 :         return false;
   11672             : }
   11673             : 
   11674             : /* test that a variable is a time coordinate, following CF 4.4 */
   11675         194 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
   11676             : {
   11677             :     /* check for matching attributes */
   11678         194 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
   11679             :                                     papszCFTimeAttribValues, nVarId,
   11680         194 :                                     pszVarName))
   11681         101 :         return true;
   11682             :     /* check for matching units */
   11683          93 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11684             :                                           papszCFTimeUnitsValues, nVarId,
   11685          93 :                                           pszVarName, false))
   11686           0 :         return true;
   11687             :     else
   11688          93 :         return false;
   11689             : }
   11690             : 
   11691             : // Parse a string, and return as a string list.
   11692             : // If it an array of the form {a,b}, then tokenize it.
   11693             : // Otherwise, return a copy.
   11694         182 : static char **NCDFTokenizeArray(const char *pszValue)
   11695             : {
   11696         182 :     if (pszValue == nullptr || EQUAL(pszValue, ""))
   11697          50 :         return nullptr;
   11698             : 
   11699         132 :     char **papszValues = nullptr;
   11700         132 :     const int nLen = static_cast<int>(strlen(pszValue));
   11701             : 
   11702         132 :     if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
   11703             :     {
   11704          41 :         char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
   11705          41 :         strncpy(pszTemp, pszValue + 1, nLen - 2);
   11706          41 :         pszTemp[nLen - 2] = '\0';
   11707          41 :         papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
   11708          41 :         CPLFree(pszTemp);
   11709             :     }
   11710             :     else
   11711             :     {
   11712          91 :         papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
   11713          91 :         papszValues[0] = CPLStrdup(pszValue);
   11714          91 :         papszValues[1] = nullptr;
   11715             :     }
   11716             : 
   11717         132 :     return papszValues;
   11718             : }
   11719             : 
   11720             : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
   11721             : // Leading slash is optional.
   11722         399 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
   11723             :                                  int *pnGroupId, int *pnVarId)
   11724             : {
   11725         399 :     *pnGroupId = -1;
   11726         399 :     *pnVarId = -1;
   11727             : 
   11728             :     // Open group.
   11729         399 :     char *pszGroupFullName = CPLStrdup(CPLGetPath(pszSubdatasetName));
   11730             :     // Add a leading slash if needed.
   11731         399 :     if (pszGroupFullName[0] != '/')
   11732             :     {
   11733         383 :         char *old = pszGroupFullName;
   11734         383 :         pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
   11735         383 :         CPLFree(old);
   11736             :     }
   11737             :     // Detect root group.
   11738         399 :     if (EQUAL(pszGroupFullName, "/"))
   11739             :     {
   11740         383 :         *pnGroupId = nCdfId;
   11741         383 :         CPLFree(pszGroupFullName);
   11742             :     }
   11743             :     else
   11744             :     {
   11745          16 :         int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
   11746          16 :         CPLFree(pszGroupFullName);
   11747          16 :         NCDF_ERR_RET(status);
   11748             :     }
   11749             : 
   11750             :     // Open var.
   11751         399 :     const char *pszVarName = CPLGetFilename(pszSubdatasetName);
   11752         399 :     NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
   11753             : 
   11754         399 :     return CE_None;
   11755             : }
   11756             : 
   11757             : // Get all dimensions visible from a given NetCDF (or group) ID and any of
   11758             : // its parents.
   11759         341 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
   11760             : {
   11761         341 :     int nDims = 0;
   11762         341 :     int *panDimIds = nullptr;
   11763         341 :     NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
   11764             : 
   11765         341 :     panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
   11766             : 
   11767         341 :     int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
   11768         341 :     if (status != NC_NOERR)
   11769           0 :         CPLFree(panDimIds);
   11770         341 :     NCDF_ERR_RET(status);
   11771             : 
   11772         341 :     *pnDims = nDims;
   11773         341 :     *ppanDimIds = panDimIds;
   11774             : 
   11775         341 :     return CE_None;
   11776             : }
   11777             : 
   11778             : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
   11779             : // Consider only direct children, does not get children of children.
   11780        2995 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
   11781             :                                int **ppanSubGroupIds)
   11782             : {
   11783        2995 :     *pnSubGroups = 0;
   11784        2995 :     *ppanSubGroupIds = nullptr;
   11785             : 
   11786             :     int nSubGroups;
   11787        2995 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
   11788             :     int *panSubGroupIds =
   11789        2995 :         static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
   11790        2995 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
   11791        2995 :     *pnSubGroups = nSubGroups;
   11792        2995 :     *ppanSubGroupIds = panSubGroupIds;
   11793             : 
   11794        2995 :     return CE_None;
   11795             : }
   11796             : 
   11797             : // Get the full name of a given NetCDF (or group) ID
   11798             : // (e.g. /group1/group2/.../groupn).
   11799             : // bNC3Compat remove the leading slash for top-level variables for
   11800             : // backward compatibility (top-level variables are the ones in the root group).
   11801       15024 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
   11802             :                                    bool bNC3Compat)
   11803             : {
   11804       15024 :     *ppszFullName = nullptr;
   11805             : 
   11806             :     size_t nFullNameLen;
   11807       15024 :     NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
   11808       15024 :     *ppszFullName =
   11809       15024 :         static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
   11810       15024 :     int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
   11811       15024 :     if (status != NC_NOERR)
   11812             :     {
   11813           0 :         CPLFree(*ppszFullName);
   11814           0 :         *ppszFullName = nullptr;
   11815           0 :         NCDF_ERR_RET(status);
   11816             :     }
   11817             : 
   11818       15024 :     if (bNC3Compat && EQUAL(*ppszFullName, "/"))
   11819        7677 :         (*ppszFullName)[0] = '\0';
   11820             : 
   11821       15024 :     return CE_None;
   11822             : }
   11823             : 
   11824        7159 : CPLString NCDFGetGroupFullName(int nGroupId)
   11825             : {
   11826        7159 :     char *pszFullname = nullptr;
   11827        7159 :     NCDFGetGroupFullName(nGroupId, &pszFullname, false);
   11828        7159 :     CPLString osRet(pszFullname ? pszFullname : "");
   11829        7159 :     CPLFree(pszFullname);
   11830       14318 :     return osRet;
   11831             : }
   11832             : 
   11833             : // Get the full name of a given NetCDF variable ID
   11834             : // (e.g. /group1/group2/.../groupn/var).
   11835             : // Handle also NC_GLOBAL as nVarId.
   11836             : // bNC3Compat remove the leading slash for top-level variables for
   11837             : // backward compatibility (top-level variables are the ones in the root group).
   11838        7816 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
   11839             :                                  bool bNC3Compat)
   11840             : {
   11841        7816 :     *ppszFullName = nullptr;
   11842        7816 :     char *pszGroupFullName = nullptr;
   11843        7816 :     ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
   11844             :     char szVarName[NC_MAX_NAME + 1];
   11845        7816 :     if (nVarId == NC_GLOBAL)
   11846             :     {
   11847        1060 :         strcpy(szVarName, "NC_GLOBAL");
   11848             :     }
   11849             :     else
   11850             :     {
   11851        6756 :         int status = nc_inq_varname(nGroupId, nVarId, szVarName);
   11852        6756 :         if (status != NC_NOERR)
   11853             :         {
   11854           0 :             CPLFree(pszGroupFullName);
   11855           0 :             NCDF_ERR_RET(status);
   11856             :         }
   11857             :     }
   11858        7816 :     const char *pszSep = "/";
   11859        7816 :     if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
   11860        7630 :         pszSep = "";
   11861        7816 :     *ppszFullName =
   11862        7816 :         CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
   11863        7816 :     CPLFree(pszGroupFullName);
   11864        7816 :     return CE_None;
   11865             : }
   11866             : 
   11867             : // Get the NetCDF root group ID of a given group ID.
   11868           0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
   11869             : {
   11870           0 :     *pnRootGroupId = -1;
   11871             :     // Recurse on parent group.
   11872             :     int nParentGroupId;
   11873           0 :     int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
   11874           0 :     if (status == NC_NOERR)
   11875           0 :         return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
   11876           0 :     else if (status != NC_ENOGRP)
   11877           0 :         NCDF_ERR_RET(status);
   11878             :     else  // No more parent group.
   11879             :     {
   11880           0 :         *pnRootGroupId = nStartGroupId;
   11881             :     }
   11882             : 
   11883           0 :     return CE_None;
   11884             : }
   11885             : 
   11886             : // Implementation of NCDFResolveVar/Att.
   11887       12778 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
   11888             :                               const char *pszAtt, int *pnGroupId, int *pnId,
   11889             :                               bool bMandatory)
   11890             : {
   11891       12778 :     if (!pszVar && !pszAtt)
   11892             :     {
   11893           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11894             :                  "pszVar and pszAtt NCDFResolveElem() args are both null.");
   11895           0 :         return CE_Failure;
   11896             :     }
   11897             : 
   11898             :     enum
   11899             :     {
   11900             :         NCRM_PARENT,
   11901             :         NCRM_WIDTH_WISE
   11902       12778 :     } eNCResolveMode = NCRM_PARENT;
   11903             : 
   11904       25556 :     std::queue<int> aoQueueGroupIdsToVisit;
   11905       12778 :     aoQueueGroupIdsToVisit.push(nStartGroupId);
   11906             : 
   11907       14426 :     while (!aoQueueGroupIdsToVisit.empty())
   11908             :     {
   11909             :         // Get the first group of the FIFO queue.
   11910       12925 :         *pnGroupId = aoQueueGroupIdsToVisit.front();
   11911       12925 :         aoQueueGroupIdsToVisit.pop();
   11912             : 
   11913             :         // Look if this group contains the searched element.
   11914             :         int status;
   11915       12925 :         if (pszVar)
   11916       12711 :             status = nc_inq_varid(*pnGroupId, pszVar, pnId);
   11917             :         else  // pszAtt != nullptr.
   11918         214 :             status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
   11919             : 
   11920       12925 :         if (status == NC_NOERR)
   11921             :         {
   11922       11277 :             return CE_None;
   11923             :         }
   11924        1648 :         else if ((pszVar && status != NC_ENOTVAR) ||
   11925         211 :                  (pszAtt && status != NC_ENOTATT))
   11926             :         {
   11927           0 :             NCDF_ERR(status);
   11928             :         }
   11929             :         // Element not found, in NC4 case we must search in other groups
   11930             :         // following the CF logic.
   11931             : 
   11932             :         // The first resolve mode consists to search on parent groups.
   11933        1648 :         if (eNCResolveMode == NCRM_PARENT)
   11934             :         {
   11935        1548 :             int nParentGroupId = -1;
   11936        1548 :             int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
   11937        1548 :             if (status2 == NC_NOERR)
   11938          45 :                 aoQueueGroupIdsToVisit.push(nParentGroupId);
   11939        1503 :             else if (status2 != NC_ENOGRP)
   11940           0 :                 NCDF_ERR(status2);
   11941        1503 :             else if (pszVar)
   11942             :                 // When resolving a variable, if there is no more
   11943             :                 // parent group then we switch to width-wise search mode
   11944             :                 // starting from the latest found parent group.
   11945        1295 :                 eNCResolveMode = NCRM_WIDTH_WISE;
   11946             :         }
   11947             : 
   11948             :         // The second resolve mode is a width-wise search.
   11949        1648 :         if (eNCResolveMode == NCRM_WIDTH_WISE)
   11950             :         {
   11951             :             // Enqueue all direct sub-groups.
   11952        1395 :             int nSubGroups = 0;
   11953        1395 :             int *panSubGroupIds = nullptr;
   11954        1395 :             NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
   11955        1497 :             for (int i = 0; i < nSubGroups; i++)
   11956         102 :                 aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
   11957        1395 :             CPLFree(panSubGroupIds);
   11958             :         }
   11959             :     }
   11960             : 
   11961        1501 :     if (bMandatory)
   11962             :     {
   11963           0 :         char *pszStartGroupFullName = nullptr;
   11964           0 :         NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
   11965           0 :         CPLError(CE_Failure, CPLE_AppDefined,
   11966             :                  "Cannot resolve mandatory %s %s from group %s",
   11967             :                  (pszVar ? pszVar : pszAtt),
   11968             :                  (pszVar ? "variable" : "attribute"),
   11969           0 :                  (pszStartGroupFullName ? pszStartGroupFullName : ""));
   11970           0 :         CPLFree(pszStartGroupFullName);
   11971             :     }
   11972             : 
   11973        1501 :     *pnGroupId = -1;
   11974        1501 :     *pnId = -1;
   11975        1501 :     return CE_Failure;
   11976             : }
   11977             : 
   11978             : // Resolve a variable name from a given starting group following the CF logic:
   11979             : // - if var name is an absolute path then directly open it
   11980             : // - first search in the starting group and its parent groups
   11981             : // - then if there is no more parent group we switch to a width-wise search
   11982             : //   mode starting from the latest found parent group.
   11983             : // The full CF logic is described here:
   11984             : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
   11985             : // If bMandatory then print an error if resolving fails.
   11986             : // TODO: implement support of relative paths.
   11987             : // TODO: to follow strictly the CF logic, when searching for a coordinate
   11988             : //       variable, we must stop the parent search mode once the corresponding
   11989             : //       dimension is found and start the width-wise search from this group.
   11990             : // TODO: to follow strictly the CF logic, when searching in width-wise mode
   11991             : //       we should skip every groups already visited during the parent
   11992             : //       search mode (but revisiting them should have no impact so we could
   11993             : //       let as it is if it is simpler...)
   11994             : // TODO: CF specifies that the width-wise search order is "left-to-right" so
   11995             : //       maybe we must sort sibling groups alphabetically? but maybe not
   11996             : //       necessary if nc_inq_grps() already sort them?
   11997       12567 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
   11998             :                       int *pnVarId, bool bMandatory)
   11999             : {
   12000       12567 :     *pnGroupId = -1;
   12001       12567 :     *pnVarId = -1;
   12002       12567 :     int nGroupId = nStartGroupId, nVarId;
   12003       12567 :     if (pszVar[0] == '/')
   12004             :     {
   12005             :         // This is an absolute path: we can open the var directly.
   12006             :         int nRootGroupId;
   12007           0 :         ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
   12008           0 :         ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
   12009             :     }
   12010             :     else
   12011             :     {
   12012             :         // We have to search the variable following the CF logic.
   12013       12567 :         ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
   12014             :                                 &nVarId, bMandatory));
   12015             :     }
   12016       11274 :     *pnGroupId = nGroupId;
   12017       11274 :     *pnVarId = nVarId;
   12018       11274 :     return CE_None;
   12019             : }
   12020             : 
   12021             : // Like NCDFResolveVar but returns directly the var full name.
   12022        1326 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
   12023             :                                      char **ppszFullName, bool bMandatory)
   12024             : {
   12025        1326 :     *ppszFullName = nullptr;
   12026             :     int nGroupId, nVarId;
   12027        1326 :     ERR_RET(
   12028             :         NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
   12029        1308 :     return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
   12030             : }
   12031             : 
   12032             : // Like NCDFResolveVar but resolves an attribute instead a variable and
   12033             : // returns its integer value.
   12034             : // Only GLOBAL attributes are supported for the moment.
   12035         211 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
   12036             :                                 const char *pszAtt, int *pnAtt, bool bMandatory)
   12037             : {
   12038         211 :     int nGroupId = nStartGroupId, nAttId = nStartVarId;
   12039         211 :     ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
   12040             :                             bMandatory));
   12041           3 :     NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
   12042           3 :     return CE_None;
   12043             : }
   12044             : 
   12045             : // Filter variables to keep only valid 2+D raster bands and vector fields in
   12046             : // a given a NetCDF (or group) ID and its sub-groups.
   12047             : // Coordinate or boundary variables are ignored.
   12048             : // It also creates corresponding vector layers.
   12049         512 : CPLErr netCDFDataset::FilterVars(
   12050             :     int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
   12051             :     int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
   12052             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
   12053             :         &oMap2DDimsToGroupAndVar)
   12054             : {
   12055         512 :     int nVars = 0;
   12056         512 :     int nRasterVars = 0;
   12057         512 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12058             : 
   12059        1024 :     std::vector<int> anPotentialVectorVarID;
   12060             :     // oMapDimIdToCount[x] = number of times dim x is the first dimension of
   12061             :     // potential vector variables
   12062        1024 :     std::map<int, int> oMapDimIdToCount;
   12063         512 :     int nVarXId = -1;
   12064         512 :     int nVarYId = -1;
   12065         512 :     int nVarZId = -1;
   12066         512 :     int nVarTimeId = -1;
   12067         512 :     int nVarTimeDimId = -1;
   12068         512 :     bool bIsVectorOnly = true;
   12069         512 :     int nProfileDimId = -1;
   12070         512 :     int nParentIndexVarID = -1;
   12071             : 
   12072        3131 :     for (int v = 0; v < nVars; v++)
   12073             :     {
   12074             :         int nVarDims;
   12075        2619 :         NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
   12076             :         // Should we ignore this variable?
   12077             :         char szTemp[NC_MAX_NAME + 1];
   12078        2619 :         szTemp[0] = '\0';
   12079        2619 :         NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
   12080             : 
   12081        2619 :         if (strstr(szTemp, "_node_coordinates") ||
   12082        2619 :             strstr(szTemp, "_node_count"))
   12083             :         {
   12084             :             // Ignore CF-1.8 Simple Geometries helper variables
   12085          69 :             continue;
   12086             :         }
   12087             : 
   12088        3822 :         if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
   12089        1272 :                               NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
   12090             :         {
   12091         348 :             nVarXId = v;
   12092             :         }
   12093        3126 :         else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
   12094         924 :                                    NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
   12095             :         {
   12096         348 :             nVarYId = v;
   12097             :         }
   12098        1854 :         else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
   12099             :         {
   12100          77 :             nVarZId = v;
   12101             :         }
   12102             :         else
   12103             :         {
   12104        1777 :             char *pszVarFullName = nullptr;
   12105        1777 :             CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
   12106        1777 :             if (eErr != CE_None)
   12107             :             {
   12108           0 :                 CPLFree(pszVarFullName);
   12109           0 :                 continue;
   12110             :             }
   12111             :             bool bIgnoreVar =
   12112        1777 :                 (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
   12113        1777 :             CPLFree(pszVarFullName);
   12114        1777 :             if (bIgnoreVar)
   12115             :             {
   12116          98 :                 if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
   12117             :                 {
   12118           7 :                     nVarTimeId = v;
   12119           7 :                     nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
   12120             :                 }
   12121          91 :                 else if (nVarDims > 1)
   12122             :                 {
   12123          87 :                     (*pnIgnoredVars)++;
   12124          87 :                     CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
   12125             :                              szTemp);
   12126             :                 }
   12127             :             }
   12128             :             // Only accept 2+D vars.
   12129        1679 :             else if (nVarDims >= 2)
   12130             :             {
   12131         667 :                 bool bRasterCandidate = true;
   12132             :                 // Identify variables that might be vector variables
   12133         667 :                 if (nVarDims == 2)
   12134             :                 {
   12135         601 :                     int anDimIds[2] = {-1, -1};
   12136         601 :                     nc_inq_vardimid(nCdfId, v, anDimIds);
   12137             : 
   12138         601 :                     nc_type vartype = NC_NAT;
   12139         601 :                     nc_inq_vartype(nCdfId, v, &vartype);
   12140             : 
   12141             :                     char szDimNameFirst[NC_MAX_NAME + 1];
   12142             :                     char szDimNameSecond[NC_MAX_NAME + 1];
   12143         601 :                     szDimNameFirst[0] = '\0';
   12144         601 :                     szDimNameSecond[0] = '\0';
   12145        1358 :                     if (vartype == NC_CHAR &&
   12146         156 :                         nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
   12147         156 :                             NC_NOERR &&
   12148         156 :                         nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
   12149         156 :                             NC_NOERR &&
   12150         156 :                         !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
   12151         156 :                         !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
   12152         913 :                         !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
   12153         156 :                         !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
   12154             :                     {
   12155         156 :                         anPotentialVectorVarID.push_back(v);
   12156         156 :                         oMapDimIdToCount[anDimIds[0]]++;
   12157         156 :                         if (strstr(szDimNameSecond, "_max_width"))
   12158             :                         {
   12159         127 :                             bRasterCandidate = false;
   12160             :                         }
   12161             :                         else
   12162             :                         {
   12163          29 :                             std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12164          29 :                                                     vartype};
   12165          29 :                             oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12166          29 :                                 std::pair(nCdfId, v));
   12167             :                         }
   12168             :                     }
   12169             :                     else
   12170             :                     {
   12171         445 :                         std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12172         445 :                                                 vartype};
   12173         445 :                         oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12174         445 :                             std::pair(nCdfId, v));
   12175         445 :                         bIsVectorOnly = false;
   12176             :                     }
   12177             :                 }
   12178             :                 else
   12179             :                 {
   12180          66 :                     bIsVectorOnly = false;
   12181             :                 }
   12182         667 :                 if (bKeepRasters && bRasterCandidate)
   12183             :                 {
   12184         511 :                     *pnGroupId = nCdfId;
   12185         511 :                     *pnVarId = v;
   12186         511 :                     nRasterVars++;
   12187             :                 }
   12188             :             }
   12189        1012 :             else if (nVarDims == 1)
   12190             :             {
   12191         724 :                 nc_type atttype = NC_NAT;
   12192         724 :                 size_t attlen = 0;
   12193         724 :                 if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
   12194          14 :                                &attlen) == NC_NOERR &&
   12195         724 :                     atttype == NC_CHAR && attlen < NC_MAX_NAME)
   12196             :                 {
   12197             :                     char szInstanceDimension[NC_MAX_NAME + 1];
   12198          14 :                     if (nc_get_att_text(nCdfId, v, "instance_dimension",
   12199          14 :                                         szInstanceDimension) == NC_NOERR)
   12200             :                     {
   12201          14 :                         szInstanceDimension[attlen] = 0;
   12202          14 :                         int status = nc_inq_dimid(nCdfId, szInstanceDimension,
   12203             :                                                   &nProfileDimId);
   12204          14 :                         if (status == NC_NOERR)
   12205          14 :                             nParentIndexVarID = v;
   12206             :                         else
   12207           0 :                             nProfileDimId = -1;
   12208          14 :                         if (status == NC_EBADDIM)
   12209           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
   12210             :                                      "Attribute instance_dimension='%s' refers "
   12211             :                                      "to a non existing dimension",
   12212             :                                      szInstanceDimension);
   12213             :                         else
   12214          14 :                             NCDF_ERR(status);
   12215             :                     }
   12216             :                 }
   12217         724 :                 if (v != nParentIndexVarID)
   12218             :                 {
   12219         710 :                     anPotentialVectorVarID.push_back(v);
   12220         710 :                     int nDimId = -1;
   12221         710 :                     nc_inq_vardimid(nCdfId, v, &nDimId);
   12222         710 :                     oMapDimIdToCount[nDimId]++;
   12223             :                 }
   12224             :             }
   12225             :         }
   12226             :     }
   12227             : 
   12228             :     // If we are opened in raster-only mode and that there are only 1D or 2D
   12229             :     // variables and that the 2D variables have no X/Y dim, and all
   12230             :     // variables refer to the same main dimension (or 2 dimensions for
   12231             :     // featureType=profile), then it is a pure vector dataset
   12232             :     CPLString osFeatureType(
   12233         512 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
   12234         401 :     if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
   12235         913 :         !anPotentialVectorVarID.empty() &&
   12236           0 :         (oMapDimIdToCount.size() == 1 ||
   12237           0 :          (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
   12238           0 :           nProfileDimId >= 0)))
   12239             :     {
   12240           0 :         anPotentialVectorVarID.resize(0);
   12241             :     }
   12242             :     else
   12243             :     {
   12244         512 :         *pnRasterVars += nRasterVars;
   12245             :     }
   12246             : 
   12247         512 :     if (!anPotentialVectorVarID.empty() && bKeepVectors)
   12248             :     {
   12249             :         // Take the dimension that is referenced the most times.
   12250          64 :         if (!(oMapDimIdToCount.size() == 1 ||
   12251          27 :               (EQUAL(osFeatureType, "profile") &&
   12252          26 :                oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
   12253             :         {
   12254           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12255             :                      "The dataset has several variables that could be "
   12256             :                      "identified as vector fields, but not all share the same "
   12257             :                      "primary dimension. Consequently they will be ignored.");
   12258             :         }
   12259             :         else
   12260             :         {
   12261          50 :             if (nVarTimeId >= 0 &&
   12262          50 :                 oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
   12263             :             {
   12264           1 :                 anPotentialVectorVarID.push_back(nVarTimeId);
   12265             :             }
   12266          49 :             CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
   12267             :                                   oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
   12268             :                                   nProfileDimId, nParentIndexVarID,
   12269             :                                   bKeepRasters);
   12270             :         }
   12271             :     }
   12272             : 
   12273             :     // Recurse on sub-groups.
   12274         512 :     int nSubGroups = 0;
   12275         512 :     int *panSubGroupIds = nullptr;
   12276         512 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12277         544 :     for (int i = 0; i < nSubGroups; i++)
   12278             :     {
   12279          32 :         FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
   12280             :                    papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
   12281             :                    pnIgnoredVars, oMap2DDimsToGroupAndVar);
   12282             :     }
   12283         512 :     CPLFree(panSubGroupIds);
   12284             : 
   12285         512 :     return CE_None;
   12286             : }
   12287             : 
   12288             : // Create vector layers from given potentially identified vector variables
   12289             : // resulting from the scanning of a NetCDF (or group) ID.
   12290          49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
   12291             :     int nCdfId, const CPLString &osFeatureType,
   12292             :     const std::vector<int> &anPotentialVectorVarID,
   12293             :     const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
   12294             :     int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
   12295             : {
   12296          49 :     char *pszGroupName = nullptr;
   12297          49 :     NCDFGetGroupFullName(nCdfId, &pszGroupName);
   12298          49 :     if (pszGroupName == nullptr || pszGroupName[0] == '\0')
   12299             :     {
   12300          47 :         CPLFree(pszGroupName);
   12301          47 :         pszGroupName = CPLStrdup(CPLGetBasename(osFilename));
   12302             :     }
   12303          49 :     OGRwkbGeometryType eGType = wkbUnknown;
   12304             :     CPLString osLayerName = CSLFetchNameValueDef(
   12305          98 :         papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
   12306          49 :     CPLFree(pszGroupName);
   12307          49 :     papszMetadata =
   12308          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
   12309             : 
   12310          49 :     if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
   12311             :     {
   12312          33 :         papszMetadata =
   12313          33 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
   12314          33 :         eGType = wkbPoint;
   12315             :     }
   12316             : 
   12317             :     const char *pszLayerType =
   12318          49 :         CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
   12319          49 :     if (pszLayerType != nullptr)
   12320             :     {
   12321           9 :         eGType = OGRFromOGCGeomType(pszLayerType);
   12322           9 :         papszMetadata =
   12323           9 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
   12324             :     }
   12325             : 
   12326             :     CPLString osGeometryField =
   12327          98 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
   12328          49 :     papszMetadata =
   12329          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
   12330             : 
   12331          49 :     int nFirstVarId = -1;
   12332          49 :     int nVectorDim = oMapDimIdToCount.rbegin()->first;
   12333          49 :     if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
   12334             :     {
   12335          13 :         if (nVectorDim == nProfileDimId)
   12336           0 :             nVectorDim = oMapDimIdToCount.begin()->first;
   12337             :     }
   12338             :     else
   12339             :     {
   12340          36 :         nProfileDimId = -1;
   12341             :     }
   12342          62 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12343             :     {
   12344          62 :         int anDimIds[2] = {-1, -1};
   12345          62 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12346          62 :         if (nVectorDim == anDimIds[0])
   12347             :         {
   12348          49 :             nFirstVarId = anPotentialVectorVarID[j];
   12349          49 :             break;
   12350             :         }
   12351             :     }
   12352             : 
   12353             :     // In case where coordinates are explicitly specified for one of the
   12354             :     // field/variable, use them in priority over the ones that might have been
   12355             :     // identified above.
   12356          49 :     char *pszCoordinates = nullptr;
   12357          49 :     if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
   12358             :         CE_None)
   12359             :     {
   12360          34 :         char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
   12361          34 :         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
   12362             :              i++)
   12363             :         {
   12364           0 :             if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
   12365           0 :                 NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
   12366             :             {
   12367           0 :                 nVarXId = -1;
   12368           0 :                 CPL_IGNORE_RET_VAL(
   12369           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
   12370             :             }
   12371           0 :             else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
   12372           0 :                      NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
   12373             :             {
   12374           0 :                 nVarYId = -1;
   12375           0 :                 CPL_IGNORE_RET_VAL(
   12376           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
   12377             :             }
   12378           0 :             else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
   12379             :             {
   12380           0 :                 nVarZId = -1;
   12381           0 :                 CPL_IGNORE_RET_VAL(
   12382           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
   12383             :             }
   12384             :         }
   12385          34 :         CSLDestroy(papszTokens);
   12386             :     }
   12387          49 :     CPLFree(pszCoordinates);
   12388             : 
   12389             :     // Check that the X,Y,Z vars share 1D and share the same dimension as
   12390             :     // attribute variables.
   12391          49 :     if (nVarXId >= 0 && nVarYId >= 0)
   12392             :     {
   12393          38 :         int nVarDimCount = -1;
   12394          38 :         int nVarDimId = -1;
   12395          38 :         if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
   12396          38 :             nVarDimCount != 1 ||
   12397          38 :             nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
   12398          38 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
   12399          35 :             nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
   12400          35 :             nVarDimCount != 1 ||
   12401         111 :             nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
   12402          35 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
   12403             :         {
   12404           3 :             nVarXId = nVarYId = -1;
   12405             :         }
   12406          69 :         else if (nVarZId >= 0 &&
   12407          34 :                  (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
   12408          34 :                   nVarDimCount != 1 ||
   12409          34 :                   nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
   12410          34 :                   nVarDimId != nVectorDim))
   12411             :         {
   12412           0 :             nVarZId = -1;
   12413             :         }
   12414             :     }
   12415             : 
   12416          49 :     if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
   12417             :     {
   12418           2 :         eGType = wkbPoint;
   12419             :     }
   12420          49 :     if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
   12421             :     {
   12422          34 :         eGType = wkbPoint25D;
   12423             :     }
   12424          49 :     if (eGType == wkbUnknown && osGeometryField.empty())
   12425             :     {
   12426           5 :         eGType = wkbNone;
   12427             :     }
   12428             : 
   12429             :     // Read projection info
   12430          49 :     char **papszMetadataBackup = CSLDuplicate(papszMetadata);
   12431          49 :     ReadAttributes(nCdfId, nFirstVarId);
   12432          49 :     if (!this->bSGSupport)
   12433          49 :         SetProjectionFromVar(nCdfId, nFirstVarId, true);
   12434          49 :     const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
   12435          49 :     char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
   12436          49 :     CSLDestroy(papszMetadata);
   12437          49 :     papszMetadata = papszMetadataBackup;
   12438             : 
   12439          49 :     OGRSpatialReference *poSRS = nullptr;
   12440          49 :     if (!m_oSRS.IsEmpty())
   12441             :     {
   12442          21 :         poSRS = m_oSRS.Clone();
   12443             :     }
   12444             :     // Reset if there's a 2D raster
   12445          49 :     m_bHasProjection = false;
   12446          49 :     m_bHasGeoTransform = false;
   12447             : 
   12448          49 :     if (!bKeepRasters)
   12449             :     {
   12450             :         // Strip out uninteresting metadata.
   12451          45 :         papszMetadata =
   12452          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
   12453          45 :         papszMetadata =
   12454          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
   12455          45 :         papszMetadata =
   12456          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
   12457             :     }
   12458             : 
   12459             :     std::shared_ptr<netCDFLayer> poLayer(
   12460          49 :         new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
   12461          49 :     if (poSRS != nullptr)
   12462          21 :         poSRS->Release();
   12463          49 :     poLayer->SetRecordDimID(nVectorDim);
   12464          49 :     if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
   12465             :     {
   12466          35 :         poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
   12467             :     }
   12468          14 :     else if (!osGeometryField.empty())
   12469             :     {
   12470           9 :         poLayer->SetWKTGeometryField(osGeometryField);
   12471             :     }
   12472          49 :     if (pszGridMapping != nullptr)
   12473             :     {
   12474          21 :         poLayer->SetGridMapping(pszGridMapping);
   12475          21 :         CPLFree(pszGridMapping);
   12476             :     }
   12477          49 :     poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
   12478             : 
   12479         574 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12480             :     {
   12481         525 :         int anDimIds[2] = {-1, -1};
   12482         525 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12483         525 :         if (anDimIds[0] == nVectorDim ||
   12484          24 :             (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
   12485             :         {
   12486             : #ifdef NCDF_DEBUG
   12487             :             char szTemp2[NC_MAX_NAME + 1] = {};
   12488             :             CPL_IGNORE_RET_VAL(
   12489             :                 nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
   12490             :             CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
   12491             : #endif
   12492         525 :             poLayer->AddField(anPotentialVectorVarID[j]);
   12493             :         }
   12494             :     }
   12495             : 
   12496          49 :     if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
   12497           0 :         poLayer->GetGeomType() != wkbNone)
   12498             :     {
   12499          49 :         papoLayers.push_back(poLayer);
   12500             :     }
   12501             : 
   12502          98 :     return CE_None;
   12503             : }
   12504             : 
   12505             : // Get all coordinate and boundary variables full names referenced in
   12506             : // a given a NetCDF (or group) ID and its sub-groups.
   12507             : // These variables are identified in other variable's
   12508             : // "coordinates" and "bounds" attribute.
   12509             : // Searching coordinate and boundary variables may need to explore
   12510             : // parents groups (or other groups in case of reference given in form of an
   12511             : // absolute path).
   12512             : // See CF sections 5.2, 5.6 and 7.1
   12513         513 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
   12514             : {
   12515         513 :     int nVars = 0;
   12516         513 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12517             : 
   12518        3146 :     for (int v = 0; v < nVars; v++)
   12519             :     {
   12520        2633 :         char *pszTemp = nullptr;
   12521        2633 :         char **papszTokens = nullptr;
   12522        2633 :         if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
   12523         441 :             papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
   12524        2633 :         CPLFree(pszTemp);
   12525        2633 :         pszTemp = nullptr;
   12526        2633 :         if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
   12527        2633 :             pszTemp != nullptr && !EQUAL(pszTemp, ""))
   12528           9 :             papszTokens = CSLAddString(papszTokens, pszTemp);
   12529        2633 :         CPLFree(pszTemp);
   12530        3899 :         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
   12531             :              i++)
   12532             :         {
   12533        1266 :             char *pszVarFullName = nullptr;
   12534        1266 :             if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
   12535        1266 :                                        &pszVarFullName) == CE_None)
   12536        1248 :                 *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
   12537        1266 :             CPLFree(pszVarFullName);
   12538             :         }
   12539        2633 :         CSLDestroy(papszTokens);
   12540             :     }
   12541             : 
   12542             :     // Recurse on sub-groups.
   12543             :     int nSubGroups;
   12544         513 :     int *panSubGroupIds = nullptr;
   12545         513 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12546         545 :     for (int i = 0; i < nSubGroups; i++)
   12547             :     {
   12548          32 :         NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
   12549             :     }
   12550         513 :     CPLFree(panSubGroupIds);
   12551             : 
   12552         513 :     return CE_None;
   12553             : }
   12554             : 
   12555             : // Check if give type is user defined
   12556         889 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
   12557             : {
   12558         889 :     return type >= NC_FIRSTUSERTYPEID;
   12559             : }
   12560             : 
   12561         533 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
   12562             : {
   12563             :     // CF conventions use space as the separator for variable names in the
   12564             :     // coordinates attribute, but some products such as
   12565             :     // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
   12566             :     // use comma.
   12567         533 :     return CSLTokenizeString2(pszCoordinates, ", ", 0);
   12568             : }

Generated by: LCOV version 1.14