LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4974 5881 84.6 %
Date: 2025-06-19 12:30:01 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 <mutex>
      33             : #include <set>
      34             : #include <queue>
      35             : #include <string>
      36             : #include <tuple>
      37             : #include <utility>
      38             : #include <vector>
      39             : 
      40             : // Must be included after standard includes, otherwise VS2015 fails when
      41             : // including <ctime>
      42             : #include "netcdfdataset.h"
      43             : #include "netcdfdrivercore.h"
      44             : #include "netcdfsg.h"
      45             : #include "netcdfuffd.h"
      46             : 
      47             : #include "netcdf_mem.h"
      48             : 
      49             : #include "cpl_conv.h"
      50             : #include "cpl_error.h"
      51             : #include "cpl_float.h"
      52             : #include "cpl_json.h"
      53             : #include "cpl_minixml.h"
      54             : #include "cpl_multiproc.h"
      55             : #include "cpl_progress.h"
      56             : #include "cpl_time.h"
      57             : #include "gdal.h"
      58             : #include "gdal_frmts.h"
      59             : #include "gdal_priv_templates.hpp"
      60             : #include "ogr_core.h"
      61             : #include "ogr_srs_api.h"
      62             : 
      63             : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
      64             : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
      65             : // this is apparently back to expecting filenames in current codepage...
      66             : // Detect netCDF 4.8 with NC_ENCZARR
      67             : // Detect netCDF 4.9 with NC_NOATTCREORD
      68             : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
      69             : #define NETCDF_USES_UTF8
      70             : #endif
      71             : 
      72             : // Internal function declarations.
      73             : 
      74             : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
      75             : 
      76             : static void
      77             : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
      78             :                    bool bWriteGDALHistory, const char *pszOldHist,
      79             :                    const char *pszFunctionName,
      80             :                    const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
      81             : 
      82             : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
      83             :                            const char *pszOldHist);
      84             : 
      85             : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
      86             :                              size_t *nDestSize);
      87             : 
      88             : // Var / attribute helper functions.
      89             : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
      90             :                           const char *pszValue);
      91             : 
      92             : // Replace this where used.
      93             : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
      94             : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
      95             : 
      96             : // Replace this where used.
      97             : static char **NCDFTokenizeArray(const char *pszValue);
      98             : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
      99             :                          GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
     100             :                          const char *pszMatchPrefix = nullptr);
     101             : 
     102             : // NetCDF-4 groups helper functions.
     103             : // They all work also for NetCDF-3 files which are considered as
     104             : // NetCDF-4 file with only one group.
     105             : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
     106             :                                  int *pnGroupId, int *pnVarId);
     107             : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
     108             : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
     109             :                                int **ppanSubGroupIds);
     110             : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
     111             :                                    bool bNC3Compat = true);
     112             : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
     113             :                                  bool bNC3Compat = true);
     114             : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
     115             : 
     116             : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
     117             :                                      char **ppszFullName,
     118             :                                      bool bMandatory = false);
     119             : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
     120             :                                 const char *pszAtt, int *pnAtt,
     121             :                                 bool bMandatory = false);
     122             : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
     123             : 
     124             : // Uncomment this for more debug output.
     125             : // #define NCDF_DEBUG 1
     126             : 
     127             : CPLMutex *hNCMutex = nullptr;
     128             : 
     129             : // Workaround https://github.com/OSGeo/gdal/issues/6253
     130             : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
     131             : // way. Apparently having the same handle works better (this is OK since
     132             : // we have a global mutex on the netCDF library)
     133             : static std::map<std::string, int> goMapNameToNetCDFId;
     134             : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
     135             : 
     136         667 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
     137             : {
     138        1334 :     std::string osKey(pszFilename);
     139         667 :     osKey += "#####";
     140         667 :     osKey += std::to_string(nMode);
     141         667 :     auto oIter = goMapNameToNetCDFId.find(osKey);
     142         667 :     if (oIter == goMapNameToNetCDFId.end())
     143             :     {
     144         618 :         int ret = nc_open(pszFilename, nMode, pID);
     145         618 :         if (ret != NC_NOERR)
     146           3 :             return ret;
     147         615 :         goMapNameToNetCDFId[osKey] = *pID;
     148         615 :         goMapNetCDFIdToKeyAndCount[*pID] =
     149        1230 :             std::pair<std::string, int>(osKey, 1);
     150         615 :         return ret;
     151             :     }
     152             :     else
     153             :     {
     154          49 :         *pID = oIter->second;
     155          49 :         goMapNetCDFIdToKeyAndCount[oIter->second].second++;
     156          49 :         return NC_NOERR;
     157             :     }
     158             : }
     159             : 
     160         923 : int GDAL_nc_close(int cdfid)
     161             : {
     162         923 :     int ret = NC_NOERR;
     163         923 :     auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
     164         923 :     if (oIter != goMapNetCDFIdToKeyAndCount.end())
     165             :     {
     166         664 :         if (--oIter->second.second == 0)
     167             :         {
     168         615 :             ret = nc_close(cdfid);
     169         615 :             goMapNameToNetCDFId.erase(oIter->second.first);
     170         615 :             goMapNetCDFIdToKeyAndCount.erase(oIter);
     171             :         }
     172             :     }
     173             :     else
     174             :     {
     175             :         // we can go here if file opened with nc_open_mem() or nc_create()
     176         259 :         ret = nc_close(cdfid);
     177             :     }
     178         923 :     return ret;
     179             : }
     180             : 
     181             : /************************************************************************/
     182             : /* ==================================================================== */
     183             : /*                         netCDFRasterBand                             */
     184             : /* ==================================================================== */
     185             : /************************************************************************/
     186             : 
     187             : class netCDFRasterBand final : public GDALPamRasterBand
     188             : {
     189             :     friend class netCDFDataset;
     190             : 
     191             :     nc_type nc_datatype;
     192             :     int cdfid;
     193             :     int nZId;
     194             :     int nZDim;
     195             :     int nLevel;
     196             :     int nBandXPos;
     197             :     int nBandYPos;
     198             :     int *panBandZPos;
     199             :     int *panBandZLev;
     200             :     bool m_bNoDataSet = false;
     201             :     double m_dfNoDataValue = 0;
     202             :     bool m_bNoDataSetAsInt64 = false;
     203             :     int64_t m_nNodataValueInt64 = 0;
     204             :     bool m_bNoDataSetAsUInt64 = false;
     205             :     uint64_t m_nNodataValueUInt64 = 0;
     206             :     bool bValidRangeValid = false;
     207             :     double adfValidRange[2]{0, 0};
     208             :     bool m_bHaveScale = false;
     209             :     bool m_bHaveOffset = false;
     210             :     double m_dfScale = 1;
     211             :     double m_dfOffset = 0;
     212             :     CPLString m_osUnitType{};
     213             :     bool bSignedData;
     214             :     bool bCheckLongitude;
     215             :     bool m_bCreateMetadataFromOtherVarsDone = false;
     216             : 
     217             :     void CreateMetadataFromAttributes();
     218             :     void CreateMetadataFromOtherVars();
     219             : 
     220             :     template <class T>
     221             :     void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     222             :                    size_t nTmpBlockYSize, bool bCheckIsNan = false);
     223             :     template <class T>
     224             :     void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     225             :                       size_t nTmpBlockYSize, bool bCheckIsNan = false);
     226             :     void SetBlockSize();
     227             : 
     228             :     bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
     229             : 
     230             :     void SetNoDataValueNoUpdate(double dfNoData);
     231             :     void SetNoDataValueNoUpdate(int64_t nNoData);
     232             :     void SetNoDataValueNoUpdate(uint64_t nNoData);
     233             : 
     234             :     void SetOffsetNoUpdate(double dfVal);
     235             :     void SetScaleNoUpdate(double dfVal);
     236             :     void SetUnitTypeNoUpdate(const char *pszNewValue);
     237             : 
     238             :   protected:
     239             :     CPLXMLNode *SerializeToXML(const char *pszUnused) override;
     240             : 
     241             :   public:
     242             :     struct CONSTRUCTOR_OPEN
     243             :     {
     244             :     };
     245             : 
     246             :     struct CONSTRUCTOR_CREATE
     247             :     {
     248             :     };
     249             : 
     250             :     netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
     251             :                      int nGroupId, int nZId, int nZDim, int nLevel,
     252             :                      const int *panBandZLen, const int *panBandPos, int nBand);
     253             :     netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
     254             :                      GDALDataType eType, int nBand, bool bSigned = true,
     255             :                      const char *pszBandName = nullptr,
     256             :                      const char *pszLongName = nullptr, int nZId = -1,
     257             :                      int nZDim = 2, int nLevel = 0,
     258             :                      const int *panBandZLev = nullptr,
     259             :                      const int *panBandZPos = nullptr,
     260             :                      const int *paDimIds = nullptr);
     261             :     virtual ~netCDFRasterBand();
     262             : 
     263             :     virtual double GetNoDataValue(int *) override;
     264             :     virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
     265             :     virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
     266             :     virtual CPLErr SetNoDataValue(double) override;
     267             :     virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
     268             :     virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
     269             :     // virtual CPLErr DeleteNoDataValue();
     270             :     virtual double GetOffset(int *) override;
     271             :     virtual CPLErr SetOffset(double) override;
     272             :     virtual double GetScale(int *) override;
     273             :     virtual CPLErr SetScale(double) override;
     274             :     virtual const char *GetUnitType() override;
     275             :     virtual CPLErr SetUnitType(const char *) override;
     276             :     virtual CPLErr IReadBlock(int, int, void *) override;
     277             :     virtual CPLErr IWriteBlock(int, int, void *) override;
     278             : 
     279             :     char **GetMetadata(const char *pszDomain = "") override;
     280             :     const char *GetMetadataItem(const char *pszName,
     281             :                                 const char *pszDomain = "") override;
     282             : 
     283             :     virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     284             :                                    const char *pszDomain = "") override;
     285             :     virtual CPLErr SetMetadata(char **papszMD,
     286             :                                const char *pszDomain = "") override;
     287             : };
     288             : 
     289             : /************************************************************************/
     290             : /*                          netCDFRasterBand()                          */
     291             : /************************************************************************/
     292             : 
     293         472 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
     294             :                                    netCDFDataset *poNCDFDS, int nGroupId,
     295             :                                    int nZIdIn, int nZDimIn, int nLevelIn,
     296             :                                    const int *panBandZLevIn,
     297         472 :                                    const int *panBandZPosIn, int nBandIn)
     298             :     : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
     299         472 :       nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
     300         472 :       nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
     301             :       panBandZLev(nullptr),
     302             :       bSignedData(true),  // Default signed, except for Byte.
     303         944 :       bCheckLongitude(false)
     304             : {
     305         472 :     poDS = poNCDFDS;
     306         472 :     nBand = nBandIn;
     307             : 
     308             :     // Take care of all other dimensions.
     309         472 :     if (nZDim > 2)
     310             :     {
     311         168 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     312         168 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     313             : 
     314         462 :         for (int i = 0; i < nZDim - 2; i++)
     315             :         {
     316         294 :             panBandZPos[i] = panBandZPosIn[i + 2];
     317         294 :             panBandZLev[i] = panBandZLevIn[i];
     318             :         }
     319             :     }
     320             : 
     321         472 :     nRasterXSize = poDS->GetRasterXSize();
     322         472 :     nRasterYSize = poDS->GetRasterYSize();
     323         472 :     nBlockXSize = poDS->GetRasterXSize();
     324         472 :     nBlockYSize = 1;
     325             : 
     326             :     // Get the type of the "z" variable, our target raster array.
     327         472 :     if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
     328         472 :                    nullptr) != NC_NOERR)
     329             :     {
     330           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
     331           0 :         return;
     332             :     }
     333             : 
     334         472 :     if (NCDFIsUserDefinedType(cdfid, nc_datatype))
     335             :     {
     336             :         // First enquire and check that the number of fields is 2
     337             :         size_t nfields, compoundsize;
     338           5 :         if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
     339           5 :                             &nfields) != NC_NOERR)
     340             :         {
     341           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     342             :                      "Error in nc_inq_compound() on 'z'.");
     343           0 :             return;
     344             :         }
     345             : 
     346           5 :         if (nfields != 2)
     347             :         {
     348           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     349             :                      "Unsupported data type encountered in nc_inq_compound() "
     350             :                      "on 'z'.");
     351           0 :             return;
     352             :         }
     353             : 
     354             :         // Now check that that two types are the same in the struct.
     355             :         nc_type field_type1, field_type2;
     356             :         int field_dims1, field_dims2;
     357           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     358             :                                   &field_type1, &field_dims1,
     359           5 :                                   nullptr) != NC_NOERR)
     360             :         {
     361           0 :             CPLError(
     362             :                 CE_Failure, CPLE_AppDefined,
     363             :                 "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
     364           0 :             return;
     365             :         }
     366             : 
     367           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     368             :                                   &field_type2, &field_dims2,
     369           5 :                                   nullptr) != NC_NOERR)
     370             :         {
     371           0 :             CPLError(
     372             :                 CE_Failure, CPLE_AppDefined,
     373             :                 "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
     374           0 :             return;
     375             :         }
     376             : 
     377           5 :         if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
     378           5 :             (field_dims1 != 0))
     379             :         {
     380           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     381             :                      "Error in interpreting compound data type on 'z'.");
     382           0 :             return;
     383             :         }
     384             : 
     385           5 :         if (field_type1 == NC_SHORT)
     386           0 :             eDataType = GDT_CInt16;
     387           5 :         else if (field_type1 == NC_INT)
     388           0 :             eDataType = GDT_CInt32;
     389           5 :         else if (field_type1 == NC_FLOAT)
     390           4 :             eDataType = GDT_CFloat32;
     391           1 :         else if (field_type1 == NC_DOUBLE)
     392           1 :             eDataType = GDT_CFloat64;
     393             :         else
     394             :         {
     395           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     396             :                      "Unsupported netCDF compound data type encountered.");
     397           0 :             return;
     398             :         }
     399             :     }
     400             :     else
     401             :     {
     402         467 :         if (nc_datatype == NC_BYTE)
     403         145 :             eDataType = GDT_Byte;
     404         322 :         else if (nc_datatype == NC_CHAR)
     405           0 :             eDataType = GDT_Byte;
     406         322 :         else if (nc_datatype == NC_SHORT)
     407          41 :             eDataType = GDT_Int16;
     408         281 :         else if (nc_datatype == NC_INT)
     409          89 :             eDataType = GDT_Int32;
     410         192 :         else if (nc_datatype == NC_FLOAT)
     411         115 :             eDataType = GDT_Float32;
     412          77 :         else if (nc_datatype == NC_DOUBLE)
     413          40 :             eDataType = GDT_Float64;
     414          37 :         else if (nc_datatype == NC_UBYTE)
     415          15 :             eDataType = GDT_Byte;
     416          22 :         else if (nc_datatype == NC_USHORT)
     417           4 :             eDataType = GDT_UInt16;
     418          18 :         else if (nc_datatype == NC_UINT)
     419           3 :             eDataType = GDT_UInt32;
     420          15 :         else if (nc_datatype == NC_INT64)
     421           8 :             eDataType = GDT_Int64;
     422           7 :         else if (nc_datatype == NC_UINT64)
     423           7 :             eDataType = GDT_UInt64;
     424             :         else
     425             :         {
     426           0 :             if (nBand == 1)
     427           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     428             :                          "Unsupported netCDF datatype (%d), treat as Float32.",
     429           0 :                          static_cast<int>(nc_datatype));
     430           0 :             eDataType = GDT_Float32;
     431           0 :             nc_datatype = NC_FLOAT;
     432             :         }
     433             :     }
     434             : 
     435             :     // Find and set No Data for this variable.
     436         472 :     nc_type atttype = NC_NAT;
     437         472 :     size_t attlen = 0;
     438         472 :     const char *pszNoValueName = nullptr;
     439             : 
     440             :     // Find attribute name, either _FillValue or missing_value.
     441         472 :     int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
     442         472 :     if (status == NC_NOERR)
     443             :     {
     444         248 :         pszNoValueName = NCDF_FillValue;
     445             :     }
     446             :     else
     447             :     {
     448         224 :         status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
     449         224 :         if (status == NC_NOERR)
     450             :         {
     451          12 :             pszNoValueName = "missing_value";
     452             :         }
     453             :     }
     454             : 
     455             :     // Fetch missing value.
     456         472 :     double dfNoData = 0.0;
     457         472 :     bool bGotNoData = false;
     458         472 :     int64_t nNoDataAsInt64 = 0;
     459         472 :     bool bGotNoDataAsInt64 = false;
     460         472 :     uint64_t nNoDataAsUInt64 = 0;
     461         472 :     bool bGotNoDataAsUInt64 = false;
     462         472 :     if (status == NC_NOERR)
     463             :     {
     464         260 :         nc_type nAttrType = NC_NAT;
     465         260 :         size_t nAttrLen = 0;
     466         260 :         status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
     467         260 :         if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
     468             :         {
     469             :             long long v;
     470           7 :             nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
     471           7 :             bGotNoData = true;
     472           7 :             bGotNoDataAsInt64 = true;
     473           7 :             nNoDataAsInt64 = static_cast<int64_t>(v);
     474             :         }
     475         253 :         else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
     476             :         {
     477             :             unsigned long long v;
     478           7 :             nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
     479           7 :             bGotNoData = true;
     480           7 :             bGotNoDataAsUInt64 = true;
     481           7 :             nNoDataAsUInt64 = static_cast<uint64_t>(v);
     482             :         }
     483         246 :         else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
     484             :         {
     485         245 :             bGotNoData = true;
     486             :         }
     487             :     }
     488             : 
     489             :     // If NoData was not found, use the default value, but for non-Byte types
     490             :     // as it is not recommended:
     491             :     // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
     492         472 :     nc_type vartype = NC_NAT;
     493         472 :     if (!bGotNoData)
     494             :     {
     495         213 :         nc_inq_vartype(cdfid, nZId, &vartype);
     496         213 :         if (vartype == NC_INT64)
     497             :         {
     498             :             nNoDataAsInt64 =
     499           1 :                 NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
     500           1 :             bGotNoDataAsInt64 = bGotNoData;
     501             :         }
     502         212 :         else if (vartype == NC_UINT64)
     503             :         {
     504             :             nNoDataAsUInt64 =
     505           0 :                 NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
     506           0 :             bGotNoDataAsUInt64 = bGotNoData;
     507             :         }
     508         212 :         else if (vartype != NC_CHAR && vartype != NC_BYTE &&
     509          90 :                  vartype != NC_UBYTE)
     510             :         {
     511          81 :             dfNoData =
     512          81 :                 NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
     513          81 :             if (bGotNoData)
     514             :             {
     515          70 :                 CPLDebug("GDAL_netCDF",
     516             :                          "did not get nodata value for variable #%d, using "
     517             :                          "default %f",
     518             :                          nZId, dfNoData);
     519             :             }
     520             :         }
     521             :     }
     522             : 
     523         472 :     bool bHasUnderscoreUnsignedAttr = false;
     524         472 :     bool bUnderscoreUnsignedAttrVal = false;
     525             :     {
     526         472 :         char *pszTemp = nullptr;
     527         472 :         if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
     528             :         {
     529         137 :             if (EQUAL(pszTemp, "true"))
     530             :             {
     531         129 :                 bHasUnderscoreUnsignedAttr = true;
     532         129 :                 bUnderscoreUnsignedAttrVal = true;
     533             :             }
     534           8 :             else if (EQUAL(pszTemp, "false"))
     535             :             {
     536           8 :                 bHasUnderscoreUnsignedAttr = true;
     537           8 :                 bUnderscoreUnsignedAttrVal = false;
     538             :             }
     539         137 :             CPLFree(pszTemp);
     540             :         }
     541             :     }
     542             : 
     543             :     // Look for valid_range or valid_min/valid_max.
     544             : 
     545             :     // First look for valid_range.
     546         472 :     if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
     547             :     {
     548         470 :         char *pszValidRange = nullptr;
     549         470 :         if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
     550         130 :                 CE_None &&
     551         600 :             pszValidRange[0] == '{' &&
     552         130 :             pszValidRange[strlen(pszValidRange) - 1] == '}')
     553             :         {
     554             :             const std::string osValidRange =
     555         390 :                 std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
     556             :             const CPLStringList aosValidRange(
     557         260 :                 CSLTokenizeString2(osValidRange.c_str(), ",", 0));
     558         130 :             if (aosValidRange.size() == 2 &&
     559         260 :                 CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
     560         130 :                 CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
     561             :             {
     562         130 :                 bValidRangeValid = true;
     563         130 :                 adfValidRange[0] = CPLAtof(aosValidRange[0]);
     564         130 :                 adfValidRange[1] = CPLAtof(aosValidRange[1]);
     565             :             }
     566             :         }
     567         470 :         CPLFree(pszValidRange);
     568             : 
     569             :         // If not found look for valid_min and valid_max.
     570         470 :         if (!bValidRangeValid)
     571             :         {
     572         340 :             double dfMin = 0;
     573         340 :             double dfMax = 0;
     574         355 :             if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
     575          15 :                 NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
     576             :             {
     577           8 :                 adfValidRange[0] = dfMin;
     578           8 :                 adfValidRange[1] = dfMax;
     579           8 :                 bValidRangeValid = true;
     580             :             }
     581             :         }
     582             : 
     583         470 :         if (bValidRangeValid &&
     584         138 :             (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
     585          17 :             nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
     586             :             bUnderscoreUnsignedAttrVal)
     587             :         {
     588           2 :             if (adfValidRange[0] < 0)
     589           0 :                 adfValidRange[0] += 65536;
     590           2 :             if (adfValidRange[1] < 0)
     591           2 :                 adfValidRange[1] += 65536;
     592           2 :             if (adfValidRange[0] <= adfValidRange[1])
     593             :             {
     594             :                 // Updating metadata item
     595           2 :                 GDALPamRasterBand::SetMetadataItem(
     596             :                     "valid_range",
     597           2 :                     CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
     598           2 :                                static_cast<int>(adfValidRange[1])));
     599             :             }
     600             :         }
     601             : 
     602         470 :         if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
     603             :         {
     604           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     605             :                      "netCDFDataset::valid_range: min > max:\n"
     606             :                      "  min: %lf\n  max: %lf\n",
     607             :                      adfValidRange[0], adfValidRange[1]);
     608           0 :             bValidRangeValid = false;
     609           0 :             adfValidRange[0] = 0.0;
     610           0 :             adfValidRange[1] = 0.0;
     611             :         }
     612             :     }
     613             : 
     614             :     // Special For Byte Bands: check for signed/unsigned byte.
     615         472 :     if (nc_datatype == NC_BYTE)
     616             :     {
     617             :         // netcdf uses signed byte by default, but GDAL uses unsigned by default
     618             :         // This may cause unexpected results, but is needed for back-compat.
     619         145 :         if (poNCDFDS->bIsGdalFile)
     620         124 :             bSignedData = false;
     621             :         else
     622          21 :             bSignedData = true;
     623             : 
     624             :         // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
     625             :         // But in case a NC3 file was converted automatically and has hints
     626             :         // that it is unsigned, take them into account
     627         145 :         if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     628             :         {
     629           3 :             bSignedData = true;
     630             :         }
     631             : 
     632             :         // If we got valid_range, test for signed/unsigned range.
     633             :         // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
     634         145 :         if (bValidRangeValid)
     635             :         {
     636             :             // If we got valid_range={0,255}, treat as unsigned.
     637         126 :             if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
     638             :             {
     639         118 :                 bSignedData = false;
     640             :                 // Reset valid_range.
     641         118 :                 bValidRangeValid = false;
     642             :             }
     643             :             // If we got valid_range={-128,127}, treat as signed.
     644           8 :             else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
     645             :             {
     646           8 :                 bSignedData = true;
     647             :                 // Reset valid_range.
     648           8 :                 bValidRangeValid = false;
     649             :             }
     650             :         }
     651             :         // Else test for _Unsigned.
     652             :         // https://docs.unidata.ucar.edu/nug/current/best_practices.html
     653             :         else
     654             :         {
     655          19 :             if (bHasUnderscoreUnsignedAttr)
     656           7 :                 bSignedData = !bUnderscoreUnsignedAttrVal;
     657             :         }
     658             : 
     659         145 :         if (bSignedData)
     660             :         {
     661          20 :             eDataType = GDT_Int8;
     662             :         }
     663         125 :         else if (dfNoData < 0)
     664             :         {
     665             :             // Fix nodata value as it was stored signed.
     666           6 :             dfNoData += 256;
     667           6 :             if (pszNoValueName)
     668             :             {
     669             :                 // Updating metadata item
     670           6 :                 GDALPamRasterBand::SetMetadataItem(
     671             :                     pszNoValueName,
     672             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     673             :             }
     674             :         }
     675             :     }
     676         327 :     else if (nc_datatype == NC_SHORT)
     677             :     {
     678          41 :         if (bHasUnderscoreUnsignedAttr)
     679             :         {
     680           4 :             bSignedData = !bUnderscoreUnsignedAttrVal;
     681           4 :             if (!bSignedData)
     682           4 :                 eDataType = GDT_UInt16;
     683             :         }
     684             : 
     685             :         // Fix nodata value as it was stored signed.
     686          41 :         if (!bSignedData && dfNoData < 0)
     687             :         {
     688           4 :             dfNoData += 65536;
     689           4 :             if (pszNoValueName)
     690             :             {
     691             :                 // Updating metadata item
     692           4 :                 GDALPamRasterBand::SetMetadataItem(
     693             :                     pszNoValueName,
     694             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     695             :             }
     696             :         }
     697             :     }
     698             : 
     699         286 :     else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
     700         267 :              nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
     701             :     {
     702          29 :         bSignedData = false;
     703             :     }
     704             : 
     705         472 :     CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
     706         472 :              nc_datatype, eDataType, static_cast<int>(bSignedData));
     707             : 
     708         472 :     if (bGotNoData)
     709             :     {
     710             :         // Set nodata value.
     711         330 :         if (bGotNoDataAsInt64)
     712             :         {
     713           8 :             if (eDataType == GDT_Int64)
     714             :             {
     715           8 :                 SetNoDataValueNoUpdate(nNoDataAsInt64);
     716             :             }
     717           0 :             else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
     718             :             {
     719           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
     720             :             }
     721             :             else
     722             :             {
     723           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
     724             :             }
     725             :         }
     726         322 :         else if (bGotNoDataAsUInt64)
     727             :         {
     728           7 :             if (eDataType == GDT_UInt64)
     729             :             {
     730           7 :                 SetNoDataValueNoUpdate(nNoDataAsUInt64);
     731             :             }
     732           0 :             else if (eDataType == GDT_Int64 &&
     733             :                      nNoDataAsUInt64 <=
     734           0 :                          static_cast<uint64_t>(
     735           0 :                              std::numeric_limits<int64_t>::max()))
     736             :             {
     737           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
     738             :             }
     739             :             else
     740             :             {
     741           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
     742             :             }
     743             :         }
     744             :         else
     745             :         {
     746             : #ifdef NCDF_DEBUG
     747             :             CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
     748             : #endif
     749         315 :             if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
     750             :             {
     751           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
     752             :             }
     753         315 :             else if (eDataType == GDT_UInt64 &&
     754           0 :                      GDALIsValueExactAs<uint64_t>(dfNoData))
     755             :             {
     756           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
     757             :             }
     758             :             else
     759             :             {
     760         315 :                 SetNoDataValueNoUpdate(dfNoData);
     761             :             }
     762             :         }
     763             :     }
     764             : 
     765         472 :     CreateMetadataFromAttributes();
     766             : 
     767             :     // Attempt to fetch the scale_factor and add_offset attributes for the
     768             :     // variable and set them.  If these values are not available, set
     769             :     // offset to 0 and scale to 1.
     770         472 :     if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
     771             :     {
     772          16 :         double dfOffset = 0;
     773          16 :         status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
     774          16 :         CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
     775             :                  status);
     776          16 :         SetOffsetNoUpdate(dfOffset);
     777             :     }
     778             : 
     779         472 :     bool bHasScale = false;
     780         472 :     if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
     781             :     {
     782          20 :         bHasScale = true;
     783          20 :         double dfScale = 1;
     784          20 :         status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
     785          20 :         CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
     786             :                  status);
     787          20 :         SetScaleNoUpdate(dfScale);
     788             :     }
     789             : 
     790          12 :     if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
     791           4 :         eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
     792           4 :         (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
     793         484 :          std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
     794           1 :         CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
     795             :             nullptr)
     796             :     {
     797           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     798             :                  "validity range = %f, %f contains floating-point values, "
     799             :                  "whereas data type is integer. valid_range is thus likely "
     800             :                  "wrong%s. Ignoring it.",
     801             :                  adfValidRange[0], adfValidRange[1],
     802             :                  bHasScale ? " (likely scaled using scale_factor/add_factor "
     803             :                              "whereas it should be using the packed data type)"
     804             :                            : "");
     805           1 :         bValidRangeValid = false;
     806           1 :         adfValidRange[0] = 0.0;
     807           1 :         adfValidRange[1] = 0.0;
     808             :     }
     809             : 
     810             :     // Should we check for longitude values > 360?
     811         472 :     bCheckLongitude =
     812         944 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
     813         472 :         NCDFIsVarLongitude(cdfid, nZId, nullptr);
     814             : 
     815             :     // Attempt to fetch the units attribute for the variable and set it.
     816         472 :     SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
     817             : 
     818         472 :     SetBlockSize();
     819             : }
     820             : 
     821         654 : void netCDFRasterBand::SetBlockSize()
     822             : {
     823             :     // Check for variable chunking (netcdf-4 only).
     824             :     // GDAL block size should be set to hdf5 chunk size.
     825         654 :     int nTmpFormat = 0;
     826         654 :     int status = nc_inq_format(cdfid, &nTmpFormat);
     827         654 :     NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
     828         654 :     if ((status == NC_NOERR) &&
     829         561 :         (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
     830             :     {
     831         109 :         size_t chunksize[MAX_NC_DIMS] = {};
     832             :         // Check for chunksize and set it as the blocksize (optimizes read).
     833         109 :         status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
     834         109 :         if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
     835             :         {
     836          13 :             nBlockXSize = (int)chunksize[nZDim - 1];
     837          13 :             if (nZDim >= 2)
     838          13 :                 nBlockYSize = (int)chunksize[nZDim - 2];
     839             :             else
     840           0 :                 nBlockYSize = 1;
     841             :         }
     842             :     }
     843             : 
     844             :     // Deal with bottom-up datasets and nBlockYSize != 1.
     845         654 :     auto poGDS = static_cast<netCDFDataset *>(poDS);
     846         654 :     if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
     847             :     {
     848           5 :         if (poGDS->eAccess == GA_ReadOnly)
     849             :         {
     850             :             // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
     851             :             // width of the raster
     852           5 :             size_t nChunks =
     853           5 :                 static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
     854           5 :             if ((nRasterYSize % nBlockYSize) != 0)
     855           1 :                 nChunks *= 2;
     856             :             const size_t nChunkSize =
     857           5 :                 static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
     858           5 :                 nBlockXSize * nBlockYSize;
     859           5 :             constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
     860           5 :             nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
     861           5 :             if (nChunks)
     862             :             {
     863           5 :                 poGDS->poChunkCache.reset(
     864           5 :                     new netCDFDataset::ChunkCacheType(nChunks));
     865             :             }
     866             :         }
     867             :         else
     868             :         {
     869           0 :             nBlockYSize = 1;
     870             :         }
     871             :     }
     872         654 : }
     873             : 
     874             : // Constructor in create mode.
     875             : // If nZId and following variables are not passed, the band will have 2
     876             : // dimensions.
     877             : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
     878         182 : netCDFRasterBand::netCDFRasterBand(
     879             :     const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
     880             :     const GDALDataType eTypeIn, int nBandIn, bool bSigned,
     881             :     const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
     882             :     int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
     883         182 :     const int *paDimIds)
     884         182 :     : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
     885             :       nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
     886             :       panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
     887         182 :       bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
     888             : {
     889         182 :     poDS = poNCDFDS;
     890         182 :     nBand = nBandIn;
     891             : 
     892         182 :     nRasterXSize = poDS->GetRasterXSize();
     893         182 :     nRasterYSize = poDS->GetRasterYSize();
     894         182 :     nBlockXSize = poDS->GetRasterXSize();
     895         182 :     nBlockYSize = 1;
     896             : 
     897         182 :     if (poDS->GetAccess() != GA_Update)
     898             :     {
     899           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     900             :                  "Dataset is not in update mode, "
     901             :                  "wrong netCDFRasterBand constructor");
     902           0 :         return;
     903             :     }
     904             : 
     905             :     // Take care of all other dimensions.
     906         182 :     if (nZDim > 2 && paDimIds != nullptr)
     907             :     {
     908          27 :         nBandXPos = panBandZPosIn[0];
     909          27 :         nBandYPos = panBandZPosIn[1];
     910             : 
     911          27 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     912          27 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     913             : 
     914          76 :         for (int i = 0; i < nZDim - 2; i++)
     915             :         {
     916          49 :             panBandZPos[i] = panBandZPosIn[i + 2];
     917          49 :             panBandZLev[i] = panBandZLevIn[i];
     918             :         }
     919             :     }
     920             : 
     921             :     // Get the type of the "z" variable, our target raster array.
     922         182 :     eDataType = eTypeIn;
     923             : 
     924         182 :     switch (eDataType)
     925             :     {
     926          77 :         case GDT_Byte:
     927          77 :             nc_datatype = NC_BYTE;
     928             :             // NC_UBYTE (unsigned byte) is only available for NC4.
     929          77 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     930           3 :                 nc_datatype = NC_UBYTE;
     931          77 :             break;
     932           7 :         case GDT_Int8:
     933           7 :             nc_datatype = NC_BYTE;
     934           7 :             break;
     935          11 :         case GDT_Int16:
     936          11 :             nc_datatype = NC_SHORT;
     937          11 :             break;
     938          24 :         case GDT_Int32:
     939          24 :             nc_datatype = NC_INT;
     940          24 :             break;
     941          13 :         case GDT_Float32:
     942          13 :             nc_datatype = NC_FLOAT;
     943          13 :             break;
     944           8 :         case GDT_Float64:
     945           8 :             nc_datatype = NC_DOUBLE;
     946           8 :             break;
     947           7 :         case GDT_Int64:
     948           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     949             :             {
     950           7 :                 nc_datatype = NC_INT64;
     951             :             }
     952             :             else
     953             :             {
     954           0 :                 if (nBand == 1)
     955           0 :                     CPLError(
     956             :                         CE_Warning, CPLE_AppDefined,
     957             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     958             :                         "Int64");
     959           0 :                 nc_datatype = NC_DOUBLE;
     960           0 :                 eDataType = GDT_Float64;
     961             :             }
     962           7 :             break;
     963           7 :         case GDT_UInt64:
     964           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     965             :             {
     966           7 :                 nc_datatype = NC_UINT64;
     967             :             }
     968             :             else
     969             :             {
     970           0 :                 if (nBand == 1)
     971           0 :                     CPLError(
     972             :                         CE_Warning, CPLE_AppDefined,
     973             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     974             :                         "UInt64");
     975           0 :                 nc_datatype = NC_DOUBLE;
     976           0 :                 eDataType = GDT_Float64;
     977             :             }
     978           7 :             break;
     979           6 :         case GDT_UInt16:
     980           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     981             :             {
     982           6 :                 nc_datatype = NC_USHORT;
     983           6 :                 break;
     984             :             }
     985             :             [[fallthrough]];
     986             :         case GDT_UInt32:
     987           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     988             :             {
     989           6 :                 nc_datatype = NC_UINT;
     990           6 :                 break;
     991             :             }
     992             :             [[fallthrough]];
     993             :         default:
     994          16 :             if (nBand == 1)
     995           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
     996             :                          "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
     997           8 :                          static_cast<int>(eDataType));
     998          16 :             nc_datatype = NC_FLOAT;
     999          16 :             eDataType = GDT_Float32;
    1000          16 :             break;
    1001             :     }
    1002             : 
    1003             :     // Define the variable if necessary (if nZId == -1).
    1004         182 :     bool bDefineVar = false;
    1005             : 
    1006         182 :     if (nZId == -1)
    1007             :     {
    1008         160 :         bDefineVar = true;
    1009             : 
    1010             :         // Make sure we are in define mode.
    1011         160 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1012             : 
    1013             :         char szTempPrivate[256 + 1];
    1014         160 :         const char *pszTemp = nullptr;
    1015         160 :         if (!pszBandName || EQUAL(pszBandName, ""))
    1016             :         {
    1017         138 :             snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
    1018         138 :             pszTemp = szTempPrivate;
    1019             :         }
    1020             :         else
    1021             :         {
    1022          22 :             pszTemp = pszBandName;
    1023             :         }
    1024             : 
    1025             :         int status;
    1026         160 :         if (nZDim > 2 && paDimIds != nullptr)
    1027             :         {
    1028           5 :             status =
    1029           5 :                 nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
    1030             :         }
    1031             :         else
    1032             :         {
    1033         155 :             int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
    1034             :             status =
    1035         155 :                 nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
    1036             :         }
    1037         160 :         NCDF_ERR(status);
    1038         160 :         CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
    1039             :                  nc_datatype, nZId);
    1040             : 
    1041         160 :         if (!pszLongName || EQUAL(pszLongName, ""))
    1042             :         {
    1043         153 :             snprintf(szTempPrivate, sizeof(szTempPrivate),
    1044             :                      "GDAL Band Number %d", nBand);
    1045         153 :             pszTemp = szTempPrivate;
    1046             :         }
    1047             :         else
    1048             :         {
    1049           7 :             pszTemp = pszLongName;
    1050             :         }
    1051             :         status =
    1052         160 :             nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
    1053         160 :         NCDF_ERR(status);
    1054             : 
    1055         160 :         poNCDFDS->DefVarDeflate(nZId, true);
    1056             :     }
    1057             : 
    1058             :     // For Byte data add signed/unsigned info.
    1059         182 :     if (eDataType == GDT_Byte || eDataType == GDT_Int8)
    1060             :     {
    1061          84 :         if (bDefineVar)
    1062             :         {
    1063             :             // Only add attributes if creating variable.
    1064             :             // For unsigned NC_BYTE (except NC4 format),
    1065             :             // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
    1066          76 :             if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
    1067             :             {
    1068          73 :                 CPLDebug("GDAL_netCDF",
    1069             :                          "adding valid_range attributes for Byte Band");
    1070          73 :                 short l_adfValidRange[2] = {0, 0};
    1071             :                 int status;
    1072          73 :                 if (bSignedData || eDataType == GDT_Int8)
    1073             :                 {
    1074           7 :                     l_adfValidRange[0] = -128;
    1075           7 :                     l_adfValidRange[1] = 127;
    1076           7 :                     status =
    1077           7 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
    1078             :                 }
    1079             :                 else
    1080             :                 {
    1081          66 :                     l_adfValidRange[0] = 0;
    1082          66 :                     l_adfValidRange[1] = 255;
    1083             :                     status =
    1084          66 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
    1085             :                 }
    1086          73 :                 NCDF_ERR(status);
    1087          73 :                 status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
    1088             :                                           2, l_adfValidRange);
    1089          73 :                 NCDF_ERR(status);
    1090             :             }
    1091             :         }
    1092             :     }
    1093             : 
    1094         182 :     if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
    1095         101 :         nc_datatype != NC_UBYTE)
    1096             :     {
    1097             :         // Set default nodata.
    1098          98 :         bool bIgnored = false;
    1099             :         double dfNoData =
    1100          98 :             NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
    1101             : #ifdef NCDF_DEBUG
    1102             :         CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
    1103             : #endif
    1104          98 :         netCDFRasterBand::SetNoDataValue(dfNoData);
    1105             :     }
    1106             : 
    1107         182 :     SetBlockSize();
    1108             : }
    1109             : 
    1110             : /************************************************************************/
    1111             : /*                         ~netCDFRasterBand()                          */
    1112             : /************************************************************************/
    1113             : 
    1114        1308 : netCDFRasterBand::~netCDFRasterBand()
    1115             : {
    1116         654 :     netCDFRasterBand::FlushCache(true);
    1117         654 :     CPLFree(panBandZPos);
    1118         654 :     CPLFree(panBandZLev);
    1119        1308 : }
    1120             : 
    1121             : /************************************************************************/
    1122             : /*                          GetMetadata()                               */
    1123             : /************************************************************************/
    1124             : 
    1125          50 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
    1126             : {
    1127          50 :     if (!m_bCreateMetadataFromOtherVarsDone)
    1128          48 :         CreateMetadataFromOtherVars();
    1129          50 :     return GDALPamRasterBand::GetMetadata(pszDomain);
    1130             : }
    1131             : 
    1132             : /************************************************************************/
    1133             : /*                        GetMetadataItem()                             */
    1134             : /************************************************************************/
    1135             : 
    1136         546 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
    1137             :                                               const char *pszDomain)
    1138             : {
    1139         546 :     if (!m_bCreateMetadataFromOtherVarsDone &&
    1140         530 :         STARTS_WITH(pszName, "NETCDF_DIM_") &&
    1141           1 :         (!pszDomain || pszDomain[0] == 0))
    1142           1 :         CreateMetadataFromOtherVars();
    1143         546 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
    1144             : }
    1145             : 
    1146             : /************************************************************************/
    1147             : /*                        SetMetadataItem()                             */
    1148             : /************************************************************************/
    1149             : 
    1150           7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
    1151             :                                          const char *pszValue,
    1152             :                                          const char *pszDomain)
    1153             : {
    1154           9 :     if (GetAccess() == GA_Update &&
    1155           9 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    1156             :     {
    1157             :         // Same logic as in CopyMetadata()
    1158             : 
    1159           2 :         const char *const papszIgnoreBand[] = {
    1160             :             CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    1161             :             NCDF_FillValue, "coordinates",   nullptr};
    1162             :         // Do not copy varname, stats, NETCDF_DIM_*, nodata
    1163             :         // and items in papszIgnoreBand.
    1164           6 :         if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
    1165           2 :             STARTS_WITH(pszName, "STATISTICS_") ||
    1166           2 :             STARTS_WITH(pszName, "NETCDF_DIM_") ||
    1167           2 :             STARTS_WITH(pszName, "missing_value") ||
    1168           6 :             STARTS_WITH(pszName, "_FillValue") ||
    1169           2 :             CSLFindString(papszIgnoreBand, pszName) != -1)
    1170             :         {
    1171             :             // do nothing
    1172             :         }
    1173             :         else
    1174             :         {
    1175           2 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1176             : 
    1177           2 :             if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
    1178           2 :                 return CE_Failure;
    1179             :         }
    1180             :     }
    1181             : 
    1182           5 :     return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    1183             : }
    1184             : 
    1185             : /************************************************************************/
    1186             : /*                          SetMetadata()                               */
    1187             : /************************************************************************/
    1188             : 
    1189           2 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
    1190             : {
    1191           4 :     if (GetAccess() == GA_Update &&
    1192           2 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    1193             :     {
    1194             :         // We don't handle metadata item removal for now
    1195           4 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    1196             :              ++papszIter)
    1197             :         {
    1198           2 :             char *pszName = nullptr;
    1199           2 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    1200           2 :             if (pszName && pszValue)
    1201           2 :                 SetMetadataItem(pszName, pszValue);
    1202           2 :             CPLFree(pszName);
    1203             :         }
    1204             :     }
    1205           2 :     return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
    1206             : }
    1207             : 
    1208             : /************************************************************************/
    1209             : /*                             GetOffset()                              */
    1210             : /************************************************************************/
    1211          50 : double netCDFRasterBand::GetOffset(int *pbSuccess)
    1212             : {
    1213          50 :     if (pbSuccess != nullptr)
    1214          45 :         *pbSuccess = static_cast<int>(m_bHaveOffset);
    1215             : 
    1216          50 :     return m_dfOffset;
    1217             : }
    1218             : 
    1219             : /************************************************************************/
    1220             : /*                             SetOffset()                              */
    1221             : /************************************************************************/
    1222           1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
    1223             : {
    1224           2 :     CPLMutexHolderD(&hNCMutex);
    1225             : 
    1226             :     // Write value if in update mode.
    1227           1 :     if (poDS->GetAccess() == GA_Update)
    1228             :     {
    1229             :         // Make sure we are in define mode.
    1230           1 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1231             : 
    1232           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
    1233             :                                              NC_DOUBLE, 1, &dfNewOffset);
    1234             : 
    1235           1 :         NCDF_ERR(status);
    1236           1 :         if (status == NC_NOERR)
    1237             :         {
    1238           1 :             SetOffsetNoUpdate(dfNewOffset);
    1239           1 :             return CE_None;
    1240             :         }
    1241             : 
    1242           0 :         return CE_Failure;
    1243             :     }
    1244             : 
    1245           0 :     SetOffsetNoUpdate(dfNewOffset);
    1246           0 :     return CE_None;
    1247             : }
    1248             : 
    1249             : /************************************************************************/
    1250             : /*                         SetOffsetNoUpdate()                          */
    1251             : /************************************************************************/
    1252          17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
    1253             : {
    1254          17 :     m_dfOffset = dfVal;
    1255          17 :     m_bHaveOffset = true;
    1256          17 : }
    1257             : 
    1258             : /************************************************************************/
    1259             : /*                              GetScale()                              */
    1260             : /************************************************************************/
    1261          50 : double netCDFRasterBand::GetScale(int *pbSuccess)
    1262             : {
    1263          50 :     if (pbSuccess != nullptr)
    1264          45 :         *pbSuccess = static_cast<int>(m_bHaveScale);
    1265             : 
    1266          50 :     return m_dfScale;
    1267             : }
    1268             : 
    1269             : /************************************************************************/
    1270             : /*                              SetScale()                              */
    1271             : /************************************************************************/
    1272           1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
    1273             : {
    1274           2 :     CPLMutexHolderD(&hNCMutex);
    1275             : 
    1276             :     // Write value if in update mode.
    1277           1 :     if (poDS->GetAccess() == GA_Update)
    1278             :     {
    1279             :         // Make sure we are in define mode.
    1280           1 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1281             : 
    1282           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
    1283             :                                              NC_DOUBLE, 1, &dfNewScale);
    1284             : 
    1285           1 :         NCDF_ERR(status);
    1286           1 :         if (status == NC_NOERR)
    1287             :         {
    1288           1 :             SetScaleNoUpdate(dfNewScale);
    1289           1 :             return CE_None;
    1290             :         }
    1291             : 
    1292           0 :         return CE_Failure;
    1293             :     }
    1294             : 
    1295           0 :     SetScaleNoUpdate(dfNewScale);
    1296           0 :     return CE_None;
    1297             : }
    1298             : 
    1299             : /************************************************************************/
    1300             : /*                         SetScaleNoUpdate()                           */
    1301             : /************************************************************************/
    1302          21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
    1303             : {
    1304          21 :     m_dfScale = dfVal;
    1305          21 :     m_bHaveScale = true;
    1306          21 : }
    1307             : 
    1308             : /************************************************************************/
    1309             : /*                            GetUnitType()                             */
    1310             : /************************************************************************/
    1311             : 
    1312          22 : const char *netCDFRasterBand::GetUnitType()
    1313             : 
    1314             : {
    1315          22 :     if (!m_osUnitType.empty())
    1316           6 :         return m_osUnitType;
    1317             : 
    1318          16 :     return GDALRasterBand::GetUnitType();
    1319             : }
    1320             : 
    1321             : /************************************************************************/
    1322             : /*                           SetUnitType()                              */
    1323             : /************************************************************************/
    1324             : 
    1325           1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
    1326             : 
    1327             : {
    1328           2 :     CPLMutexHolderD(&hNCMutex);
    1329             : 
    1330           2 :     const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1331             : 
    1332           1 :     if (!osUnitType.empty())
    1333             :     {
    1334             :         // Write value if in update mode.
    1335           1 :         if (poDS->GetAccess() == GA_Update)
    1336             :         {
    1337             :             // Make sure we are in define mode.
    1338           1 :             static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
    1339             : 
    1340           1 :             const int status = nc_put_att_text(
    1341             :                 cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
    1342             : 
    1343           1 :             NCDF_ERR(status);
    1344           1 :             if (status == NC_NOERR)
    1345             :             {
    1346           1 :                 SetUnitTypeNoUpdate(pszNewValue);
    1347           1 :                 return CE_None;
    1348             :             }
    1349             : 
    1350           0 :             return CE_Failure;
    1351             :         }
    1352             :     }
    1353             : 
    1354           0 :     SetUnitTypeNoUpdate(pszNewValue);
    1355             : 
    1356           0 :     return CE_None;
    1357             : }
    1358             : 
    1359             : /************************************************************************/
    1360             : /*                       SetUnitTypeNoUpdate()                          */
    1361             : /************************************************************************/
    1362             : 
    1363         473 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
    1364             : {
    1365         473 :     m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1366         473 : }
    1367             : 
    1368             : /************************************************************************/
    1369             : /*                           GetNoDataValue()                           */
    1370             : /************************************************************************/
    1371             : 
    1372         155 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
    1373             : 
    1374             : {
    1375         155 :     if (m_bNoDataSetAsInt64)
    1376             :     {
    1377           0 :         if (pbSuccess)
    1378           0 :             *pbSuccess = TRUE;
    1379           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
    1380             :     }
    1381             : 
    1382         155 :     if (m_bNoDataSetAsUInt64)
    1383             :     {
    1384           0 :         if (pbSuccess)
    1385           0 :             *pbSuccess = TRUE;
    1386           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
    1387             :     }
    1388             : 
    1389         155 :     if (m_bNoDataSet)
    1390             :     {
    1391         118 :         if (pbSuccess)
    1392         102 :             *pbSuccess = TRUE;
    1393         118 :         return m_dfNoDataValue;
    1394             :     }
    1395             : 
    1396          37 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
    1397             : }
    1398             : 
    1399             : /************************************************************************/
    1400             : /*                        GetNoDataValueAsInt64()                       */
    1401             : /************************************************************************/
    1402             : 
    1403           4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
    1404             : 
    1405             : {
    1406           4 :     if (m_bNoDataSetAsInt64)
    1407             :     {
    1408           4 :         if (pbSuccess)
    1409           4 :             *pbSuccess = TRUE;
    1410             : 
    1411           4 :         return m_nNodataValueInt64;
    1412             :     }
    1413             : 
    1414           0 :     return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
    1415             : }
    1416             : 
    1417             : /************************************************************************/
    1418             : /*                        GetNoDataValueAsUInt64()                      */
    1419             : /************************************************************************/
    1420             : 
    1421           4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
    1422             : 
    1423             : {
    1424           4 :     if (m_bNoDataSetAsUInt64)
    1425             :     {
    1426           4 :         if (pbSuccess)
    1427           4 :             *pbSuccess = TRUE;
    1428             : 
    1429           4 :         return m_nNodataValueUInt64;
    1430             :     }
    1431             : 
    1432           0 :     return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
    1433             : }
    1434             : 
    1435             : /************************************************************************/
    1436             : /*                           SetNoDataValue()                           */
    1437             : /************************************************************************/
    1438             : 
    1439         134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
    1440             : 
    1441             : {
    1442         268 :     CPLMutexHolderD(&hNCMutex);
    1443             : 
    1444             :     // If already set to new value, don't do anything.
    1445         134 :     if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
    1446          19 :         return CE_None;
    1447             : 
    1448             :     // Write value if in update mode.
    1449         115 :     if (poDS->GetAccess() == GA_Update)
    1450             :     {
    1451             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1452             :         // but it is ok if variable has not been written to, so only print
    1453             :         // debug. See bug #4484.
    1454         125 :         if (m_bNoDataSet &&
    1455          10 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1456             :         {
    1457           0 :             CPLDebug("GDAL_netCDF",
    1458             :                      "Setting NoDataValue to %.17g (previously set to %.17g) "
    1459             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1460             :                      dfNoData, m_dfNoDataValue, cdfid, nBand);
    1461             :         }
    1462             : #ifdef NCDF_DEBUG
    1463             :         else
    1464             :         {
    1465             :             CPLDebug("GDAL_netCDF",
    1466             :                      "Setting NoDataValue to %.17g (id #%d, band #%d)",
    1467             :                      dfNoData, cdfid, nBand);
    1468             :         }
    1469             : #endif
    1470             :         // Make sure we are in define mode.
    1471         115 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1472             : 
    1473             :         int status;
    1474         115 :         if (eDataType == GDT_Byte)
    1475             :         {
    1476           6 :             if (bSignedData)
    1477             :             {
    1478           0 :                 signed char cNoDataValue = static_cast<signed char>(dfNoData);
    1479           0 :                 status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
    1480             :                                           nc_datatype, 1, &cNoDataValue);
    1481             :             }
    1482             :             else
    1483             :             {
    1484           6 :                 const unsigned char ucNoDataValue =
    1485           6 :                     static_cast<unsigned char>(dfNoData);
    1486           6 :                 status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
    1487             :                                           nc_datatype, 1, &ucNoDataValue);
    1488             :             }
    1489             :         }
    1490         109 :         else if (eDataType == GDT_Int16)
    1491             :         {
    1492          14 :             short nsNoDataValue = static_cast<short>(dfNoData);
    1493          14 :             status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1494             :                                       1, &nsNoDataValue);
    1495             :         }
    1496          95 :         else if (eDataType == GDT_Int32)
    1497             :         {
    1498          27 :             int nNoDataValue = static_cast<int>(dfNoData);
    1499          27 :             status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
    1500             :                                     &nNoDataValue);
    1501             :         }
    1502          68 :         else if (eDataType == GDT_Float32)
    1503             :         {
    1504          31 :             float fNoDataValue = static_cast<float>(dfNoData);
    1505          31 :             status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1506             :                                       1, &fNoDataValue);
    1507             :         }
    1508          37 :         else if (eDataType == GDT_UInt16 &&
    1509           6 :                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
    1510             :                      NCDF_FORMAT_NC4)
    1511             :         {
    1512           6 :             unsigned short usNoDataValue =
    1513           6 :                 static_cast<unsigned short>(dfNoData);
    1514           6 :             status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1515           6 :                                        1, &usNoDataValue);
    1516             :         }
    1517          31 :         else if (eDataType == GDT_UInt32 &&
    1518           7 :                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
    1519             :                      NCDF_FORMAT_NC4)
    1520             :         {
    1521           7 :             unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
    1522           7 :             status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1523           7 :                                      1, &unNoDataValue);
    1524             :         }
    1525             :         else
    1526             :         {
    1527          24 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1528             :                                        1, &dfNoData);
    1529             :         }
    1530             : 
    1531         115 :         NCDF_ERR(status);
    1532             : 
    1533             :         // Update status if write worked.
    1534         115 :         if (status == NC_NOERR)
    1535             :         {
    1536         115 :             SetNoDataValueNoUpdate(dfNoData);
    1537         115 :             return CE_None;
    1538             :         }
    1539             : 
    1540           0 :         return CE_Failure;
    1541             :     }
    1542             : 
    1543           0 :     SetNoDataValueNoUpdate(dfNoData);
    1544           0 :     return CE_None;
    1545             : }
    1546             : 
    1547             : /************************************************************************/
    1548             : /*                       SetNoDataValueNoUpdate()                       */
    1549             : /************************************************************************/
    1550             : 
    1551         430 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
    1552             : {
    1553         430 :     m_dfNoDataValue = dfNoData;
    1554         430 :     m_bNoDataSet = true;
    1555         430 :     m_bNoDataSetAsInt64 = false;
    1556         430 :     m_bNoDataSetAsUInt64 = false;
    1557         430 : }
    1558             : 
    1559             : /************************************************************************/
    1560             : /*                        SetNoDataValueAsInt64()                       */
    1561             : /************************************************************************/
    1562             : 
    1563           3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    1564             : 
    1565             : {
    1566           6 :     CPLMutexHolderD(&hNCMutex);
    1567             : 
    1568             :     // If already set to new value, don't do anything.
    1569           3 :     if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
    1570           0 :         return CE_None;
    1571             : 
    1572             :     // Write value if in update mode.
    1573           3 :     if (poDS->GetAccess() == GA_Update)
    1574             :     {
    1575             :         // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
    1576             :         // but it is ok if variable has not been written to, so only print
    1577             :         // debug. See bug #4484.
    1578           3 :         if (m_bNoDataSetAsInt64 &&
    1579           0 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1580             :         {
    1581           0 :             CPLDebug("GDAL_netCDF",
    1582             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1583             :                      " (previously set to " CPL_FRMT_GIB ") "
    1584             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1585             :                      static_cast<GIntBig>(nNoData),
    1586           0 :                      static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
    1587             :         }
    1588             : #ifdef NCDF_DEBUG
    1589             :         else
    1590             :         {
    1591             :             CPLDebug("GDAL_netCDF",
    1592             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1593             :                      " (id #%d, band #%d)",
    1594             :                      static_cast<GIntBig>(nNoData), cdfid, nBand);
    1595             :         }
    1596             : #endif
    1597             :         // Make sure we are in define mode.
    1598           3 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1599             : 
    1600             :         int status;
    1601           3 :         if (eDataType == GDT_Int64 &&
    1602           3 :             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1603             :         {
    1604           3 :             long long tmp = static_cast<long long>(nNoData);
    1605           3 :             status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
    1606           3 :                                          nc_datatype, 1, &tmp);
    1607             :         }
    1608             :         else
    1609             :         {
    1610           0 :             double dfNoData = static_cast<double>(nNoData);
    1611           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1612             :                                        1, &dfNoData);
    1613             :         }
    1614             : 
    1615           3 :         NCDF_ERR(status);
    1616             : 
    1617             :         // Update status if write worked.
    1618           3 :         if (status == NC_NOERR)
    1619             :         {
    1620           3 :             SetNoDataValueNoUpdate(nNoData);
    1621           3 :             return CE_None;
    1622             :         }
    1623             : 
    1624           0 :         return CE_Failure;
    1625             :     }
    1626             : 
    1627           0 :     SetNoDataValueNoUpdate(nNoData);
    1628           0 :     return CE_None;
    1629             : }
    1630             : 
    1631             : /************************************************************************/
    1632             : /*                       SetNoDataValueNoUpdate()                       */
    1633             : /************************************************************************/
    1634             : 
    1635          11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
    1636             : {
    1637          11 :     m_nNodataValueInt64 = nNoData;
    1638          11 :     m_bNoDataSet = false;
    1639          11 :     m_bNoDataSetAsInt64 = true;
    1640          11 :     m_bNoDataSetAsUInt64 = false;
    1641          11 : }
    1642             : 
    1643             : /************************************************************************/
    1644             : /*                        SetNoDataValueAsUInt64()                      */
    1645             : /************************************************************************/
    1646             : 
    1647           3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    1648             : 
    1649             : {
    1650           6 :     CPLMutexHolderD(&hNCMutex);
    1651             : 
    1652             :     // If already set to new value, don't do anything.
    1653           3 :     if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
    1654           0 :         return CE_None;
    1655             : 
    1656             :     // Write value if in update mode.
    1657           3 :     if (poDS->GetAccess() == GA_Update)
    1658             :     {
    1659             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1660             :         // but it is ok if variable has not been written to, so only print
    1661             :         // debug. See bug #4484.
    1662           3 :         if (m_bNoDataSetAsUInt64 &&
    1663           0 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1664             :         {
    1665           0 :             CPLDebug("GDAL_netCDF",
    1666             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1667             :                      " (previously set to " CPL_FRMT_GUIB ") "
    1668             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1669             :                      static_cast<GUIntBig>(nNoData),
    1670           0 :                      static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
    1671             :         }
    1672             : #ifdef NCDF_DEBUG
    1673             :         else
    1674             :         {
    1675             :             CPLDebug("GDAL_netCDF",
    1676             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1677             :                      " (id #%d, band #%d)",
    1678             :                      static_cast<GUIntBig>(nNoData), cdfid, nBand);
    1679             :         }
    1680             : #endif
    1681             :         // Make sure we are in define mode.
    1682           3 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1683             : 
    1684             :         int status;
    1685           3 :         if (eDataType == GDT_UInt64 &&
    1686           3 :             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1687             :         {
    1688           3 :             unsigned long long tmp = static_cast<long long>(nNoData);
    1689           3 :             status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
    1690           3 :                                           nc_datatype, 1, &tmp);
    1691             :         }
    1692             :         else
    1693             :         {
    1694           0 :             double dfNoData = static_cast<double>(nNoData);
    1695           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1696             :                                        1, &dfNoData);
    1697             :         }
    1698             : 
    1699           3 :         NCDF_ERR(status);
    1700             : 
    1701             :         // Update status if write worked.
    1702           3 :         if (status == NC_NOERR)
    1703             :         {
    1704           3 :             SetNoDataValueNoUpdate(nNoData);
    1705           3 :             return CE_None;
    1706             :         }
    1707             : 
    1708           0 :         return CE_Failure;
    1709             :     }
    1710             : 
    1711           0 :     SetNoDataValueNoUpdate(nNoData);
    1712           0 :     return CE_None;
    1713             : }
    1714             : 
    1715             : /************************************************************************/
    1716             : /*                       SetNoDataValueNoUpdate()                       */
    1717             : /************************************************************************/
    1718             : 
    1719          10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
    1720             : {
    1721          10 :     m_nNodataValueUInt64 = nNoData;
    1722          10 :     m_bNoDataSet = false;
    1723          10 :     m_bNoDataSetAsInt64 = false;
    1724          10 :     m_bNoDataSetAsUInt64 = true;
    1725          10 : }
    1726             : 
    1727             : /************************************************************************/
    1728             : /*                        DeleteNoDataValue()                           */
    1729             : /************************************************************************/
    1730             : 
    1731             : #ifdef notdef
    1732             : CPLErr netCDFRasterBand::DeleteNoDataValue()
    1733             : 
    1734             : {
    1735             :     CPLMutexHolderD(&hNCMutex);
    1736             : 
    1737             :     if (!bNoDataSet)
    1738             :         return CE_None;
    1739             : 
    1740             :     // Write value if in update mode.
    1741             :     if (poDS->GetAccess() == GA_Update)
    1742             :     {
    1743             :         // Make sure we are in define mode.
    1744             :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1745             : 
    1746             :         status = nc_del_att(cdfid, nZId, NCDF_FillValue);
    1747             : 
    1748             :         NCDF_ERR(status);
    1749             : 
    1750             :         // Update status if write worked.
    1751             :         if (status == NC_NOERR)
    1752             :         {
    1753             :             dfNoDataValue = 0.0;
    1754             :             bNoDataSet = false;
    1755             :             return CE_None;
    1756             :         }
    1757             : 
    1758             :         return CE_Failure;
    1759             :     }
    1760             : 
    1761             :     dfNoDataValue = 0.0;
    1762             :     bNoDataSet = false;
    1763             :     return CE_None;
    1764             : }
    1765             : #endif
    1766             : 
    1767             : /************************************************************************/
    1768             : /*                           SerializeToXML()                           */
    1769             : /************************************************************************/
    1770             : 
    1771           5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
    1772             : {
    1773             :     // Overridden from GDALPamDataset to add only band histogram
    1774             :     // and statistics. See bug #4244.
    1775           5 :     if (psPam == nullptr)
    1776           0 :         return nullptr;
    1777             : 
    1778             :     // Setup root node and attributes.
    1779             :     CPLXMLNode *psTree =
    1780           5 :         CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
    1781             : 
    1782           5 :     if (GetBand() > 0)
    1783             :     {
    1784          10 :         CPLString oFmt;
    1785           5 :         CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
    1786             :     }
    1787             : 
    1788             :     // Histograms.
    1789           5 :     if (psPam->psSavedHistograms != nullptr)
    1790           1 :         CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
    1791             : 
    1792             :     // Metadata (statistics only).
    1793           5 :     GDALMultiDomainMetadata oMDMDStats;
    1794           5 :     const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
    1795             :                                   "STATISTICS_MEAN", "STATISTICS_STDDEV",
    1796             :                                   nullptr};
    1797          25 :     for (int i = 0; i < CSLCount(papszMDStats); i++)
    1798             :     {
    1799          20 :         const char *pszMDI = GetMetadataItem(papszMDStats[i]);
    1800          20 :         if (pszMDI)
    1801           4 :             oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
    1802             :     }
    1803           5 :     CPLXMLNode *psMD = oMDMDStats.Serialize();
    1804             : 
    1805           5 :     if (psMD != nullptr)
    1806             :     {
    1807           1 :         if (psMD->psChild == nullptr)
    1808           0 :             CPLDestroyXMLNode(psMD);
    1809             :         else
    1810           1 :             CPLAddXMLChild(psTree, psMD);
    1811             :     }
    1812             : 
    1813             :     // We don't want to return anything if we had no metadata to attach.
    1814           5 :     if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
    1815             :     {
    1816           3 :         CPLDestroyXMLNode(psTree);
    1817           3 :         psTree = nullptr;
    1818             :     }
    1819             : 
    1820           5 :     return psTree;
    1821             : }
    1822             : 
    1823             : /************************************************************************/
    1824             : /*               Get1DVariableIndexedByDimension()                      */
    1825             : /************************************************************************/
    1826             : 
    1827          79 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
    1828             :                                            const char *pszDimName,
    1829             :                                            bool bVerboseError, int *pnGroupID)
    1830             : {
    1831          79 :     *pnGroupID = -1;
    1832          79 :     int nVarID = -1;
    1833             :     // First try to find a variable whose name is identical to the dimension
    1834             :     // name, and check that it is indeed indexed by this dimension
    1835          79 :     if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
    1836             :     {
    1837          65 :         int nDimCountOfVariable = 0;
    1838          65 :         nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
    1839          65 :         if (nDimCountOfVariable == 1)
    1840             :         {
    1841          65 :             int nDimIdOfVariable = -1;
    1842          65 :             nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
    1843          65 :             if (nDimIdOfVariable == nDimId)
    1844             :             {
    1845          65 :                 return nVarID;
    1846             :             }
    1847             :         }
    1848             :     }
    1849             : 
    1850             :     // Otherwise iterate over the variables to find potential candidates
    1851             :     // TODO: should be modified to search also in other groups using the same
    1852             :     //       logic than in NCDFResolveVar(), but maybe not needed if it's a
    1853             :     //       very rare case? and I think this is not CF compliant.
    1854          14 :     int nvars = 0;
    1855          14 :     CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
    1856             : 
    1857          14 :     int nCountCandidateVars = 0;
    1858          14 :     int nCandidateVarID = -1;
    1859          65 :     for (int k = 0; k < nvars; k++)
    1860             :     {
    1861          51 :         int nDimCountOfVariable = 0;
    1862          51 :         nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
    1863          51 :         if (nDimCountOfVariable == 1)
    1864             :         {
    1865          27 :             int nDimIdOfVariable = -1;
    1866          27 :             nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
    1867          27 :             if (nDimIdOfVariable == nDimId)
    1868             :             {
    1869           7 :                 nCountCandidateVars++;
    1870           7 :                 nCandidateVarID = k;
    1871             :             }
    1872             :         }
    1873             :     }
    1874          14 :     if (nCountCandidateVars > 1)
    1875             :     {
    1876           1 :         if (bVerboseError)
    1877             :         {
    1878           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1879             :                      "Several 1D variables are indexed by dimension %s",
    1880             :                      pszDimName);
    1881             :         }
    1882           1 :         *pnGroupID = -1;
    1883           1 :         return -1;
    1884             :     }
    1885          13 :     else if (nCandidateVarID < 0)
    1886             :     {
    1887           8 :         if (bVerboseError)
    1888             :         {
    1889           8 :             CPLError(CE_Warning, CPLE_AppDefined,
    1890             :                      "No 1D variable is indexed by dimension %s", pszDimName);
    1891             :         }
    1892             :     }
    1893          13 :     *pnGroupID = cdfid;
    1894          13 :     return nCandidateVarID;
    1895             : }
    1896             : 
    1897             : /************************************************************************/
    1898             : /*                      CreateMetadataFromAttributes()                  */
    1899             : /************************************************************************/
    1900             : 
    1901         472 : void netCDFRasterBand::CreateMetadataFromAttributes()
    1902             : {
    1903         472 :     char szVarName[NC_MAX_NAME + 1] = {};
    1904         472 :     int status = nc_inq_varname(cdfid, nZId, szVarName);
    1905         472 :     NCDF_ERR(status);
    1906             : 
    1907         472 :     GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
    1908             : 
    1909             :     // Get attribute metadata.
    1910         472 :     int nAtt = 0;
    1911         472 :     NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
    1912             : 
    1913        2018 :     for (int i = 0; i < nAtt; i++)
    1914             :     {
    1915        1546 :         char szMetaName[NC_MAX_NAME + 1] = {};
    1916        1546 :         status = nc_inq_attname(cdfid, nZId, i, szMetaName);
    1917        1546 :         if (status != NC_NOERR)
    1918          12 :             continue;
    1919             : 
    1920        1546 :         if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
    1921             :         {
    1922          12 :             continue;
    1923             :         }
    1924             : 
    1925        1534 :         char *pszMetaValue = nullptr;
    1926        1534 :         if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
    1927             :         {
    1928        1534 :             GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
    1929             :         }
    1930             :         else
    1931             :         {
    1932           0 :             CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
    1933             :         }
    1934             : 
    1935        1534 :         if (pszMetaValue)
    1936             :         {
    1937        1534 :             CPLFree(pszMetaValue);
    1938        1534 :             pszMetaValue = nullptr;
    1939             :         }
    1940             :     }
    1941         472 : }
    1942             : 
    1943             : /************************************************************************/
    1944             : /*                      CreateMetadataFromOtherVars()                   */
    1945             : /************************************************************************/
    1946             : 
    1947          49 : void netCDFRasterBand::CreateMetadataFromOtherVars()
    1948             : 
    1949             : {
    1950          49 :     CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
    1951          49 :     m_bCreateMetadataFromOtherVarsDone = true;
    1952             : 
    1953          49 :     netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
    1954          49 :     const int nPamFlagsBackup = l_poDS->nPamFlags;
    1955             : 
    1956             :     // Compute all dimensions from Band number and save in Metadata.
    1957          49 :     int nd = 0;
    1958          49 :     nc_inq_varndims(cdfid, nZId, &nd);
    1959             :     // Compute multidimention band position.
    1960             :     //
    1961             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    1962             :     // if Data[2,3,4,x,y]
    1963             :     //
    1964             :     //  BandPos0 = (nBand) / (3*4)
    1965             :     //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)
    1966             :     //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)
    1967             : 
    1968          49 :     int Sum = 1;
    1969          49 :     if (nd == 3)
    1970             :     {
    1971           5 :         Sum *= panBandZLev[0];
    1972             :     }
    1973             : 
    1974             :     // Loop over non-spatial dimensions.
    1975          49 :     int Taken = 0;
    1976             : 
    1977          89 :     for (int i = 0; i < nd - 2; i++)
    1978             :     {
    1979             :         int result;
    1980          40 :         if (i != nd - 2 - 1)
    1981             :         {
    1982          18 :             Sum = 1;
    1983          37 :             for (int j = i + 1; j < nd - 2; j++)
    1984             :             {
    1985          19 :                 Sum *= panBandZLev[j];
    1986             :             }
    1987          18 :             result = static_cast<int>((nLevel - Taken) / Sum);
    1988             :         }
    1989             :         else
    1990             :         {
    1991          22 :             result = static_cast<int>((nLevel - Taken) % Sum);
    1992             :         }
    1993             : 
    1994          40 :         char szName[NC_MAX_NAME + 1] = {};
    1995          40 :         snprintf(szName, sizeof(szName), "%s",
    1996          40 :                  l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
    1997             : 
    1998             :         char szMetaName[NC_MAX_NAME + 1 + 32];
    1999          40 :         snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
    2000             : 
    2001          40 :         const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
    2002          40 :         const int nVarID = l_poDS->m_anExtraDimVarIds[i];
    2003          40 :         if (nVarID < 0)
    2004             :         {
    2005           2 :             GDALPamRasterBand::SetMetadataItem(szMetaName,
    2006             :                                                CPLSPrintf("%d", result + 1));
    2007             :         }
    2008             :         else
    2009             :         {
    2010             :             // TODO: Make sure all the status checks make sense.
    2011             : 
    2012          38 :             nc_type nVarType = NC_NAT;
    2013          38 :             /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
    2014             : 
    2015          38 :             int nDims = 0;
    2016          38 :             /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
    2017             : 
    2018          38 :             char szMetaTemp[256] = {};
    2019          38 :             if (nDims == 1)
    2020             :             {
    2021          38 :                 size_t count[1] = {1};
    2022          38 :                 size_t start[1] = {static_cast<size_t>(result)};
    2023             : 
    2024          38 :                 switch (nVarType)
    2025             :                 {
    2026           0 :                     case NC_BYTE:
    2027             :                         // TODO: Check for signed/unsigned byte.
    2028             :                         signed char cData;
    2029           0 :                         /* status = */ nc_get_vara_schar(nGroupID, nVarID,
    2030             :                                                          start, count, &cData);
    2031           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
    2032           0 :                         break;
    2033           0 :                     case NC_SHORT:
    2034             :                         short sData;
    2035           0 :                         /* status = */ nc_get_vara_short(nGroupID, nVarID,
    2036             :                                                          start, count, &sData);
    2037           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
    2038           0 :                         break;
    2039          19 :                     case NC_INT:
    2040             :                     {
    2041             :                         int nData;
    2042          19 :                         /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
    2043             :                                                        count, &nData);
    2044          19 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
    2045          19 :                         break;
    2046             :                     }
    2047           0 :                     case NC_FLOAT:
    2048             :                         float fData;
    2049           0 :                         /* status = */ nc_get_vara_float(nGroupID, nVarID,
    2050             :                                                          start, count, &fData);
    2051           0 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
    2052             :                                     fData);
    2053           0 :                         break;
    2054          18 :                     case NC_DOUBLE:
    2055             :                         double dfData;
    2056          18 :                         /* status = */ nc_get_vara_double(
    2057             :                             nGroupID, nVarID, start, count, &dfData);
    2058          18 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
    2059             :                                     dfData);
    2060          18 :                         break;
    2061           0 :                     case NC_UBYTE:
    2062             :                         unsigned char ucData;
    2063           0 :                         /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
    2064             :                                                          start, count, &ucData);
    2065           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
    2066           0 :                         break;
    2067           0 :                     case NC_USHORT:
    2068             :                         unsigned short usData;
    2069           0 :                         /* status = */ nc_get_vara_ushort(
    2070             :                             nGroupID, nVarID, start, count, &usData);
    2071           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
    2072           0 :                         break;
    2073           0 :                     case NC_UINT:
    2074             :                     {
    2075             :                         unsigned int unData;
    2076           0 :                         /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
    2077             :                                                         count, &unData);
    2078           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
    2079           0 :                         break;
    2080             :                     }
    2081           1 :                     case NC_INT64:
    2082             :                     {
    2083             :                         long long nData;
    2084           1 :                         /* status = */ nc_get_vara_longlong(
    2085             :                             nGroupID, nVarID, start, count, &nData);
    2086           1 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
    2087             :                                  nData);
    2088           1 :                         break;
    2089             :                     }
    2090           0 :                     case NC_UINT64:
    2091             :                     {
    2092             :                         unsigned long long unData;
    2093           0 :                         /* status = */ nc_get_vara_ulonglong(
    2094             :                             nGroupID, nVarID, start, count, &unData);
    2095           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
    2096             :                                  unData);
    2097           0 :                         break;
    2098             :                     }
    2099           0 :                     default:
    2100           0 :                         CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
    2101             :                                  szMetaTemp, nVarType);
    2102           0 :                         break;
    2103             :                 }
    2104             :             }
    2105             :             else
    2106             :             {
    2107           0 :                 snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
    2108             :             }
    2109             : 
    2110             :             // Save dimension value.
    2111             :             // NOTE: removed #original_units as not part of CF-1.
    2112             : 
    2113          38 :             GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
    2114             :         }
    2115             : 
    2116             :         // Avoid int32 overflow. Perhaps something more sensible to do here ?
    2117          40 :         if (result > 0 && Sum > INT_MAX / result)
    2118           0 :             break;
    2119          40 :         if (Taken > INT_MAX - result * Sum)
    2120           0 :             break;
    2121             : 
    2122          40 :         Taken += result * Sum;
    2123             :     }  // End loop non-spatial dimensions.
    2124             : 
    2125          49 :     l_poDS->nPamFlags = nPamFlagsBackup;
    2126          49 : }
    2127             : 
    2128             : /************************************************************************/
    2129             : /*                             CheckData()                              */
    2130             : /************************************************************************/
    2131             : template <class T>
    2132        5732 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
    2133             :                                  size_t nTmpBlockXSize, size_t nTmpBlockYSize,
    2134             :                                  bool bCheckIsNan)
    2135             : {
    2136        5732 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2137             : 
    2138             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2139             :     // the data this is because partial blocks are not arranged the same way in
    2140             :     // netcdf and gdal.
    2141        5732 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2142             :     {
    2143           6 :         T *ptrWrite = static_cast<T *>(pImage);
    2144           6 :         T *ptrRead = static_cast<T *>(pImageNC);
    2145          29 :         for (size_t j = 0; j < nTmpBlockYSize;
    2146          23 :              j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
    2147             :         {
    2148          23 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
    2149             :         }
    2150             :     }
    2151             : 
    2152             :     // Is valid data checking needed or requested?
    2153        5732 :     if (bValidRangeValid || bCheckIsNan)
    2154             :     {
    2155        1265 :         T *ptrImage = static_cast<T *>(pImage);
    2156        2584 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2157             :         {
    2158             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2159        1319 :             size_t k = j * nBlockXSize;
    2160       96938 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2161             :             {
    2162             :                 // Check for nodata and nan.
    2163       95619 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2164        6301 :                     continue;
    2165       89318 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2166             :                 {
    2167        5737 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2168        5737 :                     continue;
    2169             :                 }
    2170             :                 // Check for valid_range.
    2171       83581 :                 if (bValidRangeValid)
    2172             :                 {
    2173       40986 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2174       40986 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2175       40983 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2176       40983 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2177             :                     {
    2178           4 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2179             :                     }
    2180             :                 }
    2181             :             }
    2182             :         }
    2183             :     }
    2184             : 
    2185             :     // If minimum longitude is > 180, subtract 360 from all.
    2186             :     // If not, disable checking for further calls (check just once).
    2187             :     // Only check first and last block elements since lon must be monotonic.
    2188        5732 :     const bool bIsSigned = std::numeric_limits<T>::is_signed;
    2189        5419 :     if (bCheckLongitude && bIsSigned &&
    2190           9 :         !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
    2191           8 :         !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
    2192        2714 :                     m_dfNoDataValue) &&
    2193           8 :         std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
    2194             :     {
    2195           0 :         T *ptrImage = static_cast<T *>(pImage);
    2196           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2197             :         {
    2198           0 :             size_t k = j * nBlockXSize;
    2199           0 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2200             :             {
    2201           0 :                 if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2202           0 :                     ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
    2203             :             }
    2204             :         }
    2205             :     }
    2206             :     else
    2207             :     {
    2208        5732 :         bCheckLongitude = false;
    2209             :     }
    2210        5732 : }
    2211             : 
    2212             : /************************************************************************/
    2213             : /*                             CheckDataCpx()                              */
    2214             : /************************************************************************/
    2215             : template <class T>
    2216          25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
    2217             :                                     size_t nTmpBlockXSize,
    2218             :                                     size_t nTmpBlockYSize, bool bCheckIsNan)
    2219             : {
    2220          25 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2221             : 
    2222             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2223             :     // the data this is because partial blocks are not arranged the same way in
    2224             :     // netcdf and gdal.
    2225          25 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2226             :     {
    2227           0 :         T *ptrWrite = static_cast<T *>(pImage);
    2228           0 :         T *ptrRead = static_cast<T *>(pImageNC);
    2229           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++,
    2230           0 :                     ptrWrite += (2 * nBlockXSize),
    2231           0 :                     ptrRead += (2 * nTmpBlockXSize))
    2232             :         {
    2233           0 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
    2234             :         }
    2235             :     }
    2236             : 
    2237             :     // Is valid data checking needed or requested?
    2238          25 :     if (bValidRangeValid || bCheckIsNan)
    2239             :     {
    2240           0 :         T *ptrImage = static_cast<T *>(pImage);
    2241           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2242             :         {
    2243             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2244           0 :             size_t k = 2 * j * nBlockXSize;
    2245           0 :             for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
    2246             :             {
    2247             :                 // Check for nodata and nan.
    2248           0 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2249           0 :                     continue;
    2250           0 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2251             :                 {
    2252           0 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2253           0 :                     continue;
    2254             :                 }
    2255             :                 // Check for valid_range.
    2256           0 :                 if (bValidRangeValid)
    2257             :                 {
    2258           0 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2259           0 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2260           0 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2261           0 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2262             :                     {
    2263           0 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2264             :                     }
    2265             :                 }
    2266             :             }
    2267             :         }
    2268             :     }
    2269          25 : }
    2270             : 
    2271             : /************************************************************************/
    2272             : /*                         FetchNetcdfChunk()                           */
    2273             : /************************************************************************/
    2274             : 
    2275        5757 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
    2276             :                                         void *pImage)
    2277             : {
    2278        5757 :     size_t start[MAX_NC_DIMS] = {};
    2279        5757 :     size_t edge[MAX_NC_DIMS] = {};
    2280             : 
    2281        5757 :     start[nBandXPos] = xstart;
    2282        5757 :     edge[nBandXPos] = nBlockXSize;
    2283        5757 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2284           6 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2285        5757 :     if (nBandYPos >= 0)
    2286             :     {
    2287        5753 :         start[nBandYPos] = ystart;
    2288        5753 :         edge[nBandYPos] = nBlockYSize;
    2289        5753 :         if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2290           4 :             edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2291             :     }
    2292        5757 :     const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
    2293             : 
    2294             : #ifdef NCDF_DEBUG
    2295             :     CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
    2296             :              start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
    2297             :              edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
    2298             : #endif
    2299             : 
    2300        5757 :     int nd = 0;
    2301        5757 :     nc_inq_varndims(cdfid, nZId, &nd);
    2302        5757 :     if (nd == 3)
    2303             :     {
    2304        1078 :         start[panBandZPos[0]] = nLevel;  // z
    2305        1078 :         edge[panBandZPos[0]] = 1;
    2306             :     }
    2307             : 
    2308             :     // Compute multidimention band position.
    2309             :     //
    2310             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2311             :     // if Data[2,3,4,x,y]
    2312             :     //
    2313             :     //  BandPos0 = (nBand) / (3*4)
    2314             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2315             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2316        5757 :     if (nd > 3)
    2317             :     {
    2318         160 :         int Sum = -1;
    2319         160 :         int Taken = 0;
    2320         480 :         for (int i = 0; i < nd - 2; i++)
    2321             :         {
    2322         320 :             if (i != nd - 2 - 1)
    2323             :             {
    2324         160 :                 Sum = 1;
    2325         320 :                 for (int j = i + 1; j < nd - 2; j++)
    2326             :                 {
    2327         160 :                     Sum *= panBandZLev[j];
    2328             :                 }
    2329         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2330         160 :                 edge[panBandZPos[i]] = 1;
    2331             :             }
    2332             :             else
    2333             :             {
    2334         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2335         160 :                 edge[panBandZPos[i]] = 1;
    2336             :             }
    2337         320 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2338             :         }
    2339             :     }
    2340             : 
    2341             :     // Make sure we are in data mode.
    2342        5757 :     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2343             : 
    2344             :     // If this block is not a full block in the x axis, we need to
    2345             :     // re-arrange the data because partial blocks are not arranged the
    2346             :     // same way in netcdf and gdal, so we first we read the netcdf data at
    2347             :     // the end of the gdal block buffer then re-arrange rows in CheckData().
    2348        5757 :     void *pImageNC = pImage;
    2349        5757 :     if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
    2350             :     {
    2351           6 :         pImageNC = static_cast<GByte *>(pImage) +
    2352           6 :                    ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
    2353          12 :                      edge[nBandXPos] * nYChunkSize) *
    2354           6 :                     GDALGetDataTypeSizeBytes(eDataType));
    2355             :     }
    2356             : 
    2357             :     // Read data according to type.
    2358             :     int status;
    2359        5757 :     if (eDataType == GDT_Byte)
    2360             :     {
    2361        3005 :         if (bSignedData)
    2362             :         {
    2363           0 :             status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2364             :                                        static_cast<signed char *>(pImageNC));
    2365           0 :             if (status == NC_NOERR)
    2366           0 :                 CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2367             :                                        nYChunkSize, false);
    2368             :         }
    2369             :         else
    2370             :         {
    2371        3005 :             status = nc_get_vara_uchar(cdfid, nZId, start, edge,
    2372             :                                        static_cast<unsigned char *>(pImageNC));
    2373        3005 :             if (status == NC_NOERR)
    2374        3005 :                 CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
    2375             :                                          nYChunkSize, false);
    2376             :         }
    2377             :     }
    2378        2752 :     else if (eDataType == GDT_Int8)
    2379             :     {
    2380          60 :         status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2381             :                                    static_cast<signed char *>(pImageNC));
    2382          60 :         if (status == NC_NOERR)
    2383          60 :             CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2384             :                                    nYChunkSize, false);
    2385             :     }
    2386        2692 :     else if (nc_datatype == NC_SHORT)
    2387             :     {
    2388         465 :         status = nc_get_vara_short(cdfid, nZId, start, edge,
    2389             :                                    static_cast<short *>(pImageNC));
    2390         465 :         if (status == NC_NOERR)
    2391             :         {
    2392         465 :             if (eDataType == GDT_Int16)
    2393             :             {
    2394         462 :                 CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
    2395             :                                   nYChunkSize, false);
    2396             :             }
    2397             :             else
    2398             :             {
    2399           3 :                 CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
    2400             :                                    nYChunkSize, false);
    2401             :             }
    2402             :         }
    2403             :     }
    2404        2227 :     else if (eDataType == GDT_Int32)
    2405             :     {
    2406             : #if SIZEOF_UNSIGNED_LONG == 4
    2407             :         status = nc_get_vara_long(cdfid, nZId, start, edge,
    2408             :                                   static_cast<long *>(pImageNC));
    2409             :         if (status == NC_NOERR)
    2410             :             CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2411             :                             false);
    2412             : #else
    2413         912 :         status = nc_get_vara_int(cdfid, nZId, start, edge,
    2414             :                                  static_cast<int *>(pImageNC));
    2415         912 :         if (status == NC_NOERR)
    2416         912 :             CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2417             :                            false);
    2418             : #endif
    2419             :     }
    2420        1315 :     else if (eDataType == GDT_Float32)
    2421             :     {
    2422        1178 :         status = nc_get_vara_float(cdfid, nZId, start, edge,
    2423             :                                    static_cast<float *>(pImageNC));
    2424        1178 :         if (status == NC_NOERR)
    2425        1178 :             CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2426             :                              true);
    2427             :     }
    2428         137 :     else if (eDataType == GDT_Float64)
    2429             :     {
    2430          86 :         status = nc_get_vara_double(cdfid, nZId, start, edge,
    2431             :                                     static_cast<double *>(pImageNC));
    2432          86 :         if (status == NC_NOERR)
    2433          86 :             CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2434             :                               true);
    2435             :     }
    2436          51 :     else if (eDataType == GDT_UInt16)
    2437             :     {
    2438           6 :         status = nc_get_vara_ushort(cdfid, nZId, start, edge,
    2439             :                                     static_cast<unsigned short *>(pImageNC));
    2440           6 :         if (status == NC_NOERR)
    2441           6 :             CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
    2442             :                                       nYChunkSize, false);
    2443             :     }
    2444          45 :     else if (eDataType == GDT_UInt32)
    2445             :     {
    2446           6 :         status = nc_get_vara_uint(cdfid, nZId, start, edge,
    2447             :                                   static_cast<unsigned int *>(pImageNC));
    2448           6 :         if (status == NC_NOERR)
    2449           6 :             CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
    2450             :                                     nYChunkSize, false);
    2451             :     }
    2452          39 :     else if (eDataType == GDT_Int64)
    2453             :     {
    2454           7 :         status = nc_get_vara_longlong(cdfid, nZId, start, edge,
    2455             :                                       static_cast<long long *>(pImageNC));
    2456           7 :         if (status == NC_NOERR)
    2457           7 :             CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
    2458             :                                     nYChunkSize, false);
    2459             :     }
    2460          32 :     else if (eDataType == GDT_UInt64)
    2461             :     {
    2462             :         status =
    2463           7 :             nc_get_vara_ulonglong(cdfid, nZId, start, edge,
    2464             :                                   static_cast<unsigned long long *>(pImageNC));
    2465           7 :         if (status == NC_NOERR)
    2466           7 :             CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
    2467             :                                      nYChunkSize, false);
    2468             :     }
    2469          25 :     else if (eDataType == GDT_CInt16)
    2470             :     {
    2471           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2472           0 :         if (status == NC_NOERR)
    2473           0 :             CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2474             :                                 false);
    2475             :     }
    2476          25 :     else if (eDataType == GDT_CInt32)
    2477             :     {
    2478           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2479           0 :         if (status == NC_NOERR)
    2480           0 :             CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2481             :                               false);
    2482             :     }
    2483          25 :     else if (eDataType == GDT_CFloat32)
    2484             :     {
    2485          20 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2486          20 :         if (status == NC_NOERR)
    2487          20 :             CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2488             :                                 false);
    2489             :     }
    2490           5 :     else if (eDataType == GDT_CFloat64)
    2491             :     {
    2492           5 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2493           5 :         if (status == NC_NOERR)
    2494           5 :             CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2495             :                                  false);
    2496             :     }
    2497             : 
    2498             :     else
    2499           0 :         status = NC_EBADTYPE;
    2500             : 
    2501        5757 :     if (status != NC_NOERR)
    2502             :     {
    2503           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2504             :                  "netCDF chunk fetch failed: #%d (%s)", status,
    2505             :                  nc_strerror(status));
    2506           0 :         return false;
    2507             :     }
    2508        5757 :     return true;
    2509             : }
    2510             : 
    2511             : /************************************************************************/
    2512             : /*                             IReadBlock()                             */
    2513             : /************************************************************************/
    2514             : 
    2515        5757 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2516             :                                     void *pImage)
    2517             : 
    2518             : {
    2519       11514 :     CPLMutexHolderD(&hNCMutex);
    2520             : 
    2521             :     // Locate X, Y and Z position in the array.
    2522             : 
    2523        5757 :     size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2524        5757 :     size_t ystart = 0;
    2525             : 
    2526             :     // Check y order.
    2527        5757 :     if (nBandYPos >= 0)
    2528             :     {
    2529        5753 :         auto poGDS = static_cast<netCDFDataset *>(poDS);
    2530        5753 :         if (poGDS->bBottomUp)
    2531             :         {
    2532        4838 :             if (nBlockYSize == 1)
    2533             :             {
    2534        4825 :                 ystart = nRasterYSize - 1 - nBlockYOff;
    2535             :             }
    2536             :             else
    2537             :             {
    2538             :                 // in GDAL space
    2539          13 :                 ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2540             :                 const size_t yend =
    2541          26 :                     std::min(ystart + nBlockYSize - 1,
    2542          13 :                              static_cast<size_t>(nRasterYSize - 1));
    2543             :                 // in netCDF space
    2544          13 :                 const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
    2545          13 :                 const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
    2546          13 :                 const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
    2547          13 :                 const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
    2548             : 
    2549             :                 const auto firstKey = netCDFDataset::ChunkKey(
    2550          13 :                     nBlockXOff, nFirstChunkBlock, nBand);
    2551             :                 const auto secondKey =
    2552          13 :                     netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
    2553             : 
    2554             :                 // Retrieve data from the one or 2 needed netCDF chunks
    2555          13 :                 std::shared_ptr<std::vector<GByte>> firstChunk;
    2556          13 :                 std::shared_ptr<std::vector<GByte>> secondChunk;
    2557          13 :                 if (poGDS->poChunkCache)
    2558             :                 {
    2559          13 :                     poGDS->poChunkCache->tryGet(firstKey, firstChunk);
    2560          13 :                     if (firstKey != secondKey)
    2561           6 :                         poGDS->poChunkCache->tryGet(secondKey, secondChunk);
    2562             :                 }
    2563             :                 const size_t nChunkLineSize =
    2564          13 :                     static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
    2565          13 :                     nBlockXSize;
    2566          13 :                 const size_t nChunkSize = nChunkLineSize * nBlockYSize;
    2567          13 :                 if (!firstChunk)
    2568             :                 {
    2569          11 :                     firstChunk.reset(new std::vector<GByte>(nChunkSize));
    2570          11 :                     if (!FetchNetcdfChunk(xstart,
    2571          11 :                                           nFirstChunkBlock * nBlockYSize,
    2572          11 :                                           firstChunk.get()->data()))
    2573           0 :                         return CE_Failure;
    2574          11 :                     if (poGDS->poChunkCache)
    2575          11 :                         poGDS->poChunkCache->insert(firstKey, firstChunk);
    2576             :                 }
    2577          13 :                 if (!secondChunk && firstKey != secondKey)
    2578             :                 {
    2579           2 :                     secondChunk.reset(new std::vector<GByte>(nChunkSize));
    2580           2 :                     if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
    2581           2 :                                           secondChunk.get()->data()))
    2582           0 :                         return CE_Failure;
    2583           2 :                     if (poGDS->poChunkCache)
    2584           2 :                         poGDS->poChunkCache->insert(secondKey, secondChunk);
    2585             :                 }
    2586             : 
    2587             :                 // Assemble netCDF chunks into GDAL block
    2588          13 :                 GByte *pabyImage = static_cast<GByte *>(pImage);
    2589          13 :                 const size_t nFirstChunkBlockLine =
    2590          13 :                     nFirstChunkBlock * nBlockYSize;
    2591          13 :                 const size_t nLastChunkBlockLine =
    2592          13 :                     nLastChunkBlock * nBlockYSize;
    2593         146 :                 for (size_t iLine = ystart; iLine <= yend; iLine++)
    2594             :                 {
    2595         133 :                     const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
    2596         133 :                     const size_t nChunkY = nLineFromBottom / nBlockYSize;
    2597         133 :                     if (nChunkY == nFirstChunkBlock)
    2598             :                     {
    2599         121 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2600         121 :                                firstChunk.get()->data() +
    2601         121 :                                    (nLineFromBottom - nFirstChunkBlockLine) *
    2602             :                                        nChunkLineSize,
    2603             :                                nChunkLineSize);
    2604             :                     }
    2605             :                     else
    2606             :                     {
    2607          12 :                         CPLAssert(nChunkY == nLastChunkBlock);
    2608          12 :                         assert(secondChunk);
    2609          12 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2610          12 :                                secondChunk.get()->data() +
    2611          12 :                                    (nLineFromBottom - nLastChunkBlockLine) *
    2612             :                                        nChunkLineSize,
    2613             :                                nChunkLineSize);
    2614             :                     }
    2615             :                 }
    2616          13 :                 return CE_None;
    2617             :             }
    2618             :         }
    2619             :         else
    2620             :         {
    2621         915 :             ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2622             :         }
    2623             :     }
    2624             : 
    2625        5744 :     return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
    2626             : }
    2627             : 
    2628             : /************************************************************************/
    2629             : /*                             IWriteBlock()                            */
    2630             : /************************************************************************/
    2631             : 
    2632        6401 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
    2633             :                                      void *pImage)
    2634             : {
    2635       12802 :     CPLMutexHolderD(&hNCMutex);
    2636             : 
    2637             : #ifdef NCDF_DEBUG
    2638             :     if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
    2639             :         CPLDebug("GDAL_netCDF",
    2640             :                  "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
    2641             :                  nBlockXOff, nBlockYOff, nBand);
    2642             : #endif
    2643             : 
    2644        6401 :     int nd = 0;
    2645        6401 :     nc_inq_varndims(cdfid, nZId, &nd);
    2646             : 
    2647             :     // Locate X, Y and Z position in the array.
    2648             : 
    2649             :     size_t start[MAX_NC_DIMS];
    2650        6401 :     memset(start, 0, sizeof(start));
    2651        6401 :     start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2652             : 
    2653             :     // check y order.
    2654        6401 :     if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
    2655             :     {
    2656        6377 :         if (nBlockYSize == 1)
    2657             :         {
    2658        6377 :             start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
    2659             :         }
    2660             :         else
    2661             :         {
    2662           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2663             :                      "nBlockYSize = %d, only 1 supported when "
    2664             :                      "writing bottom-up dataset",
    2665             :                      nBlockYSize);
    2666           0 :             return CE_Failure;
    2667             :         }
    2668             :     }
    2669             :     else
    2670             :     {
    2671          24 :         start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y
    2672             :     }
    2673             : 
    2674        6401 :     size_t edge[MAX_NC_DIMS] = {};
    2675             : 
    2676        6401 :     edge[nBandXPos] = nBlockXSize;
    2677        6401 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2678           0 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2679        6401 :     edge[nBandYPos] = nBlockYSize;
    2680        6401 :     if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2681           0 :         edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2682             : 
    2683        6401 :     if (nd == 3)
    2684             :     {
    2685         610 :         start[panBandZPos[0]] = nLevel;  // z
    2686         610 :         edge[panBandZPos[0]] = 1;
    2687             :     }
    2688             : 
    2689             :     // Compute multidimention band position.
    2690             :     //
    2691             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2692             :     // if Data[2,3,4,x,y]
    2693             :     //
    2694             :     //  BandPos0 = (nBand) / (3*4)
    2695             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2696             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2697        6401 :     if (nd > 3)
    2698             :     {
    2699         178 :         int Sum = -1;
    2700         178 :         int Taken = 0;
    2701         534 :         for (int i = 0; i < nd - 2; i++)
    2702             :         {
    2703         356 :             if (i != nd - 2 - 1)
    2704             :             {
    2705         178 :                 Sum = 1;
    2706         356 :                 for (int j = i + 1; j < nd - 2; j++)
    2707             :                 {
    2708         178 :                     Sum *= panBandZLev[j];
    2709             :                 }
    2710         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2711         178 :                 edge[panBandZPos[i]] = 1;
    2712             :             }
    2713             :             else
    2714             :             {
    2715         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2716         178 :                 edge[panBandZPos[i]] = 1;
    2717             :             }
    2718         356 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2719             :         }
    2720             :     }
    2721             : 
    2722             :     // Make sure we are in data mode.
    2723        6401 :     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2724             : 
    2725             :     // Copy data according to type.
    2726        6401 :     int status = 0;
    2727        6401 :     if (eDataType == GDT_Byte)
    2728             :     {
    2729        5842 :         if (bSignedData)
    2730           0 :             status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2731             :                                        static_cast<signed char *>(pImage));
    2732             :         else
    2733        5842 :             status = nc_put_vara_uchar(cdfid, nZId, start, edge,
    2734             :                                        static_cast<unsigned char *>(pImage));
    2735             :     }
    2736         559 :     else if (eDataType == GDT_Int8)
    2737             :     {
    2738          40 :         status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2739             :                                    static_cast<signed char *>(pImage));
    2740             :     }
    2741         519 :     else if (nc_datatype == NC_SHORT)
    2742             :     {
    2743         101 :         status = nc_put_vara_short(cdfid, nZId, start, edge,
    2744             :                                    static_cast<short *>(pImage));
    2745             :     }
    2746         418 :     else if (eDataType == GDT_Int32)
    2747             :     {
    2748         210 :         status = nc_put_vara_int(cdfid, nZId, start, edge,
    2749             :                                  static_cast<int *>(pImage));
    2750             :     }
    2751         208 :     else if (eDataType == GDT_Float32)
    2752             :     {
    2753         128 :         status = nc_put_vara_float(cdfid, nZId, start, edge,
    2754             :                                    static_cast<float *>(pImage));
    2755             :     }
    2756          80 :     else if (eDataType == GDT_Float64)
    2757             :     {
    2758          50 :         status = nc_put_vara_double(cdfid, nZId, start, edge,
    2759             :                                     static_cast<double *>(pImage));
    2760             :     }
    2761          30 :     else if (eDataType == GDT_UInt16 &&
    2762          12 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2763             :     {
    2764          12 :         status = nc_put_vara_ushort(cdfid, nZId, start, edge,
    2765             :                                     static_cast<unsigned short *>(pImage));
    2766             :     }
    2767          18 :     else if (eDataType == GDT_UInt32 &&
    2768          12 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2769             :     {
    2770          12 :         status = nc_put_vara_uint(cdfid, nZId, start, edge,
    2771             :                                   static_cast<unsigned int *>(pImage));
    2772             :     }
    2773           6 :     else if (eDataType == GDT_UInt64 &&
    2774           3 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2775             :     {
    2776           3 :         status =
    2777           3 :             nc_put_vara_ulonglong(cdfid, nZId, start, edge,
    2778             :                                   static_cast<unsigned long long *>(pImage));
    2779             :     }
    2780           3 :     else if (eDataType == GDT_Int64 &&
    2781           3 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2782             :     {
    2783           3 :         status = nc_put_vara_longlong(cdfid, nZId, start, edge,
    2784             :                                       static_cast<long long *>(pImage));
    2785             :     }
    2786             :     else
    2787             :     {
    2788           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2789             :                  "The NetCDF driver does not support GDAL data type %d",
    2790           0 :                  eDataType);
    2791           0 :         status = NC_EBADTYPE;
    2792             :     }
    2793        6401 :     NCDF_ERR(status);
    2794             : 
    2795        6401 :     if (status != NC_NOERR)
    2796             :     {
    2797           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2798             :                  "netCDF scanline write failed: %s", nc_strerror(status));
    2799           0 :         return CE_Failure;
    2800             :     }
    2801             : 
    2802        6401 :     return CE_None;
    2803             : }
    2804             : 
    2805             : /************************************************************************/
    2806             : /* ==================================================================== */
    2807             : /*                              netCDFDataset                           */
    2808             : /* ==================================================================== */
    2809             : /************************************************************************/
    2810             : 
    2811             : /************************************************************************/
    2812             : /*                           netCDFDataset()                            */
    2813             : /************************************************************************/
    2814             : 
    2815        1004 : netCDFDataset::netCDFDataset()
    2816             :     :
    2817             : // Basic dataset vars.
    2818             : #ifdef ENABLE_NCDUMP
    2819             :       bFileToDestroyAtClosing(false),
    2820             : #endif
    2821             :       cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
    2822             :       papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
    2823             :       bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
    2824             :       pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
    2825        1004 :       eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
    2826        1004 :       GeometryScribe(vcdf, this->generateLogName()),
    2827        1004 :       FieldScribe(vcdf, this->generateLogName()),
    2828        2008 :       bufManager(CPLGetUsablePhysicalRAM() / 5),
    2829             : 
    2830             :       // projection/GT.
    2831             :       nXDimID(-1), nYDimID(-1), bIsProjected(false),
    2832             :       bIsGeographic(false),  // Can be not projected, and also not geographic
    2833             :       // State vars.
    2834             :       bDefineMode(true), bAddedGridMappingRef(false),
    2835             : 
    2836             :       // Create vars.
    2837             :       papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
    2838             :       nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
    2839        3012 :       bSignedData(true)
    2840             : {
    2841        1004 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2842             : 
    2843             :     // Projection/GT.
    2844        1004 :     m_adfGeoTransform[0] = 0.0;
    2845        1004 :     m_adfGeoTransform[1] = 1.0;
    2846        1004 :     m_adfGeoTransform[2] = 0.0;
    2847        1004 :     m_adfGeoTransform[3] = 0.0;
    2848        1004 :     m_adfGeoTransform[4] = 0.0;
    2849        1004 :     m_adfGeoTransform[5] = 1.0;
    2850             : 
    2851             :     // Set buffers
    2852        1004 :     bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
    2853        1004 :     bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
    2854        1004 : }
    2855             : 
    2856             : /************************************************************************/
    2857             : /*                           ~netCDFDataset()                           */
    2858             : /************************************************************************/
    2859             : 
    2860        1934 : netCDFDataset::~netCDFDataset()
    2861             : 
    2862             : {
    2863        1004 :     netCDFDataset::Close();
    2864        1934 : }
    2865             : 
    2866             : /************************************************************************/
    2867             : /*                              Close()                                 */
    2868             : /************************************************************************/
    2869             : 
    2870        1750 : CPLErr netCDFDataset::Close()
    2871             : {
    2872        1750 :     CPLErr eErr = CE_None;
    2873        1750 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    2874             :     {
    2875        2008 :         CPLMutexHolderD(&hNCMutex);
    2876             : 
    2877             : #ifdef NCDF_DEBUG
    2878             :         CPLDebug("GDAL_netCDF",
    2879             :                  "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
    2880             :                  osFilename.c_str());
    2881             : #endif
    2882             : 
    2883             :         // Write data related to geotransform
    2884        1235 :         if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
    2885         231 :             (m_bHasProjection || m_bHasGeoTransform))
    2886             :         {
    2887             :             // Ensure projection is written if GeoTransform OR Projection are
    2888             :             // missing.
    2889          37 :             if (!m_bAddedProjectionVarsDefs)
    2890             :             {
    2891           2 :                 AddProjectionVars(true, nullptr, nullptr);
    2892             :             }
    2893          37 :             AddProjectionVars(false, nullptr, nullptr);
    2894             :         }
    2895             : 
    2896        1004 :         if (netCDFDataset::FlushCache(true) != CE_None)
    2897           0 :             eErr = CE_Failure;
    2898             : 
    2899        1004 :         if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
    2900           0 :             eErr = CE_Failure;
    2901             : 
    2902        1006 :         for (size_t i = 0; i < apoVectorDatasets.size(); i++)
    2903           2 :             delete apoVectorDatasets[i];
    2904             : 
    2905             :         // Make sure projection variable is written to band variable.
    2906        1004 :         if (GetAccess() == GA_Update && !bAddedGridMappingRef)
    2907             :         {
    2908         248 :             if (!AddGridMappingRef())
    2909           0 :                 eErr = CE_Failure;
    2910             :         }
    2911             : 
    2912        1004 :         CSLDestroy(papszMetadata);
    2913        1004 :         CSLDestroy(papszSubDatasets);
    2914        1004 :         CSLDestroy(papszCreationOptions);
    2915             : 
    2916        1004 :         CPLFree(pszCFProjection);
    2917             : 
    2918        1004 :         if (cdfid > 0)
    2919             :         {
    2920             : #ifdef NCDF_DEBUG
    2921             :             CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
    2922             : #endif
    2923         658 :             int status = GDAL_nc_close(cdfid);
    2924             : #ifdef ENABLE_UFFD
    2925         658 :             NETCDF_UFFD_UNMAP(pCtx);
    2926             : #endif
    2927         658 :             NCDF_ERR(status);
    2928         658 :             if (status != NC_NOERR)
    2929           0 :                 eErr = CE_Failure;
    2930             :         }
    2931             : 
    2932        1004 :         if (fpVSIMEM)
    2933          15 :             VSIFCloseL(fpVSIMEM);
    2934             : 
    2935             : #ifdef ENABLE_NCDUMP
    2936        1004 :         if (bFileToDestroyAtClosing)
    2937           0 :             VSIUnlink(osFilename);
    2938             : #endif
    2939             : 
    2940        1004 :         if (GDALPamDataset::Close() != CE_None)
    2941           0 :             eErr = CE_Failure;
    2942             :     }
    2943        1750 :     return eErr;
    2944             : }
    2945             : 
    2946             : /************************************************************************/
    2947             : /*                            SetDefineMode()                           */
    2948             : /************************************************************************/
    2949       14226 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
    2950             : {
    2951             :     // Do nothing if already in new define mode
    2952             :     // or if dataset is in read-only mode or if dataset is true NC4 dataset.
    2953       14785 :     if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
    2954         559 :         eFormat == NCDF_FORMAT_NC4)
    2955       13814 :         return true;
    2956             : 
    2957         412 :     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
    2958         412 :              static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
    2959             : 
    2960         412 :     bDefineMode = bNewDefineMode;
    2961             : 
    2962             :     int status;
    2963         412 :     if (bDefineMode)
    2964         143 :         status = nc_redef(cdfid);
    2965             :     else
    2966         269 :         status = nc_enddef(cdfid);
    2967             : 
    2968         412 :     NCDF_ERR(status);
    2969         412 :     return status == NC_NOERR;
    2970             : }
    2971             : 
    2972             : /************************************************************************/
    2973             : /*                      GetMetadataDomainList()                         */
    2974             : /************************************************************************/
    2975             : 
    2976          27 : char **netCDFDataset::GetMetadataDomainList()
    2977             : {
    2978          27 :     char **papszDomains = BuildMetadataDomainList(
    2979             :         GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
    2980          28 :     for (const auto &kv : m_oMapDomainToJSon)
    2981           1 :         papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
    2982          27 :     return papszDomains;
    2983             : }
    2984             : 
    2985             : /************************************************************************/
    2986             : /*                            GetMetadata()                             */
    2987             : /************************************************************************/
    2988         368 : char **netCDFDataset::GetMetadata(const char *pszDomain)
    2989             : {
    2990         368 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
    2991          38 :         return papszSubDatasets;
    2992             : 
    2993         330 :     if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
    2994             :     {
    2995           1 :         auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
    2996           1 :         if (iter != m_oMapDomainToJSon.end())
    2997           1 :             return iter->second.List();
    2998             :     }
    2999             : 
    3000         329 :     return GDALDataset::GetMetadata(pszDomain);
    3001             : }
    3002             : 
    3003             : /************************************************************************/
    3004             : /*                        SetMetadataItem()                             */
    3005             : /************************************************************************/
    3006             : 
    3007          43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    3008             :                                       const char *pszDomain)
    3009             : {
    3010          85 :     if (GetAccess() == GA_Update &&
    3011          85 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    3012             :     {
    3013          42 :         std::string osName(pszName);
    3014             : 
    3015             :         // Same logic as in CopyMetadata()
    3016          42 :         if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
    3017           8 :             osName = osName.substr(strlen("NC_GLOBAL#"));
    3018          34 :         else if (strchr(osName.c_str(), '#') == nullptr)
    3019           5 :             osName = "GDAL_" + osName;
    3020             : 
    3021          84 :         if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
    3022          42 :             strchr(osName.c_str(), '#') != nullptr)
    3023             :         {
    3024             :             // do nothing
    3025          29 :             return CE_None;
    3026             :         }
    3027             :         else
    3028             :         {
    3029          13 :             SetDefineMode(true);
    3030             : 
    3031          13 :             if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
    3032          13 :                 return CE_Failure;
    3033             :         }
    3034             :     }
    3035             : 
    3036           1 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    3037             : }
    3038             : 
    3039             : /************************************************************************/
    3040             : /*                          SetMetadata()                               */
    3041             : /************************************************************************/
    3042             : 
    3043           8 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
    3044             : {
    3045          13 :     if (GetAccess() == GA_Update &&
    3046           5 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    3047             :     {
    3048             :         // We don't handle metadata item removal for now
    3049          50 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    3050             :              ++papszIter)
    3051             :         {
    3052          42 :             char *pszName = nullptr;
    3053          42 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    3054          42 :             if (pszName && pszValue)
    3055          42 :                 SetMetadataItem(pszName, pszValue);
    3056          42 :             CPLFree(pszName);
    3057             :         }
    3058           8 :         return CE_None;
    3059             :     }
    3060           0 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    3061             : }
    3062             : 
    3063             : /************************************************************************/
    3064             : /*                          GetSpatialRef()                             */
    3065             : /************************************************************************/
    3066             : 
    3067         184 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
    3068             : {
    3069         184 :     if (m_bHasProjection)
    3070          74 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3071             : 
    3072         110 :     return GDALPamDataset::GetSpatialRef();
    3073             : }
    3074             : 
    3075             : /************************************************************************/
    3076             : /*                           FetchCopyParam()                            */
    3077             : /************************************************************************/
    3078             : 
    3079         436 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
    3080             :                                      const char *pszParam, double dfDefault,
    3081             :                                      bool *pbFound)
    3082             : 
    3083             : {
    3084             :     char *pszTemp =
    3085         436 :         CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
    3086         436 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
    3087         436 :     CPLFree(pszTemp);
    3088             : 
    3089         436 :     if (pbFound)
    3090             :     {
    3091         436 :         *pbFound = (pszValue != nullptr);
    3092             :     }
    3093             : 
    3094         436 :     if (pszValue)
    3095             :     {
    3096           0 :         return CPLAtofM(pszValue);
    3097             :     }
    3098             : 
    3099         436 :     return dfDefault;
    3100             : }
    3101             : 
    3102             : /************************************************************************/
    3103             : /*                           FetchStandardParallels()                   */
    3104             : /************************************************************************/
    3105             : 
    3106             : std::vector<std::string>
    3107           0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
    3108             : {
    3109             :     // cf-1.0 tags
    3110           0 :     const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
    3111             : 
    3112           0 :     std::vector<std::string> ret;
    3113           0 :     if (pszValue != nullptr)
    3114             :     {
    3115           0 :         CPLStringList aosValues;
    3116           0 :         if (pszValue[0] != '{' &&
    3117           0 :             CPLString(pszValue).Trim().find(' ') != std::string::npos)
    3118             :         {
    3119             :             // Some files like
    3120             :             // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
    3121             :             // do not use standard formatting for arrays, but just space
    3122             :             // separated syntax
    3123           0 :             aosValues = CSLTokenizeString2(pszValue, " ", 0);
    3124             :         }
    3125             :         else
    3126             :         {
    3127           0 :             aosValues = NCDFTokenizeArray(pszValue);
    3128             :         }
    3129           0 :         for (int i = 0; i < aosValues.size(); i++)
    3130             :         {
    3131           0 :             ret.push_back(aosValues[i]);
    3132             :         }
    3133             :     }
    3134             :     // Try gdal tags.
    3135             :     else
    3136             :     {
    3137           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
    3138             : 
    3139           0 :         if (pszValue != nullptr)
    3140           0 :             ret.push_back(pszValue);
    3141             : 
    3142           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
    3143             : 
    3144           0 :         if (pszValue != nullptr)
    3145           0 :             ret.push_back(pszValue);
    3146             :     }
    3147             : 
    3148           0 :     return ret;
    3149             : }
    3150             : 
    3151             : /************************************************************************/
    3152             : /*                           FetchAttr()                                */
    3153             : /************************************************************************/
    3154             : 
    3155        3703 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
    3156             :                                      const char *pszAttr)
    3157             : 
    3158             : {
    3159        3703 :     char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
    3160        3703 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
    3161        3703 :     CPLFree(pszKey);
    3162        3703 :     return pszValue;
    3163             : }
    3164             : 
    3165        2454 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
    3166             :                                      const char *pszAttr)
    3167             : 
    3168             : {
    3169        2454 :     char *pszVarFullName = nullptr;
    3170        2454 :     NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
    3171        2454 :     const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
    3172        2454 :     CPLFree(pszVarFullName);
    3173        2454 :     return pszValue;
    3174             : }
    3175             : 
    3176             : /************************************************************************/
    3177             : /*                       IsDifferenceBelow()                            */
    3178             : /************************************************************************/
    3179             : 
    3180        1085 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
    3181             : {
    3182        1085 :     const double dfAbsDiff = fabs(dfA - dfB);
    3183        1085 :     return dfAbsDiff <= dfError;
    3184             : }
    3185             : 
    3186             : /************************************************************************/
    3187             : /*                      SetProjectionFromVar()                          */
    3188             : /************************************************************************/
    3189         514 : void netCDFDataset::SetProjectionFromVar(
    3190             :     int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
    3191             :     std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
    3192             :     std::vector<std::string> *paosRemovedMDItems)
    3193             : {
    3194         514 :     bool bGotGeogCS = false;
    3195         514 :     bool bGotCfSRS = false;
    3196         514 :     bool bGotCfWktSRS = false;
    3197         514 :     bool bGotGdalSRS = false;
    3198         514 :     bool bGotCfGT = false;
    3199         514 :     bool bGotGdalGT = false;
    3200             : 
    3201             :     // These values from CF metadata.
    3202         514 :     OGRSpatialReference oSRS;
    3203         514 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3204         514 :     size_t xdim = nRasterXSize;
    3205         514 :     size_t ydim = nRasterYSize;
    3206             : 
    3207             :     // These values from GDAL metadata.
    3208         514 :     const char *pszWKT = nullptr;
    3209         514 :     const char *pszGeoTransform = nullptr;
    3210             : 
    3211         514 :     netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.
    3212             : 
    3213         514 :     CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
    3214             :              nVarId);
    3215             : 
    3216             :     // Get x/y range information.
    3217             : 
    3218             :     // Temp variables to use in SetGeoTransform() and SetProjection().
    3219         514 :     double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
    3220             : 
    3221             :     // Look for grid_mapping metadata.
    3222         514 :     const char *pszValue = pszGivenGM;
    3223         514 :     CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may
    3224             :         // point to it
    3225         514 :     if (pszValue == nullptr)
    3226             :     {
    3227         471 :         pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
    3228         471 :         if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
    3229             :         {
    3230             :             // Expanded form of grid_mapping
    3231             :             // e.g. "crsOSGB: x y crsWGS84: lat lon"
    3232             :             // Pickup the grid_mapping whose coordinates are dimensions of the
    3233             :             // variable
    3234           6 :             CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
    3235           3 :             if ((aosTokens.size() % 3) == 0)
    3236             :             {
    3237           3 :                 for (int i = 0; i < aosTokens.size() / 3; i++)
    3238             :                 {
    3239           3 :                     if (CSLFindString(poDS->papszDimName,
    3240           9 :                                       aosTokens[3 * i + 1]) >= 0 &&
    3241           3 :                         CSLFindString(poDS->papszDimName,
    3242           3 :                                       aosTokens[3 * i + 2]) >= 0)
    3243             :                     {
    3244           3 :                         osTmpGridMapping = aosTokens[3 * i];
    3245           6 :                         if (!osTmpGridMapping.empty() &&
    3246           3 :                             osTmpGridMapping.back() == ':')
    3247             :                         {
    3248           3 :                             osTmpGridMapping.resize(osTmpGridMapping.size() -
    3249             :                                                     1);
    3250             :                         }
    3251           3 :                         pszValue = osTmpGridMapping.c_str();
    3252           3 :                         break;
    3253             :                     }
    3254             :                 }
    3255             :             }
    3256             :         }
    3257             :     }
    3258         514 :     char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
    3259             : 
    3260         514 :     if (!EQUAL(pszGridMappingValue, ""))
    3261             :     {
    3262             :         // Read grid_mapping metadata.
    3263         219 :         int nProjGroupID = -1;
    3264         219 :         int nProjVarID = -1;
    3265         219 :         if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
    3266         219 :                            &nProjVarID) == CE_None)
    3267             :         {
    3268         218 :             poDS->ReadAttributes(nProjGroupID, nProjVarID);
    3269             : 
    3270             :             // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
    3271         218 :             CPLFree(pszGridMappingValue);
    3272         218 :             pszGridMappingValue = nullptr;
    3273         218 :             NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
    3274         218 :             if (pszGridMappingValue)
    3275             :             {
    3276         218 :                 CPLDebug("GDAL_netCDF", "got grid_mapping %s",
    3277             :                          pszGridMappingValue);
    3278         218 :                 pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
    3279         218 :                 if (!pszWKT)
    3280             :                 {
    3281          34 :                     pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
    3282             :                 }
    3283             :                 else
    3284             :                 {
    3285         184 :                     bGotGdalSRS = true;
    3286         184 :                     CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
    3287             :                 }
    3288         218 :                 if (pszWKT)
    3289             :                 {
    3290         188 :                     if (!bGotGdalSRS)
    3291             :                     {
    3292           4 :                         bGotCfWktSRS = true;
    3293           4 :                         CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3294             :                     }
    3295         188 :                     if (returnProjStr != nullptr)
    3296             :                     {
    3297          41 :                         (*returnProjStr) = std::string(pszWKT);
    3298             :                     }
    3299             :                     else
    3300             :                     {
    3301         147 :                         m_bAddedProjectionVarsDefs = true;
    3302         147 :                         m_bAddedProjectionVarsData = true;
    3303         294 :                         OGRSpatialReference oSRSTmp;
    3304         147 :                         oSRSTmp.SetAxisMappingStrategy(
    3305             :                             OAMS_TRADITIONAL_GIS_ORDER);
    3306         147 :                         oSRSTmp.importFromWkt(pszWKT);
    3307         147 :                         SetSpatialRefNoUpdate(&oSRSTmp);
    3308             :                     }
    3309             :                     pszGeoTransform =
    3310         188 :                         FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
    3311             :                 }
    3312             :             }
    3313             :             else
    3314             :             {
    3315           0 :                 pszGridMappingValue = CPLStrdup("");
    3316             :             }
    3317             :         }
    3318             :     }
    3319             : 
    3320             :     // Get information about the file.
    3321             :     //
    3322             :     // Was this file created by the GDAL netcdf driver?
    3323             :     // Was this file created by the newer (CF-conformant) driver?
    3324             :     //
    3325             :     // 1) If GDAL netcdf metadata is set, and version >= 1.9,
    3326             :     //    it was created with the new driver
    3327             :     // 2) Else, if spatial_ref and GeoTransform are present in the
    3328             :     //    grid_mapping variable, it was created by the old driver
    3329         514 :     pszValue = FetchAttr("NC_GLOBAL", "GDAL");
    3330             : 
    3331         514 :     if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
    3332             :     {
    3333         241 :         bIsGdalFile = true;
    3334         241 :         bIsGdalCfFile = true;
    3335             :     }
    3336         273 :     else if (pszWKT != nullptr && pszGeoTransform != nullptr)
    3337             :     {
    3338          17 :         bIsGdalFile = true;
    3339          17 :         bIsGdalCfFile = false;
    3340             :     }
    3341             : 
    3342             :     // Set default bottom-up default value.
    3343             :     // Y axis dimension and absence of GT can modify this value.
    3344             :     // Override with Config option GDAL_NETCDF_BOTTOMUP.
    3345             : 
    3346             :     // New driver is bottom-up by default.
    3347         514 :     if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
    3348          19 :         poDS->bBottomUp = false;
    3349             :     else
    3350         495 :         poDS->bBottomUp = true;
    3351             : 
    3352         514 :     CPLDebug("GDAL_netCDF",
    3353             :              "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
    3354         514 :              static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
    3355         514 :              static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
    3356             : 
    3357             :     // Read projection coordinates.
    3358             : 
    3359         514 :     int nGroupDimXID = -1;
    3360         514 :     int nVarDimXID = -1;
    3361         514 :     int nGroupDimYID = -1;
    3362         514 :     int nVarDimYID = -1;
    3363         514 :     if (sg != nullptr)
    3364             :     {
    3365          43 :         nGroupDimXID = sg->get_ncID();
    3366          43 :         nGroupDimYID = sg->get_ncID();
    3367          43 :         nVarDimXID = sg->getNodeCoordVars()[0];
    3368          43 :         nVarDimYID = sg->getNodeCoordVars()[1];
    3369             :     }
    3370             : 
    3371         514 :     if (!bReadSRSOnly)
    3372             :     {
    3373         348 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
    3374             :                        &nVarDimXID);
    3375         348 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
    3376             :                        &nVarDimYID);
    3377             :         // TODO: if above resolving fails we should also search for coordinate
    3378             :         // variables without same name than dimension using the same resolving
    3379             :         // logic. This should handle for example NASA Ocean Color L2 products.
    3380             : 
    3381             :         const bool bIgnoreXYAxisNameChecks =
    3382         696 :             CPLTestBool(CSLFetchNameValueDef(
    3383         348 :                 papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
    3384             :                 CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
    3385         348 :                                    "NO"))) ||
    3386             :             // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
    3387             :             // and transform attributes
    3388         348 :             (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
    3389         696 :              FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
    3390         347 :             FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
    3391             : 
    3392             :         // Check that they are 1D or 2D variables
    3393         348 :         if (nVarDimXID >= 0)
    3394             :         {
    3395         254 :             int ndims = -1;
    3396         254 :             nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
    3397         254 :             if (ndims == 0 || ndims > 2)
    3398           0 :                 nVarDimXID = -1;
    3399         254 :             else if (!bIgnoreXYAxisNameChecks)
    3400             :             {
    3401         252 :                 if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    3402         162 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
    3403             :                     // In case of inversion of X/Y
    3404         446 :                     !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
    3405          32 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
    3406             :                 {
    3407             :                     char szVarNameX[NC_MAX_NAME + 1];
    3408          32 :                     CPL_IGNORE_RET_VAL(
    3409          32 :                         nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    3410          32 :                     if (!(ndims == 1 &&
    3411          31 :                           (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
    3412          30 :                            EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
    3413             :                     {
    3414          31 :                         CPLDebug(
    3415             :                             "netCDF",
    3416             :                             "Georeferencing ignored due to non-specific "
    3417             :                             "enough X axis name. "
    3418             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3419             :                             "as configuration option to bypass this check");
    3420          31 :                         nVarDimXID = -1;
    3421             :                     }
    3422             :                 }
    3423             :             }
    3424             :         }
    3425             : 
    3426         348 :         if (nVarDimYID >= 0)
    3427             :         {
    3428         256 :             int ndims = -1;
    3429         256 :             nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
    3430         256 :             if (ndims == 0 || ndims > 2)
    3431           1 :                 nVarDimYID = -1;
    3432         255 :             else if (!bIgnoreXYAxisNameChecks)
    3433             :             {
    3434         253 :                 if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
    3435         163 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
    3436             :                     // In case of inversion of X/Y
    3437         449 :                     !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    3438          33 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
    3439             :                 {
    3440             :                     char szVarNameY[NC_MAX_NAME + 1];
    3441          33 :                     CPL_IGNORE_RET_VAL(
    3442          33 :                         nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    3443          33 :                     if (!(ndims == 1 &&
    3444          33 :                           (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
    3445          32 :                            EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
    3446             :                     {
    3447          32 :                         CPLDebug(
    3448             :                             "netCDF",
    3449             :                             "Georeferencing ignored due to non-specific "
    3450             :                             "enough Y axis name. "
    3451             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3452             :                             "as configuration option to bypass this check");
    3453          32 :                         nVarDimYID = -1;
    3454             :                     }
    3455             :                 }
    3456             :             }
    3457             :         }
    3458             : 
    3459         348 :         if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
    3460             :         {
    3461           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3462             :                      "1-pixel width/height files not supported, "
    3463             :                      "xdim: %ld ydim: %ld",
    3464             :                      static_cast<long>(xdim), static_cast<long>(ydim));
    3465           0 :             nVarDimXID = -1;
    3466           0 :             nVarDimYID = -1;
    3467             :         }
    3468             :     }
    3469             : 
    3470         514 :     const char *pszUnits = nullptr;
    3471         514 :     if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3472             :     {
    3473         266 :         const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
    3474         266 :         const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
    3475             :         // Normalize degrees_east/degrees_north to degrees
    3476             :         // Cf https://github.com/OSGeo/gdal/issues/11009
    3477         266 :         if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
    3478          79 :             pszUnitsX = "degrees";
    3479         266 :         if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
    3480          79 :             pszUnitsY = "degrees";
    3481             : 
    3482         266 :         if (pszUnitsX && pszUnitsY)
    3483             :         {
    3484         219 :             if (EQUAL(pszUnitsX, pszUnitsY))
    3485         216 :                 pszUnits = pszUnitsX;
    3486           3 :             else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3487             :             {
    3488           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3489             :                          "X axis unit (%s) is different from Y axis "
    3490             :                          "unit (%s). SRS will ignore axis unit and be "
    3491             :                          "likely wrong.",
    3492             :                          pszUnitsX, pszUnitsY);
    3493             :             }
    3494             :         }
    3495          47 :         else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3496             :         {
    3497           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3498             :                      "X axis unit is defined, but not Y one ."
    3499             :                      "SRS will ignore axis unit and be likely wrong.");
    3500             :         }
    3501          47 :         else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3502             :         {
    3503           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3504             :                      "Y axis unit is defined, but not X one ."
    3505             :                      "SRS will ignore axis unit and be likely wrong.");
    3506             :         }
    3507             :     }
    3508             : 
    3509         514 :     if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3510             :     {
    3511          31 :         CPLStringList aosGridMappingKeyValues;
    3512          31 :         const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
    3513         789 :         for (const char *const *papszIter = papszMetadata;
    3514         789 :              papszIter && *papszIter; ++papszIter)
    3515             :         {
    3516         758 :             if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
    3517         236 :                 (*papszIter)[nLenGridMappingValue] == '#')
    3518             :             {
    3519         236 :                 char *pszKey = nullptr;
    3520         472 :                 pszValue = CPLParseNameValue(
    3521         236 :                     *papszIter + nLenGridMappingValue + 1, &pszKey);
    3522         236 :                 if (pszKey && pszValue)
    3523         236 :                     aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
    3524         236 :                 CPLFree(pszKey);
    3525             :             }
    3526             :         }
    3527             : 
    3528          31 :         bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
    3529             :                          CF_PP_SEMI_MAJOR_AXIS) != nullptr;
    3530             : 
    3531          31 :         oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
    3532          31 :         bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
    3533             :     }
    3534             :     else
    3535             :     {
    3536             :         // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
    3537             :         // attribute hold on the variable of interest that contains a PROJ.4
    3538             :         // string
    3539         483 :         pszValue = FetchAttr(nGroupId, nVarId, "crs");
    3540         484 :         if (pszValue &&
    3541           1 :             (strstr(pszValue, "+proj=") != nullptr ||
    3542           0 :              strstr(pszValue, "GEOGCS") != nullptr ||
    3543           0 :              strstr(pszValue, "PROJCS") != nullptr ||
    3544         484 :              strstr(pszValue, "EPSG:") != nullptr) &&
    3545           1 :             oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
    3546             :         {
    3547           1 :             bGotCfSRS = true;
    3548             :         }
    3549             :     }
    3550             : 
    3551             :     // Set Projection from CF.
    3552         514 :     double dfLinearUnitsConvFactor = 1.0;
    3553         514 :     if ((bGotGeogCS || bGotCfSRS))
    3554             :     {
    3555          31 :         if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3556             :         {
    3557             :             // Set SRS Units.
    3558             : 
    3559             :             // Check units for x and y.
    3560          28 :             if (oSRS.IsProjected())
    3561             :             {
    3562          25 :                 dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
    3563             : 
    3564             :                 // If the user doesn't ask to preserve the axis unit,
    3565             :                 // then normalize to metre
    3566          31 :                 if (dfLinearUnitsConvFactor != 1.0 &&
    3567           6 :                     !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
    3568             :                                   false))
    3569             :                 {
    3570           5 :                     oSRS.SetLinearUnits("metre", 1.0);
    3571           5 :                     oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
    3572             :                 }
    3573             :                 else
    3574             :                 {
    3575          20 :                     dfLinearUnitsConvFactor = 1.0;
    3576             :                 }
    3577             :             }
    3578             :         }
    3579             : 
    3580             :         // Set projection.
    3581          31 :         char *pszTempProjection = nullptr;
    3582          31 :         oSRS.exportToWkt(&pszTempProjection);
    3583          31 :         if (pszTempProjection)
    3584             :         {
    3585          31 :             CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3586          31 :             if (returnProjStr != nullptr)
    3587             :             {
    3588           2 :                 (*returnProjStr) = std::string(pszTempProjection);
    3589             :             }
    3590             :             else
    3591             :             {
    3592          29 :                 m_bAddedProjectionVarsDefs = true;
    3593          29 :                 m_bAddedProjectionVarsData = true;
    3594          29 :                 SetSpatialRefNoUpdate(&oSRS);
    3595             :             }
    3596             :         }
    3597          31 :         CPLFree(pszTempProjection);
    3598             :     }
    3599             : 
    3600         514 :     if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
    3601             :         ydim > 0)
    3602             :     {
    3603             :         double *pdfXCoord =
    3604         223 :             static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
    3605             :         double *pdfYCoord =
    3606         223 :             static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
    3607             : 
    3608         223 :         size_t start[2] = {0, 0};
    3609         223 :         size_t edge[2] = {xdim, 0};
    3610         223 :         int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
    3611             :                                         pdfXCoord);
    3612         223 :         NCDF_ERR(status);
    3613             : 
    3614         223 :         edge[0] = ydim;
    3615         223 :         status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
    3616             :                                     pdfYCoord);
    3617         223 :         NCDF_ERR(status);
    3618             : 
    3619         223 :         nc_type nc_var_dimx_datatype = NC_NAT;
    3620             :         status =
    3621         223 :             nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
    3622         223 :         NCDF_ERR(status);
    3623             : 
    3624         223 :         nc_type nc_var_dimy_datatype = NC_NAT;
    3625             :         status =
    3626         223 :             nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
    3627         223 :         NCDF_ERR(status);
    3628             : 
    3629         223 :         if (!poDS->bSwitchedXY)
    3630             :         {
    3631             :             // Convert ]180,540] longitude values to ]-180,0].
    3632         311 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3633          90 :                 CPLTestBool(
    3634             :                     CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
    3635             :             {
    3636             :                 // If minimum longitude is > 180, subtract 360 from all.
    3637             :                 // Add a check on the maximum X value too, since
    3638             :                 // NCDFIsVarLongitude() is not very specific by default (see
    3639             :                 // https://github.com/OSGeo/gdal/issues/1440)
    3640          97 :                 if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
    3641           7 :                     std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
    3642             :                 {
    3643           0 :                     CPLDebug(
    3644             :                         "GDAL_netCDF",
    3645             :                         "Offsetting longitudes from ]180,540] to ]-180,180]. "
    3646             :                         "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
    3647           0 :                     for (size_t i = 0; i < xdim; i++)
    3648           0 :                         pdfXCoord[i] -= 360;
    3649             :                 }
    3650             :             }
    3651             :         }
    3652             : 
    3653             :         // Is pixel spacing uniform across the map?
    3654             : 
    3655             :         // Check Longitude.
    3656             : 
    3657         223 :         bool bLonSpacingOK = false;
    3658         223 :         if (xdim == 2)
    3659             :         {
    3660          28 :             bLonSpacingOK = true;
    3661             :         }
    3662             :         else
    3663             :         {
    3664         195 :             bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
    3665             : 
    3666             :             // fix longitudes if longitudes should increase from
    3667             :             // west to east, but west > east
    3668         275 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3669          80 :                 !bWestIsLeft)
    3670             :             {
    3671           2 :                 size_t ndecreases = 0;
    3672             : 
    3673             :                 // there is lon wrap if longitudes increase
    3674             :                 // with one single decrease
    3675         107 :                 for (size_t i = 1; i < xdim; i++)
    3676             :                 {
    3677         105 :                     if (pdfXCoord[i] < pdfXCoord[i - 1])
    3678           1 :                         ndecreases++;
    3679             :                 }
    3680             : 
    3681           2 :                 if (ndecreases == 1)
    3682             :                 {
    3683           1 :                     CPLDebug("GDAL_netCDF", "longitude wrap detected");
    3684           4 :                     for (size_t i = 0; i < xdim; i++)
    3685             :                     {
    3686           3 :                         if (pdfXCoord[i] > pdfXCoord[xdim - 1])
    3687           1 :                             pdfXCoord[i] -= 360;
    3688             :                     }
    3689             :                 }
    3690             :             }
    3691             : 
    3692         195 :             const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
    3693         195 :             const double dfSpacingMiddle =
    3694         195 :                 pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
    3695         195 :             const double dfSpacingLast =
    3696         195 :                 pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
    3697             : 
    3698         195 :             CPLDebug("GDAL_netCDF",
    3699             :                      "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3700             :                      "dfSpacingLast: %f",
    3701             :                      static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
    3702             :                      dfSpacingLast);
    3703             : #ifdef NCDF_DEBUG
    3704             :             CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
    3705             :                      pdfXCoord[1], pdfXCoord[xdim / 2],
    3706             :                      pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
    3707             :                      pdfXCoord[xdim - 1]);
    3708             : #endif
    3709             : 
    3710             :             // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
    3711             :             // requires a 0.02% tolerance, so let's settle for 0.05%
    3712             : 
    3713             :             // For float variables, increase to 0.2% (as seen in
    3714             :             // https://github.com/OSGeo/gdal/issues/3663)
    3715         195 :             const double dfEpsRel =
    3716         195 :                 nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3717             : 
    3718             :             const double dfEps =
    3719             :                 dfEpsRel *
    3720         390 :                 std::max(fabs(dfSpacingBegin),
    3721         195 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3722         384 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3723         384 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3724         189 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3725             :             {
    3726         189 :                 bLonSpacingOK = true;
    3727             :             }
    3728           6 :             else if (CPLTestBool(CPLGetConfigOption(
    3729             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3730             :             {
    3731           0 :                 bLonSpacingOK = true;
    3732           0 :                 CPLDebug(
    3733             :                     "GDAL_netCDF",
    3734             :                     "Longitude/X is not equally spaced, but will be considered "
    3735             :                     "as such because of "
    3736             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3737             :             }
    3738             :         }
    3739             : 
    3740         223 :         if (bLonSpacingOK == false)
    3741             :         {
    3742           6 :             CPLDebug(
    3743             :                 "GDAL_netCDF", "%s",
    3744             :                 "Longitude/X is not equally spaced (with a 0.05% tolerance). "
    3745             :                 "You may set the "
    3746             :                 "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3747             :                 "option to YES to ignore this check");
    3748             :         }
    3749             : 
    3750             :         // Check Latitude.
    3751         223 :         bool bLatSpacingOK = false;
    3752             : 
    3753         223 :         if (ydim == 2)
    3754             :         {
    3755          48 :             bLatSpacingOK = true;
    3756             :         }
    3757             :         else
    3758             :         {
    3759         175 :             const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
    3760         175 :             const double dfSpacingMiddle =
    3761         175 :                 pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
    3762             : 
    3763         175 :             const double dfSpacingLast =
    3764         175 :                 pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
    3765             : 
    3766         175 :             CPLDebug("GDAL_netCDF",
    3767             :                      "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3768             :                      "dfSpacingLast: %f",
    3769             :                      (long)ydim, dfSpacingBegin, dfSpacingMiddle,
    3770             :                      dfSpacingLast);
    3771             : #ifdef NCDF_DEBUG
    3772             :             CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
    3773             :                      pdfYCoord[1], pdfYCoord[ydim / 2],
    3774             :                      pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
    3775             :                      pdfYCoord[ydim - 1]);
    3776             : #endif
    3777             : 
    3778         175 :             const double dfEpsRel =
    3779         175 :                 nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3780             : 
    3781             :             const double dfEps =
    3782             :                 dfEpsRel *
    3783         350 :                 std::max(fabs(dfSpacingBegin),
    3784         175 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3785         348 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3786         348 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3787         164 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3788             :             {
    3789         164 :                 bLatSpacingOK = true;
    3790             :             }
    3791          11 :             else if (CPLTestBool(CPLGetConfigOption(
    3792             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3793             :             {
    3794           0 :                 bLatSpacingOK = true;
    3795           0 :                 CPLDebug(
    3796             :                     "GDAL_netCDF",
    3797             :                     "Latitude/Y is not equally spaced, but will be considered "
    3798             :                     "as such because of "
    3799             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3800             :             }
    3801          11 :             else if (!oSRS.IsProjected() &&
    3802          11 :                      fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
    3803          30 :                      fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
    3804           8 :                      fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
    3805             :             {
    3806           8 :                 bLatSpacingOK = true;
    3807           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3808             :                          "Latitude grid not spaced evenly.  "
    3809             :                          "Setting projection for grid spacing is "
    3810             :                          "within 0.1 degrees threshold.");
    3811             : 
    3812           8 :                 CPLDebug("GDAL_netCDF",
    3813             :                          "Latitude grid not spaced evenly, but within 0.1 "
    3814             :                          "degree threshold (probably a Gaussian grid).  "
    3815             :                          "Saving original latitude values in Y_VALUES "
    3816             :                          "geolocation metadata");
    3817           8 :                 Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
    3818             :             }
    3819             : 
    3820         175 :             if (bLatSpacingOK == false)
    3821             :             {
    3822           3 :                 CPLDebug(
    3823             :                     "GDAL_netCDF", "%s",
    3824             :                     "Latitude/Y is not equally spaced (with a 0.05% "
    3825             :                     "tolerance). "
    3826             :                     "You may set the "
    3827             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3828             :                     "option to YES to ignore this check");
    3829             :             }
    3830             :         }
    3831             : 
    3832         223 :         if (bLonSpacingOK && bLatSpacingOK)
    3833             :         {
    3834             :             // We have gridded data so we can set the Georeferencing info.
    3835             : 
    3836             :             // Enable GeoTransform.
    3837             : 
    3838             :             // In the following "actual_range" and "node_offset"
    3839             :             // are attributes used by netCDF files created by GMT.
    3840             :             // If we find them we know how to proceed. Else, use
    3841             :             // the original algorithm.
    3842         216 :             bGotCfGT = true;
    3843             : 
    3844         216 :             int node_offset = 0;
    3845         216 :             NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
    3846             : 
    3847         216 :             double adfActualRange[2] = {0.0, 0.0};
    3848         216 :             double xMinMax[2] = {0.0, 0.0};
    3849         216 :             double yMinMax[2] = {0.0, 0.0};
    3850             : 
    3851             :             const auto RoundMinMaxForFloatVals =
    3852          58 :                 [](double &dfMin, double &dfMax, int nIntervals)
    3853             :             {
    3854             :                 // Helps for a case where longitudes range from
    3855             :                 // -179.99 to 180.0 with a 0.01 degree spacing.
    3856             :                 // However as this is encoded in a float array,
    3857             :                 // -179.99 is actually read as -179.99000549316406 as
    3858             :                 // a double. Try to detect that and correct the rounding
    3859             : 
    3860          87 :                 const auto IsAlmostInteger = [](double dfVal)
    3861             :                 {
    3862          87 :                     constexpr double THRESHOLD_INTEGER = 1e-3;
    3863          87 :                     return std::fabs(dfVal - std::round(dfVal)) <=
    3864          87 :                            THRESHOLD_INTEGER;
    3865             :                 };
    3866             : 
    3867          58 :                 const double dfSpacing = (dfMax - dfMin) / nIntervals;
    3868          58 :                 if (dfSpacing > 0)
    3869             :                 {
    3870          47 :                     const double dfInvSpacing = 1.0 / dfSpacing;
    3871          47 :                     if (IsAlmostInteger(dfInvSpacing))
    3872             :                     {
    3873          20 :                         const double dfRoundedSpacing =
    3874          20 :                             1.0 / std::round(dfInvSpacing);
    3875          20 :                         const double dfMinDivRoundedSpacing =
    3876          20 :                             dfMin / dfRoundedSpacing;
    3877          20 :                         const double dfMaxDivRoundedSpacing =
    3878          20 :                             dfMax / dfRoundedSpacing;
    3879          40 :                         if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
    3880          20 :                             IsAlmostInteger(dfMaxDivRoundedSpacing))
    3881             :                         {
    3882          20 :                             const double dfRoundedMin =
    3883          20 :                                 std::round(dfMinDivRoundedSpacing) *
    3884             :                                 dfRoundedSpacing;
    3885          20 :                             const double dfRoundedMax =
    3886          20 :                                 std::round(dfMaxDivRoundedSpacing) *
    3887             :                                 dfRoundedSpacing;
    3888          20 :                             if (static_cast<float>(dfMin) ==
    3889          20 :                                     static_cast<float>(dfRoundedMin) &&
    3890           8 :                                 static_cast<float>(dfMax) ==
    3891           8 :                                     static_cast<float>(dfRoundedMax))
    3892             :                             {
    3893           7 :                                 dfMin = dfRoundedMin;
    3894           7 :                                 dfMax = dfRoundedMax;
    3895             :                             }
    3896             :                         }
    3897             :                     }
    3898             :                 }
    3899          58 :             };
    3900             : 
    3901         216 :             if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
    3902             :                                    adfActualRange))
    3903             :             {
    3904           3 :                 xMinMax[0] = adfActualRange[0];
    3905           3 :                 xMinMax[1] = adfActualRange[1];
    3906             : 
    3907             :                 // Present xMinMax[] in the same order as padfXCoord
    3908           3 :                 if ((xMinMax[0] - xMinMax[1]) *
    3909           3 :                         (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
    3910             :                     0)
    3911             :                 {
    3912           0 :                     std::swap(xMinMax[0], xMinMax[1]);
    3913             :                 }
    3914             :             }
    3915             :             else
    3916             :             {
    3917         213 :                 xMinMax[0] = pdfXCoord[0];
    3918         213 :                 xMinMax[1] = pdfXCoord[xdim - 1];
    3919         213 :                 node_offset = 0;
    3920             : 
    3921         213 :                 if (nc_var_dimx_datatype == NC_FLOAT)
    3922             :                 {
    3923          29 :                     RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
    3924          29 :                                             poDS->nRasterXSize - 1);
    3925             :                 }
    3926             :             }
    3927             : 
    3928         216 :             if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
    3929             :                                    adfActualRange))
    3930             :             {
    3931           3 :                 yMinMax[0] = adfActualRange[0];
    3932           3 :                 yMinMax[1] = adfActualRange[1];
    3933             : 
    3934             :                 // Present yMinMax[] in the same order as pdfYCoord
    3935           3 :                 if ((yMinMax[0] - yMinMax[1]) *
    3936           3 :                         (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
    3937             :                     0)
    3938             :                 {
    3939           1 :                     std::swap(yMinMax[0], yMinMax[1]);
    3940             :                 }
    3941             :             }
    3942             :             else
    3943             :             {
    3944         213 :                 yMinMax[0] = pdfYCoord[0];
    3945         213 :                 yMinMax[1] = pdfYCoord[ydim - 1];
    3946         213 :                 node_offset = 0;
    3947             : 
    3948         213 :                 if (nc_var_dimy_datatype == NC_FLOAT)
    3949             :                 {
    3950          29 :                     RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
    3951          29 :                                             poDS->nRasterYSize - 1);
    3952             :                 }
    3953             :             }
    3954             : 
    3955         216 :             double dfCoordOffset = 0.0;
    3956         216 :             double dfCoordScale = 1.0;
    3957         216 :             if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
    3958         220 :                                    &dfCoordOffset) &&
    3959           4 :                 !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
    3960             :                                    &dfCoordScale))
    3961             :             {
    3962           4 :                 xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
    3963           4 :                 xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
    3964             :             }
    3965             : 
    3966         216 :             if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
    3967         220 :                                    &dfCoordOffset) &&
    3968           4 :                 !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
    3969             :                                    &dfCoordScale))
    3970             :             {
    3971           4 :                 yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
    3972           4 :                 yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
    3973             :             }
    3974             : 
    3975             :             // Check for reverse order of y-coordinate.
    3976         216 :             if (!bSwitchedXY)
    3977             :             {
    3978         214 :                 poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
    3979         214 :                 CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
    3980         214 :                          static_cast<int>(poDS->bBottomUp));
    3981         214 :                 if (!poDS->bBottomUp)
    3982             :                 {
    3983          32 :                     std::swap(yMinMax[0], yMinMax[1]);
    3984             :                 }
    3985             :             }
    3986             : 
    3987             :             // Geostationary satellites can specify units in (micro)radians
    3988             :             // So we check if they do, and if so convert to linear units
    3989             :             // (meters)
    3990         216 :             const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
    3991         216 :             if (pszProjName != nullptr)
    3992             :             {
    3993          24 :                 if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
    3994             :                 {
    3995             :                     double satelliteHeight =
    3996           3 :                         oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
    3997           3 :                     size_t nAttlen = 0;
    3998             :                     char szUnits[NC_MAX_NAME + 1];
    3999           3 :                     szUnits[0] = '\0';
    4000           3 :                     nc_type nAttype = NC_NAT;
    4001           3 :                     nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
    4002             :                                &nAttlen);
    4003           6 :                     if (nAttlen < sizeof(szUnits) &&
    4004           3 :                         nc_get_att_text(nGroupId, nVarDimXID, "units",
    4005             :                                         szUnits) == NC_NOERR)
    4006             :                     {
    4007           3 :                         szUnits[nAttlen] = '\0';
    4008           3 :                         if (EQUAL(szUnits, "microradian"))
    4009             :                         {
    4010           1 :                             xMinMax[0] =
    4011           1 :                                 xMinMax[0] * satelliteHeight * 0.000001;
    4012           1 :                             xMinMax[1] =
    4013           1 :                                 xMinMax[1] * satelliteHeight * 0.000001;
    4014             :                         }
    4015           2 :                         else if (EQUAL(szUnits, "rad") ||
    4016           1 :                                  EQUAL(szUnits, "radian"))
    4017             :                         {
    4018           2 :                             xMinMax[0] = xMinMax[0] * satelliteHeight;
    4019           2 :                             xMinMax[1] = xMinMax[1] * satelliteHeight;
    4020             :                         }
    4021             :                     }
    4022           3 :                     szUnits[0] = '\0';
    4023           3 :                     nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
    4024             :                                &nAttlen);
    4025           6 :                     if (nAttlen < sizeof(szUnits) &&
    4026           3 :                         nc_get_att_text(nGroupId, nVarDimYID, "units",
    4027             :                                         szUnits) == NC_NOERR)
    4028             :                     {
    4029           3 :                         szUnits[nAttlen] = '\0';
    4030           3 :                         if (EQUAL(szUnits, "microradian"))
    4031             :                         {
    4032           1 :                             yMinMax[0] =
    4033           1 :                                 yMinMax[0] * satelliteHeight * 0.000001;
    4034           1 :                             yMinMax[1] =
    4035           1 :                                 yMinMax[1] * satelliteHeight * 0.000001;
    4036             :                         }
    4037           2 :                         else if (EQUAL(szUnits, "rad") ||
    4038           1 :                                  EQUAL(szUnits, "radian"))
    4039             :                         {
    4040           2 :                             yMinMax[0] = yMinMax[0] * satelliteHeight;
    4041           2 :                             yMinMax[1] = yMinMax[1] * satelliteHeight;
    4042             :                         }
    4043             :                     }
    4044             :                 }
    4045             :             }
    4046             : 
    4047         216 :             adfTempGeoTransform[0] = xMinMax[0];
    4048         216 :             adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
    4049         216 :                                      (poDS->nRasterXSize + (node_offset - 1));
    4050         216 :             adfTempGeoTransform[2] = 0;
    4051         216 :             if (bSwitchedXY)
    4052             :             {
    4053           2 :                 adfTempGeoTransform[3] = yMinMax[0];
    4054           2 :                 adfTempGeoTransform[4] = 0;
    4055           2 :                 adfTempGeoTransform[5] =
    4056           2 :                     (yMinMax[1] - yMinMax[0]) /
    4057           2 :                     (poDS->nRasterYSize + (node_offset - 1));
    4058             :             }
    4059             :             else
    4060             :             {
    4061         214 :                 adfTempGeoTransform[3] = yMinMax[1];
    4062         214 :                 adfTempGeoTransform[4] = 0;
    4063         214 :                 adfTempGeoTransform[5] =
    4064         214 :                     (yMinMax[0] - yMinMax[1]) /
    4065         214 :                     (poDS->nRasterYSize + (node_offset - 1));
    4066             :             }
    4067             : 
    4068             :             // Compute the center of the pixel.
    4069         216 :             if (!node_offset)
    4070             :             {
    4071             :                 // Otherwise its already the pixel center.
    4072         216 :                 adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
    4073         216 :                 adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
    4074             :             }
    4075             :         }
    4076             : 
    4077             :         const auto AreSRSEqualThroughProj4String =
    4078           2 :             [](const OGRSpatialReference &oSRS1,
    4079             :                const OGRSpatialReference &oSRS2)
    4080             :         {
    4081           2 :             char *pszProj4Str1 = nullptr;
    4082           2 :             oSRS1.exportToProj4(&pszProj4Str1);
    4083             : 
    4084           2 :             char *pszProj4Str2 = nullptr;
    4085           2 :             oSRS2.exportToProj4(&pszProj4Str2);
    4086             : 
    4087             :             {
    4088           2 :                 char *pszTmp = strstr(pszProj4Str1, "+datum=");
    4089           2 :                 if (pszTmp)
    4090           0 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4091             :             }
    4092             : 
    4093             :             {
    4094           2 :                 char *pszTmp = strstr(pszProj4Str2, "+datum=");
    4095           2 :                 if (pszTmp)
    4096           2 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4097             :             }
    4098             : 
    4099           2 :             bool bRet = false;
    4100           2 :             if (pszProj4Str1 && pszProj4Str2 &&
    4101           2 :                 EQUAL(pszProj4Str1, pszProj4Str2))
    4102             :             {
    4103           1 :                 bRet = true;
    4104             :             }
    4105             : 
    4106           2 :             CPLFree(pszProj4Str1);
    4107           2 :             CPLFree(pszProj4Str2);
    4108           2 :             return bRet;
    4109             :         };
    4110             : 
    4111         223 :         if (dfLinearUnitsConvFactor != 1.0)
    4112             :         {
    4113          35 :             for (int i = 0; i < 6; ++i)
    4114          30 :                 adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
    4115             : 
    4116           5 :             if (paosRemovedMDItems)
    4117             :             {
    4118             :                 char szVarNameX[NC_MAX_NAME + 1];
    4119           5 :                 CPL_IGNORE_RET_VAL(
    4120           5 :                     nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4121             : 
    4122             :                 char szVarNameY[NC_MAX_NAME + 1];
    4123           5 :                 CPL_IGNORE_RET_VAL(
    4124           5 :                     nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4125             : 
    4126           5 :                 paosRemovedMDItems->push_back(
    4127             :                     CPLSPrintf("%s#units", szVarNameX));
    4128           5 :                 paosRemovedMDItems->push_back(
    4129             :                     CPLSPrintf("%s#units", szVarNameY));
    4130             :             }
    4131             :         }
    4132             : 
    4133             :         // If there is a global "geospatial_bounds_crs" attribute, check that it
    4134             :         // is consistent with the SRS, and if so, use it as the SRS
    4135             :         const char *pszGBCRS =
    4136         223 :             FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
    4137         223 :         if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
    4138             :         {
    4139           4 :             OGRSpatialReference oSRSFromGBCRS;
    4140           2 :             oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4141           2 :             if (oSRSFromGBCRS.SetFromUserInput(
    4142             :                     pszGBCRS,
    4143             :                     OGRSpatialReference::
    4144           4 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
    4145           2 :                 AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
    4146             :             {
    4147           1 :                 oSRS = std::move(oSRSFromGBCRS);
    4148           1 :                 SetSpatialRefNoUpdate(&oSRS);
    4149             :             }
    4150             :         }
    4151             : 
    4152         223 :         CPLFree(pdfXCoord);
    4153         223 :         CPLFree(pdfYCoord);
    4154             :     }  // end if(has dims)
    4155             : 
    4156             :     // Process custom GeoTransform GDAL value.
    4157         514 :     if (!EQUAL(pszGridMappingValue, ""))
    4158             :     {
    4159         219 :         if (pszGeoTransform != nullptr)
    4160             :         {
    4161             :             CPLStringList aosGeoTransform(
    4162         220 :                 CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
    4163         110 :             if (aosGeoTransform.size() == 6)
    4164             :             {
    4165             :                 std::array<double, 6> adfGeoTransformFromAttribute;
    4166         770 :                 for (int i = 0; i < 6; i++)
    4167             :                 {
    4168         660 :                     adfGeoTransformFromAttribute[i] =
    4169         660 :                         CPLAtof(aosGeoTransform[i]);
    4170             :                 }
    4171             : 
    4172         110 :                 if (bGotCfGT)
    4173             :                 {
    4174          94 :                     constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
    4175          94 :                     double dfMaxAbsoluteError = 0.0;
    4176         658 :                     for (int i = 0; i < 6; i++)
    4177             :                     {
    4178             :                         double dfAbsoluteError =
    4179         564 :                             std::abs(adfTempGeoTransform[i] -
    4180         564 :                                      adfGeoTransformFromAttribute[i]);
    4181         564 :                         if (dfAbsoluteError >
    4182         564 :                             std::abs(adfGeoTransformFromAttribute[i] *
    4183             :                                      GT_RELERROR_WARN_THRESHOLD))
    4184             :                         {
    4185           2 :                             dfMaxAbsoluteError =
    4186           2 :                                 std::max(dfMaxAbsoluteError, dfAbsoluteError);
    4187             :                         }
    4188             :                     }
    4189             : 
    4190          94 :                     if (dfMaxAbsoluteError > 0)
    4191             :                     {
    4192           2 :                         CPLError(CE_Warning, CPLE_AppDefined,
    4193             :                                  "GeoTransform read from attribute of %s "
    4194             :                                  "variable differs from value calculated from "
    4195             :                                  "dimension variables (max diff = %g). Using "
    4196             :                                  "value from attribute.",
    4197             :                                  pszGridMappingValue, dfMaxAbsoluteError);
    4198             :                     }
    4199             :                 }
    4200             : 
    4201         110 :                 std::copy(adfGeoTransformFromAttribute.begin(),
    4202             :                           adfGeoTransformFromAttribute.end(),
    4203             :                           adfTempGeoTransform);
    4204         110 :                 bGotGdalGT = true;
    4205             :             }
    4206             :         }
    4207             :         else
    4208             :         {
    4209             :             // Look for corner array values.
    4210             :             // CPLDebug("GDAL_netCDF",
    4211             :             //           "looking for geotransform corners");
    4212         109 :             bool bGotNN = false;
    4213         109 :             double dfNN = FetchCopyParam(pszGridMappingValue,
    4214             :                                          "Northernmost_Northing", 0, &bGotNN);
    4215             : 
    4216         109 :             bool bGotSN = false;
    4217         109 :             double dfSN = FetchCopyParam(pszGridMappingValue,
    4218             :                                          "Southernmost_Northing", 0, &bGotSN);
    4219             : 
    4220         109 :             bool bGotEE = false;
    4221         109 :             double dfEE = FetchCopyParam(pszGridMappingValue,
    4222             :                                          "Easternmost_Easting", 0, &bGotEE);
    4223             : 
    4224         109 :             bool bGotWE = false;
    4225         109 :             double dfWE = FetchCopyParam(pszGridMappingValue,
    4226             :                                          "Westernmost_Easting", 0, &bGotWE);
    4227             : 
    4228             :             // Only set the GeoTransform if we got all the values.
    4229         109 :             if (bGotNN && bGotSN && bGotEE && bGotWE)
    4230             :             {
    4231           0 :                 bGotGdalGT = true;
    4232             : 
    4233           0 :                 adfTempGeoTransform[0] = dfWE;
    4234           0 :                 adfTempGeoTransform[1] =
    4235           0 :                     (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
    4236           0 :                 adfTempGeoTransform[2] = 0.0;
    4237           0 :                 adfTempGeoTransform[3] = dfNN;
    4238           0 :                 adfTempGeoTransform[4] = 0.0;
    4239           0 :                 adfTempGeoTransform[5] =
    4240           0 :                     (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
    4241             :                 // Compute the center of the pixel.
    4242           0 :                 adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
    4243           0 :                 adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
    4244             :             }
    4245             :         }  // (pszGeoTransform != NULL)
    4246             : 
    4247         219 :         if (bGotGdalSRS && !bGotGdalGT)
    4248          74 :             CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
    4249             :     }
    4250             : 
    4251         514 :     if (!pszWKT && !bGotCfSRS)
    4252             :     {
    4253             :         // Some netCDF files have a srid attribute (#6613) like
    4254             :         // urn:ogc:def:crs:EPSG::6931
    4255         295 :         const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
    4256         295 :         if (pszSRID != nullptr)
    4257             :         {
    4258           0 :             oSRS.Clear();
    4259           0 :             if (oSRS.SetFromUserInput(
    4260             :                     pszSRID,
    4261             :                     OGRSpatialReference::
    4262           0 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    4263             :             {
    4264           0 :                 char *pszWKTExport = nullptr;
    4265           0 :                 CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
    4266           0 :                 oSRS.exportToWkt(&pszWKTExport);
    4267           0 :                 if (returnProjStr != nullptr)
    4268             :                 {
    4269           0 :                     (*returnProjStr) = std::string(pszWKTExport);
    4270             :                 }
    4271             :                 else
    4272             :                 {
    4273           0 :                     m_bAddedProjectionVarsDefs = true;
    4274           0 :                     m_bAddedProjectionVarsData = true;
    4275           0 :                     SetSpatialRefNoUpdate(&oSRS);
    4276             :                 }
    4277           0 :                 CPLFree(pszWKTExport);
    4278             :             }
    4279             :         }
    4280             :     }
    4281             : 
    4282         514 :     CPLFree(pszGridMappingValue);
    4283             : 
    4284         514 :     if (bReadSRSOnly)
    4285         166 :         return;
    4286             : 
    4287             :     // Determines the SRS to be used by the geolocation array, if any
    4288         696 :     std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
    4289         348 :     if (!m_oSRS.IsEmpty())
    4290             :     {
    4291         262 :         OGRSpatialReference oGeogCRS;
    4292         131 :         oGeogCRS.CopyGeogCSFrom(&m_oSRS);
    4293         131 :         char *pszWKTTmp = nullptr;
    4294         131 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
    4295         131 :         if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
    4296             :         {
    4297         131 :             osGeolocWKT = pszWKTTmp;
    4298             :         }
    4299         131 :         CPLFree(pszWKTTmp);
    4300             :     }
    4301             : 
    4302             :     // Process geolocation arrays from CF "coordinates" attribute.
    4303         696 :     std::string osGeolocXName, osGeolocYName;
    4304         348 :     if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
    4305         348 :                              osGeolocYName))
    4306             :     {
    4307          52 :         bool bCanCancelGT = true;
    4308          52 :         if ((nVarDimXID != -1) && (nVarDimYID != -1))
    4309             :         {
    4310             :             char szVarNameX[NC_MAX_NAME + 1];
    4311          44 :             CPL_IGNORE_RET_VAL(
    4312          44 :                 nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4313             :             char szVarNameY[NC_MAX_NAME + 1];
    4314          44 :             CPL_IGNORE_RET_VAL(
    4315          44 :                 nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4316          44 :             bCanCancelGT =
    4317          44 :                 !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
    4318             :         }
    4319          86 :         if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
    4320          34 :             !bSwitchedXY)
    4321             :         {
    4322          32 :             bGotCfGT = false;
    4323             :         }
    4324             :     }
    4325         120 :     else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
    4326         419 :              (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
    4327           3 :              ((!bSwitchedXY &&
    4328           3 :                NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    4329           1 :                NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
    4330           2 :               (bSwitchedXY &&
    4331           0 :                NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    4332           0 :                NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
    4333             :     {
    4334             :         // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
    4335             :         // which is indexed by lat, lon variables, but lat has irregular
    4336             :         // spacing.
    4337           1 :         const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
    4338           1 :         const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
    4339           1 :         if (bSwitchedXY)
    4340             :         {
    4341           0 :             std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4342           0 :             GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4343             :         }
    4344             : 
    4345           1 :         CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4346             :                  pszGeolocXFullName, pszGeolocYFullName);
    4347             : 
    4348           1 :         GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4349             :                                         "GEOLOCATION");
    4350             : 
    4351           2 :         CPLString osTMP;
    4352           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4353           1 :                      pszGeolocXFullName);
    4354             : 
    4355           1 :         GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4356           1 :         GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4357           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4358           1 :                      pszGeolocYFullName);
    4359             : 
    4360           1 :         GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4361           1 :         GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4362             : 
    4363           1 :         GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4364           1 :         GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4365             : 
    4366           1 :         GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4367           1 :         GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4368             : 
    4369           1 :         GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4370             :                                         "PIXEL_CENTER", "GEOLOCATION");
    4371             :     }
    4372             : 
    4373             :     // Set GeoTransform if we got a complete one - after projection has been set
    4374         348 :     if (bGotCfGT || bGotGdalGT)
    4375             :     {
    4376         198 :         m_bAddedProjectionVarsDefs = true;
    4377         198 :         m_bAddedProjectionVarsData = true;
    4378         198 :         SetGeoTransformNoUpdate(adfTempGeoTransform);
    4379             :     }
    4380             : 
    4381             :     // Debugging reports.
    4382         348 :     CPLDebug("GDAL_netCDF",
    4383             :              "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
    4384             :              "bGotGdalSRS=%d bGotGdalGT=%d",
    4385             :              static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
    4386             :              static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
    4387             :              static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
    4388             : 
    4389         348 :     if (!bGotCfGT && !bGotGdalGT)
    4390         150 :         CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
    4391             : 
    4392         348 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
    4393         150 :         CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
    4394             : 
    4395             :     // wish of 6195
    4396             :     // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
    4397         348 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
    4398             :     {
    4399         217 :         if (bGotCfGT || bGotGdalGT)
    4400             :         {
    4401         134 :             bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
    4402          67 :                 papszOpenOptions, "ASSUME_LONGLAT",
    4403             :                 CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
    4404             : 
    4405           2 :             if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
    4406           2 :                 adfTempGeoTransform[0] < 360 &&
    4407           4 :                 (adfTempGeoTransform[0] +
    4408           2 :                  adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
    4409          71 :                 adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
    4410           4 :                 (adfTempGeoTransform[3] +
    4411           2 :                  adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
    4412             :             {
    4413             : 
    4414           2 :                 poDS->bIsGeographic = true;
    4415           2 :                 char *pszTempProjection = nullptr;
    4416             :                 // seems odd to use 4326 so OGC:CRS84
    4417           2 :                 oSRS.SetFromUserInput("OGC:CRS84");
    4418           2 :                 oSRS.exportToWkt(&pszTempProjection);
    4419           2 :                 if (returnProjStr != nullptr)
    4420             :                 {
    4421           0 :                     (*returnProjStr) = std::string(pszTempProjection);
    4422             :                 }
    4423             :                 else
    4424             :                 {
    4425           2 :                     m_bAddedProjectionVarsDefs = true;
    4426           2 :                     m_bAddedProjectionVarsData = true;
    4427           2 :                     SetSpatialRefNoUpdate(&oSRS);
    4428             :                 }
    4429           2 :                 CPLFree(pszTempProjection);
    4430             : 
    4431           2 :                 CPLDebug("netCDF",
    4432             :                          "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
    4433             :                          "none otherwise available and geotransform within "
    4434             :                          "suitable bounds. "
    4435             :                          "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
    4436             :                          "option or "
    4437             :                          "    ASSUME_LONGLAT=NO as open option to bypass this "
    4438             :                          "assumption.");
    4439             :             }
    4440             :         }
    4441             :     }
    4442             : 
    4443             : // Search for Well-known GeogCS if got only CF WKT
    4444             : // Disabled for now, as a named datum also include control points
    4445             : // (see mailing list and bug#4281
    4446             : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
    4447             : 
    4448             : // Disabled for now, but could be set in a config option.
    4449             : #if 0
    4450             :     bool bLookForWellKnownGCS = false;  // This could be a Config Option.
    4451             : 
    4452             :     if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
    4453             :     {
    4454             :         // ET - Could use a more exhaustive method by scanning all EPSG codes in
    4455             :         // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
    4456             :         // for comparing two WKT".
    4457             :         // This code could be contributed to a new function.
    4458             :         // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
    4459             :         //     const OGRSpatialReference *poOther) */
    4460             :         CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
    4461             :         const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
    4462             :         char *pszWKGCS = NULL;
    4463             :         oSRS.exportToPrettyWkt(&pszWKGCS);
    4464             :         for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
    4465             :         {
    4466             :             pszWKGCS = CPLStrdup(pszWKGCSList[i]);
    4467             :             OGRSpatialReference oSRSTmp;
    4468             :             oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
    4469             :             // Set datum to unknown, bug #4281.
    4470             :             if( oSRSTmp.GetAttrNode("DATUM" ) )
    4471             :                 oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
    4472             :             // Could use OGRSpatialReference::StripCTParms(), but let's keep
    4473             :             // TOWGS84.
    4474             :             oSRSTmp.GetRoot()->StripNodes("AXIS");
    4475             :             oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
    4476             :             oSRSTmp.GetRoot()->StripNodes("EXTENSION");
    4477             : 
    4478             :             oSRSTmp.exportToPrettyWkt(&pszWKGCS);
    4479             :             if( oSRS.IsSameGeogCS(&oSRSTmp) )
    4480             :             {
    4481             :                 oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
    4482             :                 oSRS.exportToWkt(&(pszTempProjection));
    4483             :                 SetProjection(pszTempProjection);
    4484             :                 CPLFree(pszTempProjection);
    4485             :             }
    4486             :         }
    4487             :     }
    4488             : #endif
    4489             : }
    4490             : 
    4491         123 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
    4492             :                                          bool bReadSRSOnly)
    4493             : {
    4494         123 :     SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
    4495             :                          nullptr, nullptr);
    4496         123 : }
    4497             : 
    4498         285 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
    4499             : {
    4500             :     // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
    4501             :     // and https://github.com/OSGeo/gdal/issues/7605
    4502             : 
    4503             :     // Check for a structure like:
    4504             :     /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
    4505             :         dimensions:
    4506             :             number_of_lines = 3248 ;
    4507             :             pixels_per_line = 3200 ;
    4508             :             [...]
    4509             :             pixel_control_points = 3200 ;
    4510             :         [...]
    4511             :         group: geophysical_data {
    4512             :           variables:
    4513             :             short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId
    4514             :                 [...]
    4515             :         }
    4516             :         group: navigation_data {
    4517             :           variables:
    4518             :             float longitude(number_of_lines, pixel_control_points) ;
    4519             :                 [...]
    4520             :             float latitude(number_of_lines, pixel_control_points) ;
    4521             :                 [...]
    4522             :         }
    4523             :     }
    4524             :     */
    4525             :     // Note that the longitude and latitude arrays are not indexed by the
    4526             :     // same dimensions. Handle only the case where
    4527             :     // pixel_control_points == pixels_per_line
    4528             :     // If there was a subsampling of the geolocation arrays, we'd need to
    4529             :     // add more logic.
    4530             : 
    4531         570 :     std::string osGroupName;
    4532         285 :     osGroupName.resize(NC_MAX_NAME);
    4533         285 :     NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
    4534         285 :     osGroupName.resize(strlen(osGroupName.data()));
    4535         285 :     if (osGroupName != "geophysical_data")
    4536         284 :         return false;
    4537             : 
    4538           1 :     int nVarDims = 0;
    4539           1 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4540           1 :     if (nVarDims != 2)
    4541           0 :         return false;
    4542             : 
    4543           1 :     int nNavigationDataGrpId = 0;
    4544           1 :     if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
    4545             :         NC_NOERR)
    4546           0 :         return false;
    4547             : 
    4548             :     std::array<int, 2> anVarDimIds;
    4549           1 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4550             : 
    4551           1 :     int nLongitudeId = 0;
    4552           1 :     int nLatitudeId = 0;
    4553           1 :     if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
    4554           2 :             NC_NOERR ||
    4555           1 :         nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
    4556             :             NC_NOERR)
    4557             :     {
    4558           0 :         return false;
    4559             :     }
    4560             : 
    4561           1 :     int nDimsLongitude = 0;
    4562           1 :     NCDF_ERR(
    4563             :         nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
    4564           1 :     int nDimsLatitude = 0;
    4565           1 :     NCDF_ERR(
    4566             :         nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
    4567           1 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4568             :     {
    4569           0 :         return false;
    4570             :     }
    4571             : 
    4572             :     std::array<int, 2> anDimLongitudeIds;
    4573           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
    4574             :                              anDimLongitudeIds.data()));
    4575             :     std::array<int, 2> anDimLatitudeIds;
    4576           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
    4577             :                              anDimLatitudeIds.data()));
    4578           1 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4579             :     {
    4580           0 :         return false;
    4581             :     }
    4582             : 
    4583             :     std::array<size_t, 2> anSizeVarDimIds;
    4584             :     std::array<size_t, 2> anSizeLongLatIds;
    4585           2 :     if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
    4586           1 :               NC_NOERR &&
    4587           1 :           nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
    4588           1 :               NC_NOERR &&
    4589           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
    4590           1 :               NC_NOERR &&
    4591           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
    4592             :               NC_NOERR &&
    4593           1 :           anSizeVarDimIds == anSizeLongLatIds))
    4594             :     {
    4595           0 :         return false;
    4596             :     }
    4597             : 
    4598           1 :     const char *pszGeolocXFullName = "/navigation_data/longitude";
    4599           1 :     const char *pszGeolocYFullName = "/navigation_data/latitude";
    4600             : 
    4601           1 :     if (bSwitchedXY)
    4602             :     {
    4603           0 :         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4604           0 :         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4605             :     }
    4606             : 
    4607           1 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4608             :              pszGeolocXFullName, pszGeolocYFullName);
    4609             : 
    4610           1 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4611             :                                     "GEOLOCATION");
    4612             : 
    4613           1 :     CPLString osTMP;
    4614           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4615             : 
    4616           1 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4617           1 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4618           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4619             : 
    4620           1 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4621           1 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4622             : 
    4623           1 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4624           1 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4625             : 
    4626           1 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4627           1 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4628             : 
    4629           1 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4630             :                                     "GEOLOCATION");
    4631           1 :     return true;
    4632             : }
    4633             : 
    4634         284 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
    4635             : {
    4636             :     // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
    4637             : 
    4638             :     // Check for a structure like:
    4639             :     /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
    4640             :         dimensions:
    4641             :             downtrack = 1280 ;
    4642             :             crosstrack = 1242 ;
    4643             :             bands = 285 ;
    4644             :             [...]
    4645             : 
    4646             :         variables:
    4647             :             float reflectance(downtrack, crosstrack, bands) ;
    4648             : 
    4649             :         group: location {
    4650             :           variables:
    4651             :                 double lon(downtrack, crosstrack) ;
    4652             :                         lon:_FillValue = -9999. ;
    4653             :                         lon:long_name = "Longitude (WGS-84)" ;
    4654             :                         lon:units = "degrees east" ;
    4655             :                 double lat(downtrack, crosstrack) ;
    4656             :                         lat:_FillValue = -9999. ;
    4657             :                         lat:long_name = "Latitude (WGS-84)" ;
    4658             :                         lat:units = "degrees north" ;
    4659             :           } // group location
    4660             : 
    4661             :     }
    4662             :     or
    4663             :     netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
    4664             :         dimensions:
    4665             :                 downtrack = 1664 ;
    4666             :                 crosstrack = 1242 ;
    4667             :                 [...]
    4668             :         variables:
    4669             :                 float group_1_band_depth(downtrack, crosstrack) ;
    4670             :                         group_1_band_depth:_FillValue = -9999.f ;
    4671             :                         group_1_band_depth:long_name = "Group 1 Band Depth" ;
    4672             :                         group_1_band_depth:units = "unitless" ;
    4673             :                 [...]
    4674             :         group: location {
    4675             :           variables:
    4676             :                 double lon(downtrack, crosstrack) ;
    4677             :                         lon:_FillValue = -9999. ;
    4678             :                         lon:long_name = "Longitude (WGS-84)" ;
    4679             :                         lon:units = "degrees east" ;
    4680             :                 double lat(downtrack, crosstrack) ;
    4681             :                         lat:_FillValue = -9999. ;
    4682             :                         lat:long_name = "Latitude (WGS-84)" ;
    4683             :                         lat:units = "degrees north" ;
    4684             :         }
    4685             :     */
    4686             : 
    4687         284 :     int nVarDims = 0;
    4688         284 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4689         284 :     if (nVarDims != 2 && nVarDims != 3)
    4690          14 :         return false;
    4691             : 
    4692         270 :     int nLocationGrpId = 0;
    4693         270 :     if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
    4694          58 :         return false;
    4695             : 
    4696             :     std::array<int, 3> anVarDimIds;
    4697         212 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4698         212 :     if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
    4699          21 :         return false;
    4700             : 
    4701         191 :     int nLongitudeId = 0;
    4702         191 :     int nLatitudeId = 0;
    4703         229 :     if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
    4704          38 :         nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
    4705             :     {
    4706         153 :         return false;
    4707             :     }
    4708             : 
    4709          38 :     int nDimsLongitude = 0;
    4710          38 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
    4711          38 :     int nDimsLatitude = 0;
    4712          38 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
    4713          38 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4714             :     {
    4715          34 :         return false;
    4716             :     }
    4717             : 
    4718             :     std::array<int, 2> anDimLongitudeIds;
    4719           4 :     NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
    4720             :                              anDimLongitudeIds.data()));
    4721             :     std::array<int, 2> anDimLatitudeIds;
    4722           4 :     NCDF_ERR(
    4723             :         nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
    4724           4 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4725             :     {
    4726           0 :         return false;
    4727             :     }
    4728             : 
    4729           8 :     if (anDimLongitudeIds[0] != anVarDimIds[0] ||
    4730           4 :         anDimLongitudeIds[1] != anVarDimIds[1])
    4731             :     {
    4732           0 :         return false;
    4733             :     }
    4734             : 
    4735           4 :     const char *pszGeolocXFullName = "/location/lon";
    4736           4 :     const char *pszGeolocYFullName = "/location/lat";
    4737             : 
    4738           4 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4739             :              pszGeolocXFullName, pszGeolocYFullName);
    4740             : 
    4741           4 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4742             :                                     "GEOLOCATION");
    4743             : 
    4744           4 :     CPLString osTMP;
    4745           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4746             : 
    4747           4 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4748           4 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4749           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4750             : 
    4751           4 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4752           4 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4753             : 
    4754           4 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4755           4 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4756             : 
    4757           4 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4758           4 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4759             : 
    4760           4 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4761             :                                     "GEOLOCATION");
    4762           4 :     return true;
    4763             : }
    4764             : 
    4765         348 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
    4766             :                                         const std::string &osGeolocWKT,
    4767             :                                         std::string &osGeolocXNameOut,
    4768             :                                         std::string &osGeolocYNameOut)
    4769             : {
    4770         348 :     bool bAddGeoloc = false;
    4771         348 :     char *pszCoordinates = nullptr;
    4772             : 
    4773             :     // If there is no explicit "coordinates" attribute, check if there are
    4774             :     // "lon" and "lat" 2D variables whose dimensions are the last
    4775             :     // 2 ones of the variable of interest.
    4776         348 :     if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
    4777             :         CE_None)
    4778             :     {
    4779         302 :         CPLFree(pszCoordinates);
    4780         302 :         pszCoordinates = nullptr;
    4781             : 
    4782         302 :         int nVarDims = 0;
    4783         302 :         NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4784         302 :         if (nVarDims >= 2)
    4785             :         {
    4786         604 :             std::vector<int> anVarDimIds(nVarDims);
    4787         302 :             NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4788             : 
    4789         302 :             int nLongitudeId = 0;
    4790         302 :             int nLatitudeId = 0;
    4791         370 :             if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
    4792          68 :                 nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
    4793             :             {
    4794          68 :                 int nDimsLongitude = 0;
    4795          68 :                 NCDF_ERR(
    4796             :                     nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
    4797          68 :                 int nDimsLatitude = 0;
    4798          68 :                 NCDF_ERR(
    4799             :                     nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
    4800          68 :                 if (nDimsLongitude == 2 && nDimsLatitude == 2)
    4801             :                 {
    4802          34 :                     std::vector<int> anDimLongitudeIds(2);
    4803          17 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
    4804             :                                              anDimLongitudeIds.data()));
    4805          34 :                     std::vector<int> anDimLatitudeIds(2);
    4806          17 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
    4807             :                                              anDimLatitudeIds.data()));
    4808          17 :                     if (anDimLongitudeIds == anDimLatitudeIds &&
    4809          34 :                         anVarDimIds[anVarDimIds.size() - 2] ==
    4810          51 :                             anDimLongitudeIds[0] &&
    4811          34 :                         anVarDimIds[anVarDimIds.size() - 1] ==
    4812          17 :                             anDimLongitudeIds[1])
    4813             :                     {
    4814          17 :                         pszCoordinates = CPLStrdup("lon lat");
    4815             :                     }
    4816             :                 }
    4817             :             }
    4818             :         }
    4819             :     }
    4820             : 
    4821         348 :     if (pszCoordinates)
    4822             :     {
    4823             :         // Get X and Y geolocation names from coordinates attribute.
    4824             :         const CPLStringList aosCoordinates(
    4825         126 :             NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    4826          63 :         if (aosCoordinates.size() >= 2)
    4827             :         {
    4828             :             char szGeolocXName[NC_MAX_NAME + 1];
    4829             :             char szGeolocYName[NC_MAX_NAME + 1];
    4830          60 :             szGeolocXName[0] = '\0';
    4831          60 :             szGeolocYName[0] = '\0';
    4832             : 
    4833             :             // Test that each variable is longitude/latitude.
    4834         193 :             for (int i = 0; i < aosCoordinates.size(); i++)
    4835             :             {
    4836         133 :                 if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
    4837             :                 {
    4838          49 :                     int nOtherGroupId = -1;
    4839          49 :                     int nOtherVarId = -1;
    4840             :                     // Check that the variable actually exists
    4841             :                     // Needed on Sentinel-3 products
    4842          49 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4843          49 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4844             :                     {
    4845          47 :                         snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
    4846             :                                  aosCoordinates[i]);
    4847             :                     }
    4848             :                 }
    4849          84 :                 else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
    4850             :                 {
    4851          49 :                     int nOtherGroupId = -1;
    4852          49 :                     int nOtherVarId = -1;
    4853             :                     // Check that the variable actually exists
    4854             :                     // Needed on Sentinel-3 products
    4855          49 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4856          49 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4857             :                     {
    4858          47 :                         snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
    4859             :                                  aosCoordinates[i]);
    4860             :                     }
    4861             :                 }
    4862             :             }
    4863             :             // Add GEOLOCATION metadata.
    4864          60 :             if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
    4865             :             {
    4866          47 :                 osGeolocXNameOut = szGeolocXName;
    4867          47 :                 osGeolocYNameOut = szGeolocYName;
    4868             : 
    4869          47 :                 char *pszGeolocXFullName = nullptr;
    4870          47 :                 char *pszGeolocYFullName = nullptr;
    4871          47 :                 if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
    4872          94 :                                            &pszGeolocXFullName) == CE_None &&
    4873          47 :                     NCDFResolveVarFullName(nGroupId, szGeolocYName,
    4874             :                                            &pszGeolocYFullName) == CE_None)
    4875             :                 {
    4876          47 :                     if (bSwitchedXY)
    4877             :                     {
    4878           2 :                         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4879           2 :                         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
    4880             :                                                         "GEOLOCATION");
    4881             :                     }
    4882             : 
    4883          47 :                     bAddGeoloc = true;
    4884          47 :                     CPLDebug("GDAL_netCDF",
    4885             :                              "using variables %s and %s for GEOLOCATION",
    4886             :                              pszGeolocXFullName, pszGeolocYFullName);
    4887             : 
    4888          47 :                     GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4889             :                                                     "GEOLOCATION");
    4890             : 
    4891          94 :                     CPLString osTMP;
    4892          47 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4893          47 :                                  pszGeolocXFullName);
    4894             : 
    4895          47 :                     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
    4896             :                                                     "GEOLOCATION");
    4897          47 :                     GDALPamDataset::SetMetadataItem("X_BAND", "1",
    4898             :                                                     "GEOLOCATION");
    4899          47 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4900          47 :                                  pszGeolocYFullName);
    4901             : 
    4902          47 :                     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
    4903             :                                                     "GEOLOCATION");
    4904          47 :                     GDALPamDataset::SetMetadataItem("Y_BAND", "1",
    4905             :                                                     "GEOLOCATION");
    4906             : 
    4907          47 :                     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
    4908             :                                                     "GEOLOCATION");
    4909          47 :                     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
    4910             :                                                     "GEOLOCATION");
    4911             : 
    4912          47 :                     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
    4913             :                                                     "GEOLOCATION");
    4914          47 :                     GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
    4915             :                                                     "GEOLOCATION");
    4916             : 
    4917          47 :                     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4918             :                                                     "PIXEL_CENTER",
    4919             :                                                     "GEOLOCATION");
    4920             :                 }
    4921             :                 else
    4922             :                 {
    4923           0 :                     CPLDebug("GDAL_netCDF",
    4924             :                              "cannot resolve location of "
    4925             :                              "lat/lon variables specified by the coordinates "
    4926             :                              "attribute [%s]",
    4927             :                              pszCoordinates);
    4928             :                 }
    4929          47 :                 CPLFree(pszGeolocXFullName);
    4930          47 :                 CPLFree(pszGeolocYFullName);
    4931             :             }
    4932             :             else
    4933             :             {
    4934          13 :                 CPLDebug("GDAL_netCDF",
    4935             :                          "coordinates attribute [%s] is unsupported",
    4936             :                          pszCoordinates);
    4937             :             }
    4938             :         }
    4939             :         else
    4940             :         {
    4941           3 :             CPLDebug("GDAL_netCDF",
    4942             :                      "coordinates attribute [%s] with %d element(s) is "
    4943             :                      "unsupported",
    4944             :                      pszCoordinates, aosCoordinates.size());
    4945             :         }
    4946             :     }
    4947             : 
    4948             :     else
    4949             :     {
    4950         285 :         bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
    4951             : 
    4952         285 :         if (!bAddGeoloc)
    4953         284 :             bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
    4954             :     }
    4955             : 
    4956         348 :     CPLFree(pszCoordinates);
    4957             : 
    4958         348 :     return bAddGeoloc;
    4959             : }
    4960             : 
    4961           8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
    4962             :                                        const char *szDimName)
    4963             : {
    4964             :     // Get values.
    4965           8 :     char *pszVarValues = nullptr;
    4966           8 :     CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
    4967           8 :     if (eErr != CE_None)
    4968           0 :         return eErr;
    4969             : 
    4970             :     // Write metadata.
    4971           8 :     char szTemp[NC_MAX_NAME + 1 + 32] = {};
    4972           8 :     snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
    4973           8 :     GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
    4974             : 
    4975           8 :     CPLFree(pszVarValues);
    4976             : 
    4977           8 :     return CE_None;
    4978             : }
    4979             : 
    4980           0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
    4981             :                                         int &nVarLen)
    4982             : {
    4983           0 :     nVarLen = 0;
    4984             : 
    4985             :     // Get Y_VALUES as tokens.
    4986             :     char **papszValues =
    4987           0 :         NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
    4988           0 :     if (papszValues == nullptr)
    4989           0 :         return nullptr;
    4990             : 
    4991             :     // Initialize and fill array.
    4992           0 :     nVarLen = CSLCount(papszValues);
    4993             :     double *pdfVarValues =
    4994           0 :         static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
    4995             : 
    4996           0 :     for (int i = 0, j = 0; i < nVarLen; i++)
    4997             :     {
    4998           0 :         if (!bBottomUp)
    4999           0 :             j = nVarLen - 1 - i;
    5000             :         else
    5001           0 :             j = i;  // Invert latitude values.
    5002           0 :         char *pszTemp = nullptr;
    5003           0 :         pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
    5004             :     }
    5005           0 :     CSLDestroy(papszValues);
    5006             : 
    5007           0 :     return pdfVarValues;
    5008             : }
    5009             : 
    5010             : /************************************************************************/
    5011             : /*                        SetSpatialRefNoUpdate()                       */
    5012             : /************************************************************************/
    5013             : 
    5014         255 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
    5015             : {
    5016         255 :     m_oSRS.Clear();
    5017         255 :     if (poSRS)
    5018         248 :         m_oSRS = *poSRS;
    5019         255 :     m_bHasProjection = true;
    5020         255 : }
    5021             : 
    5022             : /************************************************************************/
    5023             : /*                          SetSpatialRef()                             */
    5024             : /************************************************************************/
    5025             : 
    5026          76 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    5027             : {
    5028         152 :     CPLMutexHolderD(&hNCMutex);
    5029             : 
    5030          76 :     if (GetAccess() != GA_Update || m_bHasProjection)
    5031             :     {
    5032           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5033             :                  "netCDFDataset::_SetProjection() should only be called once "
    5034             :                  "in update mode!");
    5035           0 :         return CE_Failure;
    5036             :     }
    5037             : 
    5038          76 :     if (m_bHasGeoTransform)
    5039             :     {
    5040          32 :         SetSpatialRefNoUpdate(poSRS);
    5041             : 
    5042             :         // For NC4/NC4C, writing both projection variables and data,
    5043             :         // followed by redefining nodata value, cancels the projection
    5044             :         // info from the Band variable, so for now only write the
    5045             :         // variable definitions, and write data at the end.
    5046             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5047          32 :         return AddProjectionVars(true, nullptr, nullptr);
    5048             :     }
    5049             : 
    5050          44 :     SetSpatialRefNoUpdate(poSRS);
    5051             : 
    5052          44 :     return CE_None;
    5053             : }
    5054             : 
    5055             : /************************************************************************/
    5056             : /*                     SetGeoTransformNoUpdate()                        */
    5057             : /************************************************************************/
    5058             : 
    5059         275 : void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
    5060             : {
    5061         275 :     memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
    5062         275 :     m_bHasGeoTransform = true;
    5063         275 : }
    5064             : 
    5065             : /************************************************************************/
    5066             : /*                          SetGeoTransform()                           */
    5067             : /************************************************************************/
    5068             : 
    5069          77 : CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
    5070             : {
    5071         154 :     CPLMutexHolderD(&hNCMutex);
    5072             : 
    5073          77 :     if (GetAccess() != GA_Update || m_bHasGeoTransform)
    5074             :     {
    5075           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5076             :                  "netCDFDataset::SetGeoTransform() should only be called once "
    5077             :                  "in update mode!");
    5078           0 :         return CE_Failure;
    5079             :     }
    5080             : 
    5081          77 :     CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
    5082          77 :              padfTransform[0], padfTransform[1], padfTransform[2],
    5083          77 :              padfTransform[3], padfTransform[4], padfTransform[5]);
    5084             : 
    5085          77 :     if (m_bHasProjection)
    5086             :     {
    5087           3 :         SetGeoTransformNoUpdate(padfTransform);
    5088             : 
    5089             :         // For NC4/NC4C, writing both projection variables and data,
    5090             :         // followed by redefining nodata value, cancels the projection
    5091             :         // info from the Band variable, so for now only write the
    5092             :         // variable definitions, and write data at the end.
    5093             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5094           3 :         return AddProjectionVars(true, nullptr, nullptr);
    5095             :     }
    5096             : 
    5097          74 :     SetGeoTransformNoUpdate(padfTransform);
    5098          74 :     return CE_None;
    5099             : }
    5100             : 
    5101             : /************************************************************************/
    5102             : /*                         NCDFWriteSRSVariable()                       */
    5103             : /************************************************************************/
    5104             : 
    5105         128 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
    5106             :                          char **ppszCFProjection, bool bWriteGDALTags,
    5107             :                          const std::string &srsVarName)
    5108             : {
    5109         128 :     char *pszCFProjection = nullptr;
    5110         128 :     char **papszKeyValues = nullptr;
    5111         128 :     poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
    5112             : 
    5113         128 :     if (bWriteGDALTags)
    5114             :     {
    5115         127 :         const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
    5116         127 :         if (pszWKT)
    5117             :         {
    5118             :             // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
    5119         127 :             papszKeyValues =
    5120         127 :                 CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
    5121             :         }
    5122             :     }
    5123             : 
    5124         128 :     const int nValues = CSLCount(papszKeyValues);
    5125             : 
    5126             :     int NCDFVarID;
    5127         256 :     std::string varNameRadix(pszCFProjection);
    5128         128 :     int nCounter = 2;
    5129             :     while (true)
    5130             :     {
    5131         130 :         NCDFVarID = -1;
    5132         130 :         nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
    5133         130 :         if (NCDFVarID < 0)
    5134         125 :             break;
    5135             : 
    5136           5 :         int nbAttr = 0;
    5137           5 :         NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
    5138           5 :         bool bSame = nbAttr == nValues;
    5139          41 :         for (int i = 0; bSame && (i < nbAttr); i++)
    5140             :         {
    5141             :             char szAttrName[NC_MAX_NAME + 1];
    5142          38 :             szAttrName[0] = 0;
    5143          38 :             NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
    5144             : 
    5145             :             const char *pszValue =
    5146          38 :                 CSLFetchNameValue(papszKeyValues, szAttrName);
    5147          38 :             if (!pszValue)
    5148             :             {
    5149           0 :                 bSame = false;
    5150           2 :                 break;
    5151             :             }
    5152             : 
    5153          38 :             nc_type atttype = NC_NAT;
    5154          38 :             size_t attlen = 0;
    5155          38 :             NCDF_ERR(
    5156             :                 nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
    5157          38 :             if (atttype != NC_CHAR && atttype != NC_DOUBLE)
    5158             :             {
    5159           0 :                 bSame = false;
    5160           0 :                 break;
    5161             :             }
    5162          38 :             if (atttype == NC_CHAR)
    5163             :             {
    5164          15 :                 if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
    5165             :                 {
    5166           0 :                     bSame = false;
    5167           0 :                     break;
    5168             :                 }
    5169          15 :                 std::string val;
    5170          15 :                 val.resize(attlen);
    5171          15 :                 nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
    5172          15 :                 if (val != pszValue)
    5173             :                 {
    5174           0 :                     bSame = false;
    5175           0 :                     break;
    5176             :                 }
    5177             :             }
    5178             :             else
    5179             :             {
    5180             :                 const CPLStringList aosTokens(
    5181          23 :                     CSLTokenizeString2(pszValue, ",", 0));
    5182          23 :                 if (static_cast<size_t>(aosTokens.size()) != attlen)
    5183             :                 {
    5184           0 :                     bSame = false;
    5185           0 :                     break;
    5186             :                 }
    5187             :                 double vals[2];
    5188          23 :                 nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
    5189          44 :                 if (vals[0] != CPLAtof(aosTokens[0]) ||
    5190          21 :                     (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
    5191             :                 {
    5192           2 :                     bSame = false;
    5193           2 :                     break;
    5194             :                 }
    5195             :             }
    5196             :         }
    5197           5 :         if (bSame)
    5198             :         {
    5199           3 :             *ppszCFProjection = pszCFProjection;
    5200           3 :             CSLDestroy(papszKeyValues);
    5201           3 :             return NCDFVarID;
    5202             :         }
    5203           2 :         CPLFree(pszCFProjection);
    5204           2 :         pszCFProjection =
    5205           2 :             CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
    5206           2 :         nCounter++;
    5207           2 :     }
    5208             : 
    5209         125 :     *ppszCFProjection = pszCFProjection;
    5210             : 
    5211             :     const char *pszVarName;
    5212             : 
    5213         125 :     if (srsVarName != "")
    5214             :     {
    5215          38 :         pszVarName = srsVarName.c_str();
    5216             :     }
    5217             :     else
    5218             :     {
    5219          87 :         pszVarName = pszCFProjection;
    5220             :     }
    5221             : 
    5222         125 :     int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
    5223         125 :     NCDF_ERR(status);
    5224        1199 :     for (int i = 0; i < nValues; ++i)
    5225             :     {
    5226        1074 :         char *pszKey = nullptr;
    5227        1074 :         const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
    5228        1074 :         if (pszKey && pszValue)
    5229             :         {
    5230        2148 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
    5231        1074 :             double adfValues[2] = {0, 0};
    5232        1074 :             const int nDoubleCount = std::min(2, aosTokens.size());
    5233        1074 :             if (!(aosTokens.size() == 2 &&
    5234        2147 :                   CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
    5235        1073 :                 CPLGetValueType(pszValue) == CPL_VALUE_STRING)
    5236             :             {
    5237         499 :                 status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
    5238             :                                          strlen(pszValue), pszValue);
    5239             :             }
    5240             :             else
    5241             :             {
    5242        1151 :                 for (int j = 0; j < nDoubleCount; ++j)
    5243         576 :                     adfValues[j] = CPLAtof(aosTokens[j]);
    5244         575 :                 status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
    5245             :                                            nDoubleCount, adfValues);
    5246             :             }
    5247        1074 :             NCDF_ERR(status);
    5248             :         }
    5249        1074 :         CPLFree(pszKey);
    5250             :     }
    5251             : 
    5252         125 :     CSLDestroy(papszKeyValues);
    5253         125 :     return NCDFVarID;
    5254             : }
    5255             : 
    5256             : /************************************************************************/
    5257             : /*                   NCDFWriteLonLatVarsAttributes()                    */
    5258             : /************************************************************************/
    5259             : 
    5260         101 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
    5261             :                                    int nVarLatID)
    5262             : {
    5263             : 
    5264             :     try
    5265             :     {
    5266         101 :         vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
    5267         101 :         vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
    5268         101 :         vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
    5269         101 :         vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
    5270         101 :         vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
    5271         101 :         vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
    5272             :     }
    5273           0 :     catch (nccfdriver::SG_Exception &e)
    5274             :     {
    5275           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5276             :     }
    5277         101 : }
    5278             : 
    5279             : /************************************************************************/
    5280             : /*                   NCDFWriteRLonRLatVarsAttributes()                    */
    5281             : /************************************************************************/
    5282             : 
    5283           0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
    5284             :                                      int nVarRLonID, int nVarRLatID)
    5285             : {
    5286             :     try
    5287             :     {
    5288           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
    5289           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
    5290             :                               "latitude in rotated pole grid");
    5291           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
    5292           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
    5293             : 
    5294           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
    5295           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
    5296             :                               "longitude in rotated pole grid");
    5297           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
    5298           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
    5299             :     }
    5300           0 :     catch (nccfdriver::SG_Exception &e)
    5301             :     {
    5302           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5303             :     }
    5304           0 : }
    5305             : 
    5306             : /************************************************************************/
    5307             : /*                        NCDFGetProjectedCFUnit()                      */
    5308             : /************************************************************************/
    5309             : 
    5310          40 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
    5311             : {
    5312          40 :     char *pszUnitsToWrite = nullptr;
    5313          40 :     poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
    5314          40 :     std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
    5315          40 :     CPLFree(pszUnitsToWrite);
    5316          80 :     return osRet;
    5317             : }
    5318             : 
    5319             : /************************************************************************/
    5320             : /*                     NCDFWriteXYVarsAttributes()                      */
    5321             : /************************************************************************/
    5322             : 
    5323          29 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
    5324             :                                int nVarYID, const OGRSpatialReference *poSRS)
    5325             : {
    5326          58 :     const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
    5327             : 
    5328             :     try
    5329             :     {
    5330          29 :         vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
    5331          29 :         vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
    5332          29 :         if (!osUnitsToWrite.empty())
    5333          29 :             vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
    5334          29 :         vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
    5335          29 :         vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
    5336          29 :         if (!osUnitsToWrite.empty())
    5337          29 :             vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
    5338             :     }
    5339           0 :     catch (nccfdriver::SG_Exception &e)
    5340             :     {
    5341           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5342             :     }
    5343          29 : }
    5344             : 
    5345             : /************************************************************************/
    5346             : /*                          AddProjectionVars()                         */
    5347             : /************************************************************************/
    5348             : 
    5349         164 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
    5350             :                                         GDALProgressFunc pfnProgress,
    5351             :                                         void *pProgressData)
    5352             : {
    5353         164 :     if (nCFVersion >= 1.8)
    5354           0 :         return CE_None;  // do nothing
    5355             : 
    5356         164 :     bool bWriteGridMapping = false;
    5357         164 :     bool bWriteLonLat = false;
    5358         164 :     bool bHasGeoloc = false;
    5359         164 :     bool bWriteGDALTags = false;
    5360         164 :     bool bWriteGeoTransform = false;
    5361             : 
    5362             :     // For GEOLOCATION information.
    5363         164 :     GDALDatasetH hDS_X = nullptr;
    5364         164 :     GDALRasterBandH hBand_X = nullptr;
    5365         164 :     GDALDatasetH hDS_Y = nullptr;
    5366         164 :     GDALRasterBandH hBand_Y = nullptr;
    5367             : 
    5368         328 :     OGRSpatialReference oSRS(m_oSRS);
    5369         164 :     if (!m_oSRS.IsEmpty())
    5370             :     {
    5371         138 :         if (oSRS.IsProjected())
    5372          50 :             bIsProjected = true;
    5373          88 :         else if (oSRS.IsGeographic())
    5374          88 :             bIsGeographic = true;
    5375             :     }
    5376             : 
    5377         164 :     if (bDefsOnly)
    5378             :     {
    5379          82 :         char *pszProjection = nullptr;
    5380          82 :         m_oSRS.exportToWkt(&pszProjection);
    5381          82 :         CPLDebug("GDAL_netCDF",
    5382             :                  "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
    5383          82 :                  pszProjection ? pszProjection : "(null)",
    5384          82 :                  static_cast<int>(bIsProjected),
    5385          82 :                  static_cast<int>(bIsGeographic));
    5386          82 :         CPLFree(pszProjection);
    5387             : 
    5388          82 :         if (!m_bHasGeoTransform)
    5389           5 :             CPLDebug("GDAL_netCDF",
    5390             :                      "netCDFDataset::AddProjectionVars() called, "
    5391             :                      "but GeoTransform has not yet been defined!");
    5392             : 
    5393          82 :         if (!m_bHasProjection)
    5394           6 :             CPLDebug("GDAL_netCDF",
    5395             :                      "netCDFDataset::AddProjectionVars() called, "
    5396             :                      "but Projection has not yet been defined!");
    5397             :     }
    5398             : 
    5399             :     // Check GEOLOCATION information.
    5400         164 :     char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
    5401         164 :     if (papszGeolocationInfo != nullptr)
    5402             :     {
    5403             :         // Look for geolocation datasets.
    5404             :         const char *pszDSName =
    5405          10 :             CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
    5406          10 :         if (pszDSName != nullptr)
    5407          10 :             hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
    5408          10 :         pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
    5409          10 :         if (pszDSName != nullptr)
    5410          10 :             hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
    5411             : 
    5412          10 :         if (hDS_X != nullptr && hDS_Y != nullptr)
    5413             :         {
    5414          10 :             int nBand = std::max(1, atoi(CSLFetchNameValueDef(
    5415          10 :                                         papszGeolocationInfo, "X_BAND", "0")));
    5416          10 :             hBand_X = GDALGetRasterBand(hDS_X, nBand);
    5417          10 :             nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
    5418          10 :                                                           "Y_BAND", "0")));
    5419          10 :             hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
    5420             : 
    5421             :             // If geoloc bands are found, do basic validation based on their
    5422             :             // dimensions.
    5423          10 :             if (hBand_X != nullptr && hBand_Y != nullptr)
    5424             :             {
    5425          10 :                 int nXSize_XBand = GDALGetRasterXSize(hDS_X);
    5426          10 :                 int nYSize_XBand = GDALGetRasterYSize(hDS_X);
    5427          10 :                 int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
    5428          10 :                 int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
    5429             : 
    5430             :                 // TODO 1D geolocation arrays not implemented.
    5431          10 :                 if (nYSize_XBand == 1 && nYSize_YBand == 1)
    5432             :                 {
    5433           0 :                     bHasGeoloc = false;
    5434           0 :                     CPLDebug("GDAL_netCDF",
    5435             :                              "1D GEOLOCATION arrays not supported yet");
    5436             :                 }
    5437             :                 // 2D bands must have same sizes as the raster bands.
    5438          10 :                 else if (nXSize_XBand != nRasterXSize ||
    5439          10 :                          nYSize_XBand != nRasterYSize ||
    5440          10 :                          nXSize_YBand != nRasterXSize ||
    5441          10 :                          nYSize_YBand != nRasterYSize)
    5442             :                 {
    5443           0 :                     bHasGeoloc = false;
    5444           0 :                     CPLDebug("GDAL_netCDF",
    5445             :                              "GEOLOCATION array sizes (%dx%d %dx%d) differ "
    5446             :                              "from raster (%dx%d), not supported",
    5447             :                              nXSize_XBand, nYSize_XBand, nXSize_YBand,
    5448             :                              nYSize_YBand, nRasterXSize, nRasterYSize);
    5449             :                 }
    5450             :                 else
    5451             :                 {
    5452          10 :                     bHasGeoloc = true;
    5453          10 :                     CPLDebug("GDAL_netCDF",
    5454             :                              "dataset has GEOLOCATION information, will try to "
    5455             :                              "write it");
    5456             :                 }
    5457             :             }
    5458             :         }
    5459             :     }
    5460             : 
    5461             :     // Process projection options.
    5462         164 :     if (bIsProjected)
    5463             :     {
    5464             :         bool bIsCfProjection =
    5465          50 :             oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
    5466          50 :         bWriteGridMapping = true;
    5467          50 :         bWriteGDALTags = CPL_TO_BOOL(
    5468          50 :             CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
    5469             :         // Force WRITE_GDAL_TAGS if is not a CF projection.
    5470          50 :         if (!bWriteGDALTags && !bIsCfProjection)
    5471           0 :             bWriteGDALTags = true;
    5472          50 :         if (bWriteGDALTags)
    5473          50 :             bWriteGeoTransform = true;
    5474             : 
    5475             :         // Write lon/lat: default is NO, except if has geolocation.
    5476             :         // With IF_NEEDED: write if has geoloc or is not CF projection.
    5477             :         const char *pszValue =
    5478          50 :             CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
    5479          50 :         if (pszValue)
    5480             :         {
    5481           2 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5482             :             {
    5483           0 :                 bWriteLonLat = bHasGeoloc || !bIsCfProjection;
    5484             :             }
    5485             :             else
    5486             :             {
    5487           2 :                 bWriteLonLat = CPLTestBool(pszValue);
    5488             :             }
    5489             :         }
    5490             :         else
    5491             :         {
    5492          48 :             bWriteLonLat = bHasGeoloc;
    5493             :         }
    5494             : 
    5495             :         // Save value of pszCFCoordinates for later.
    5496          50 :         if (bWriteLonLat)
    5497             :         {
    5498           4 :             pszCFCoordinates = NCDF_LONLAT;
    5499             :         }
    5500             :     }
    5501             :     else
    5502             :     {
    5503             :         // Files without a Datum will not have a grid_mapping variable and
    5504             :         // geographic information.
    5505         114 :         bWriteGridMapping = bIsGeographic;
    5506             : 
    5507         114 :         if (bHasGeoloc)
    5508             :         {
    5509           8 :             bWriteLonLat = true;
    5510             :         }
    5511             :         else
    5512             :         {
    5513         106 :             bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
    5514         106 :                 papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
    5515         106 :             if (bWriteGDALTags)
    5516          88 :                 bWriteGeoTransform = true;
    5517             : 
    5518         106 :             const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
    5519             :                                                         "WRITE_LONLAT", "YES");
    5520         106 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5521           0 :                 bWriteLonLat = true;
    5522             :             else
    5523         106 :                 bWriteLonLat = CPLTestBool(pszValue);
    5524             :             //  Don't write lon/lat if no source geotransform.
    5525         106 :             if (!m_bHasGeoTransform)
    5526           0 :                 bWriteLonLat = false;
    5527             :             // If we don't write lon/lat, set dimnames to X/Y and write gdal
    5528             :             // tags.
    5529         106 :             if (!bWriteLonLat)
    5530             :             {
    5531           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5532             :                          "creating geographic file without lon/lat values!");
    5533           0 :                 if (m_bHasGeoTransform)
    5534             :                 {
    5535           0 :                     bWriteGDALTags = true;  // Not desirable if no geotransform.
    5536           0 :                     bWriteGeoTransform = true;
    5537             :                 }
    5538             :             }
    5539             :         }
    5540             :     }
    5541             : 
    5542             :     // Make sure we write grid_mapping if we need to write GDAL tags.
    5543         164 :     if (bWriteGDALTags)
    5544         138 :         bWriteGridMapping = true;
    5545             : 
    5546             :     // bottom-up value: new driver is bottom-up by default.
    5547             :     // Override with WRITE_BOTTOMUP.
    5548         164 :     bBottomUp = CPL_TO_BOOL(
    5549         164 :         CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
    5550             : 
    5551         164 :     if (bDefsOnly)
    5552             :     {
    5553          82 :         CPLDebug(
    5554             :             "GDAL_netCDF",
    5555             :             "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
    5556             :             "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
    5557          82 :             static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
    5558             :             static_cast<int>(bWriteGridMapping),
    5559             :             static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
    5560          82 :             static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
    5561             :     }
    5562             : 
    5563             :     // Exit if nothing to do.
    5564         164 :     if (!bIsProjected && !bWriteLonLat)
    5565           0 :         return CE_None;
    5566             : 
    5567             :     // Define dimension names.
    5568             : 
    5569         164 :     constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
    5570             : 
    5571         164 :     if (bDefsOnly)
    5572             :     {
    5573          82 :         int nVarLonID = -1;
    5574          82 :         int nVarLatID = -1;
    5575          82 :         int nVarXID = -1;
    5576          82 :         int nVarYID = -1;
    5577             : 
    5578          82 :         m_bAddedProjectionVarsDefs = true;
    5579             : 
    5580             :         // Make sure we are in define mode.
    5581          82 :         SetDefineMode(true);
    5582             : 
    5583             :         // Write projection attributes.
    5584          82 :         if (bWriteGridMapping)
    5585             :         {
    5586          69 :             const int NCDFVarID = NCDFWriteSRSVariable(
    5587             :                 cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
    5588          69 :             if (NCDFVarID < 0)
    5589           0 :                 return CE_Failure;
    5590             : 
    5591             :             // Optional GDAL custom projection tags.
    5592          69 :             if (bWriteGDALTags)
    5593             :             {
    5594         138 :                 CPLString osGeoTransform;
    5595         483 :                 for (int i = 0; i < 6; i++)
    5596             :                 {
    5597             :                     osGeoTransform +=
    5598         414 :                         CPLSPrintf("%.17g ", m_adfGeoTransform[i]);
    5599             :                 }
    5600          69 :                 CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
    5601             :                          osGeoTransform.c_str());
    5602             : 
    5603             :                 // if( strlen(pszProj4Defn) > 0 ) {
    5604             :                 //     nc_put_att_text(cdfid, NCDFVarID, "proj4",
    5605             :                 //                      strlen(pszProj4Defn), pszProj4Defn);
    5606             :                 // }
    5607             : 
    5608             :                 // For now, write the geotransform for back-compat or else
    5609             :                 // the old (1.8.1) driver overrides the CF geotransform with
    5610             :                 // empty values from dfNN, dfSN, dfEE, dfWE;
    5611             : 
    5612             :                 // TODO: fix this in 1.8 branch, and then remove this here.
    5613          69 :                 if (bWriteGeoTransform && m_bHasGeoTransform)
    5614             :                 {
    5615             :                     {
    5616          68 :                         const int status = nc_put_att_text(
    5617             :                             cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
    5618             :                             osGeoTransform.size(), osGeoTransform.c_str());
    5619          68 :                         NCDF_ERR(status);
    5620             :                     }
    5621             :                 }
    5622             :             }
    5623             : 
    5624             :             // Write projection variable to band variable.
    5625             :             // Need to call later if there are no bands.
    5626          69 :             AddGridMappingRef();
    5627             :         }  // end if( bWriteGridMapping )
    5628             : 
    5629             :         // Write CF Projection vars.
    5630             : 
    5631          82 :         const bool bIsRotatedPole =
    5632         151 :             pszCFProjection != nullptr &&
    5633          69 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5634          82 :         if (bIsRotatedPole)
    5635             :         {
    5636             :             // Rename dims to rlat/rlon.
    5637             :             papszDimName
    5638           0 :                 .Clear();  // If we add other dims one day, this has to change
    5639           0 :             papszDimName.AddString(NCDF_DIMNAME_RLAT);
    5640           0 :             papszDimName.AddString(NCDF_DIMNAME_RLON);
    5641             : 
    5642           0 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
    5643           0 :             NCDF_ERR(status);
    5644           0 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
    5645           0 :             NCDF_ERR(status);
    5646             :         }
    5647             :         // Rename dimensions if lon/lat.
    5648          82 :         else if (!bIsProjected && !bHasGeoloc)
    5649             :         {
    5650             :             // Rename dims to lat/lon.
    5651             :             papszDimName
    5652          53 :                 .Clear();  // If we add other dims one day, this has to change
    5653          53 :             papszDimName.AddString(NCDF_DIMNAME_LAT);
    5654          53 :             papszDimName.AddString(NCDF_DIMNAME_LON);
    5655             : 
    5656          53 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
    5657          53 :             NCDF_ERR(status);
    5658          53 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
    5659          53 :             NCDF_ERR(status);
    5660             :         }
    5661             : 
    5662             :         // Write X/Y attributes.
    5663             :         else /* if( bIsProjected || bHasGeoloc ) */
    5664             :         {
    5665             :             // X
    5666             :             int anXDims[1];
    5667          29 :             anXDims[0] = nXDimID;
    5668          29 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5669             :                      CF_PROJ_X_VAR_NAME, NC_DOUBLE);
    5670          29 :             int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
    5671             :                                     anXDims, &nVarXID);
    5672          29 :             NCDF_ERR(status);
    5673             : 
    5674             :             // Y
    5675             :             int anYDims[1];
    5676          29 :             anYDims[0] = nYDimID;
    5677          29 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5678             :                      CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
    5679          29 :             status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
    5680             :                                 anYDims, &nVarYID);
    5681          29 :             NCDF_ERR(status);
    5682             : 
    5683          29 :             if (bIsProjected)
    5684             :             {
    5685          25 :                 NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
    5686             :             }
    5687             :             else
    5688             :             {
    5689           4 :                 CPLAssert(bHasGeoloc);
    5690             :                 try
    5691             :                 {
    5692           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
    5693           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
    5694             :                                           "x-coordinate in Cartesian system");
    5695           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
    5696           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
    5697           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
    5698             :                                           "y-coordinate in Cartesian system");
    5699           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
    5700             : 
    5701           4 :                     pszCFCoordinates = NCDF_LONLAT;
    5702             :                 }
    5703           0 :                 catch (nccfdriver::SG_Exception &e)
    5704             :                 {
    5705           0 :                     CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5706           0 :                     return CE_Failure;
    5707             :                 }
    5708             :             }
    5709             :         }
    5710             : 
    5711             :         // Write lat/lon attributes if needed.
    5712          82 :         if (bWriteLonLat)
    5713             :         {
    5714          59 :             int *panLatDims = nullptr;
    5715          59 :             int *panLonDims = nullptr;
    5716          59 :             int nLatDims = -1;
    5717          59 :             int nLonDims = -1;
    5718             : 
    5719             :             // Get information.
    5720          59 :             if (bHasGeoloc)
    5721             :             {
    5722             :                 // Geoloc
    5723           5 :                 nLatDims = 2;
    5724             :                 panLatDims =
    5725           5 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5726           5 :                 panLatDims[0] = nYDimID;
    5727           5 :                 panLatDims[1] = nXDimID;
    5728           5 :                 nLonDims = 2;
    5729             :                 panLonDims =
    5730           5 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5731           5 :                 panLonDims[0] = nYDimID;
    5732           5 :                 panLonDims[1] = nXDimID;
    5733             :             }
    5734          54 :             else if (bIsProjected)
    5735             :             {
    5736             :                 // Projected
    5737           1 :                 nLatDims = 2;
    5738             :                 panLatDims =
    5739           1 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5740           1 :                 panLatDims[0] = nYDimID;
    5741           1 :                 panLatDims[1] = nXDimID;
    5742           1 :                 nLonDims = 2;
    5743             :                 panLonDims =
    5744           1 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5745           1 :                 panLonDims[0] = nYDimID;
    5746           1 :                 panLonDims[1] = nXDimID;
    5747             :             }
    5748             :             else
    5749             :             {
    5750             :                 // Geographic
    5751          53 :                 nLatDims = 1;
    5752             :                 panLatDims =
    5753          53 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5754          53 :                 panLatDims[0] = nYDimID;
    5755          53 :                 nLonDims = 1;
    5756             :                 panLonDims =
    5757          53 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5758          53 :                 panLonDims[0] = nXDimID;
    5759             :             }
    5760             : 
    5761          59 :             nc_type eLonLatType = NC_NAT;
    5762          59 :             if (bIsProjected)
    5763             :             {
    5764           2 :                 eLonLatType = NC_FLOAT;
    5765           4 :                 const char *pszValue = CSLFetchNameValueDef(
    5766           2 :                     papszCreationOptions, "TYPE_LONLAT", "FLOAT");
    5767           2 :                 if (EQUAL(pszValue, "DOUBLE"))
    5768           0 :                     eLonLatType = NC_DOUBLE;
    5769             :             }
    5770             :             else
    5771             :             {
    5772          57 :                 eLonLatType = NC_DOUBLE;
    5773         114 :                 const char *pszValue = CSLFetchNameValueDef(
    5774          57 :                     papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
    5775          57 :                 if (EQUAL(pszValue, "FLOAT"))
    5776           0 :                     eLonLatType = NC_FLOAT;
    5777             :             }
    5778             : 
    5779             :             // Def vars and attributes.
    5780             :             {
    5781          59 :                 const char *pszVarName =
    5782          59 :                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
    5783          59 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5784             :                                         nLatDims, panLatDims, &nVarLatID);
    5785          59 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5786             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
    5787          59 :                 NCDF_ERR(status);
    5788          59 :                 DefVarDeflate(nVarLatID, false);  // Don't set chunking.
    5789             :             }
    5790             : 
    5791             :             {
    5792          59 :                 const char *pszVarName =
    5793          59 :                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
    5794          59 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5795             :                                         nLonDims, panLonDims, &nVarLonID);
    5796          59 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5797             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
    5798          59 :                 NCDF_ERR(status);
    5799          59 :                 DefVarDeflate(nVarLonID, false);  // Don't set chunking.
    5800             :             }
    5801             : 
    5802          59 :             if (bIsRotatedPole)
    5803           0 :                 NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
    5804             :                                                 nVarLatID);
    5805             :             else
    5806          59 :                 NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
    5807             : 
    5808          59 :             CPLFree(panLatDims);
    5809          59 :             CPLFree(panLonDims);
    5810             :         }
    5811             :     }
    5812             : 
    5813         164 :     if (!bDefsOnly)
    5814             :     {
    5815          82 :         m_bAddedProjectionVarsData = true;
    5816             : 
    5817          82 :         int nVarXID = -1;
    5818          82 :         int nVarYID = -1;
    5819             : 
    5820          82 :         nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
    5821          82 :         nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
    5822             : 
    5823          82 :         int nVarLonID = -1;
    5824          82 :         int nVarLatID = -1;
    5825             : 
    5826          82 :         const bool bIsRotatedPole =
    5827         151 :             pszCFProjection != nullptr &&
    5828          69 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5829          82 :         nc_inq_varid(cdfid,
    5830             :                      bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
    5831             :                      &nVarLonID);
    5832          82 :         nc_inq_varid(cdfid,
    5833             :                      bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
    5834             :                      &nVarLatID);
    5835             : 
    5836             :         // Get projection values.
    5837             : 
    5838          82 :         double *padLonVal = nullptr;
    5839          82 :         double *padLatVal = nullptr;
    5840             : 
    5841          82 :         if (bIsProjected)
    5842             :         {
    5843          25 :             OGRSpatialReference *poLatLonSRS = nullptr;
    5844          25 :             OGRCoordinateTransformation *poTransform = nullptr;
    5845             : 
    5846             :             size_t startX[1];
    5847             :             size_t countX[1];
    5848             :             size_t startY[1];
    5849             :             size_t countY[1];
    5850             : 
    5851          25 :             CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
    5852             : 
    5853             :             double *padXVal =
    5854          25 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    5855             :             double *padYVal =
    5856          25 :                 static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
    5857             : 
    5858             :             // Get Y values.
    5859          25 :             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
    5860             :                                              // Invert latitude values.
    5861          25 :                                     m_adfGeoTransform[3] +
    5862          25 :                                         (m_adfGeoTransform[5] * nRasterYSize);
    5863          25 :             const double dfDY = m_adfGeoTransform[5];
    5864             : 
    5865        1456 :             for (int j = 0; j < nRasterYSize; j++)
    5866             :             {
    5867             :                 // The data point is centered inside the pixel.
    5868        1431 :                 if (!bBottomUp)
    5869           0 :                     padYVal[j] = dfY0 + (j + 0.5) * dfDY;
    5870             :                 else  // Invert latitude values.
    5871        1431 :                     padYVal[j] = dfY0 - (j + 0.5) * dfDY;
    5872             :             }
    5873          25 :             startX[0] = 0;
    5874          25 :             countX[0] = nRasterXSize;
    5875             : 
    5876             :             // Get X values.
    5877          25 :             const double dfX0 = m_adfGeoTransform[0];
    5878          25 :             const double dfDX = m_adfGeoTransform[1];
    5879             : 
    5880        1477 :             for (int i = 0; i < nRasterXSize; i++)
    5881             :             {
    5882             :                 // The data point is centered inside the pixel.
    5883        1452 :                 padXVal[i] = dfX0 + (i + 0.5) * dfDX;
    5884             :             }
    5885          25 :             startY[0] = 0;
    5886          25 :             countY[0] = nRasterYSize;
    5887             : 
    5888             :             // Write X/Y values.
    5889             : 
    5890             :             // Make sure we are in data mode.
    5891          25 :             SetDefineMode(false);
    5892             : 
    5893          25 :             CPLDebug("GDAL_netCDF", "Writing X values");
    5894             :             int status =
    5895          25 :                 nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
    5896          25 :             NCDF_ERR(status);
    5897             : 
    5898          25 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    5899             :             status =
    5900          25 :                 nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
    5901          25 :             NCDF_ERR(status);
    5902             : 
    5903          25 :             if (pfnProgress)
    5904          21 :                 pfnProgress(0.20, nullptr, pProgressData);
    5905             : 
    5906             :             // Write lon/lat arrays (CF coordinates) if requested.
    5907             : 
    5908             :             // Get OGR transform if GEOLOCATION is not available.
    5909          25 :             if (bWriteLonLat && !bHasGeoloc)
    5910             :             {
    5911           1 :                 poLatLonSRS = m_oSRS.CloneGeogCS();
    5912           1 :                 if (poLatLonSRS != nullptr)
    5913             :                 {
    5914           1 :                     poLatLonSRS->SetAxisMappingStrategy(
    5915             :                         OAMS_TRADITIONAL_GIS_ORDER);
    5916             :                     poTransform =
    5917           1 :                         OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
    5918             :                 }
    5919             :                 // If no OGR transform, then don't write CF lon/lat.
    5920           1 :                 if (poTransform == nullptr)
    5921             :                 {
    5922           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5923             :                              "Unable to get Coordinate Transform");
    5924           0 :                     bWriteLonLat = false;
    5925             :                 }
    5926             :             }
    5927             : 
    5928          25 :             if (bWriteLonLat)
    5929             :             {
    5930           2 :                 if (!bHasGeoloc)
    5931           1 :                     CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
    5932             :                 else
    5933           1 :                     CPLDebug("GDAL_netCDF",
    5934             :                              "Writing (lon,lat) from GEOLOCATION arrays");
    5935             : 
    5936           2 :                 bool bOK = true;
    5937           2 :                 double dfProgress = 0.2;
    5938             : 
    5939           2 :                 size_t start[] = {0, 0};
    5940           2 :                 size_t count[] = {1, (size_t)nRasterXSize};
    5941             :                 padLatVal = static_cast<double *>(
    5942           2 :                     CPLMalloc(nRasterXSize * sizeof(double)));
    5943             :                 padLonVal = static_cast<double *>(
    5944           2 :                     CPLMalloc(nRasterXSize * sizeof(double)));
    5945             : 
    5946          61 :                 for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
    5947             :                      j++)
    5948             :                 {
    5949          59 :                     start[0] = j;
    5950             : 
    5951             :                     // Get values from geotransform.
    5952          59 :                     if (!bHasGeoloc)
    5953             :                     {
    5954             :                         // Fill values to transform.
    5955         420 :                         for (int i = 0; i < nRasterXSize; i++)
    5956             :                         {
    5957         400 :                             padLatVal[i] = padYVal[j];
    5958         400 :                             padLonVal[i] = padXVal[i];
    5959             :                         }
    5960             : 
    5961             :                         // Do the transform.
    5962          20 :                         bOK = CPL_TO_BOOL(poTransform->Transform(
    5963          20 :                             nRasterXSize, padLonVal, padLatVal, nullptr));
    5964          20 :                         if (!bOK)
    5965             :                         {
    5966           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5967             :                                      "Unable to Transform (X,Y) to (lon,lat).");
    5968             :                         }
    5969             :                     }
    5970             :                     // Get values from geoloc arrays.
    5971             :                     else
    5972             :                     {
    5973          39 :                         CPLErr eErr = GDALRasterIO(
    5974             :                             hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
    5975             :                             nRasterXSize, 1, GDT_Float64, 0, 0);
    5976          39 :                         if (eErr == CE_None)
    5977             :                         {
    5978          39 :                             eErr = GDALRasterIO(
    5979             :                                 hBand_X, GF_Read, 0, j, nRasterXSize, 1,
    5980             :                                 padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
    5981             :                         }
    5982             : 
    5983          39 :                         if (eErr == CE_None)
    5984             :                         {
    5985          39 :                             bOK = true;
    5986             :                         }
    5987             :                         else
    5988             :                         {
    5989           0 :                             bOK = false;
    5990           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5991             :                                      "Unable to get scanline %d", j);
    5992             :                         }
    5993             :                     }
    5994             : 
    5995             :                     // Write data.
    5996          59 :                     if (bOK)
    5997             :                     {
    5998          59 :                         status = nc_put_vara_double(cdfid, nVarLatID, start,
    5999             :                                                     count, padLatVal);
    6000          59 :                         NCDF_ERR(status);
    6001          59 :                         status = nc_put_vara_double(cdfid, nVarLonID, start,
    6002             :                                                     count, padLonVal);
    6003          59 :                         NCDF_ERR(status);
    6004             :                     }
    6005             : 
    6006          59 :                     if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6007          59 :                         (j % (nRasterYSize / 10) == 0))
    6008             :                     {
    6009          23 :                         dfProgress += 0.08;
    6010          23 :                         pfnProgress(dfProgress, nullptr, pProgressData);
    6011             :                     }
    6012             :                 }
    6013             :             }
    6014             : 
    6015          25 :             if (poLatLonSRS != nullptr)
    6016           1 :                 delete poLatLonSRS;
    6017          25 :             if (poTransform != nullptr)
    6018           1 :                 delete poTransform;
    6019             : 
    6020          25 :             CPLFree(padXVal);
    6021          25 :             CPLFree(padYVal);
    6022             :         }  // Projected
    6023             : 
    6024             :         // If not projected/geographic and has geoloc
    6025          57 :         else if (!bIsGeographic && bHasGeoloc)
    6026             :         {
    6027             :             // Use
    6028             :             // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
    6029             : 
    6030           4 :             bool bOK = true;
    6031           4 :             double dfProgress = 0.2;
    6032             : 
    6033             :             // Make sure we are in data mode.
    6034           4 :             SetDefineMode(false);
    6035             : 
    6036             :             size_t startX[1];
    6037             :             size_t countX[1];
    6038             :             size_t startY[1];
    6039             :             size_t countY[1];
    6040           4 :             startX[0] = 0;
    6041           4 :             countX[0] = nRasterXSize;
    6042             : 
    6043           4 :             startY[0] = 0;
    6044           4 :             countY[0] = nRasterYSize;
    6045             : 
    6046           8 :             std::vector<double> adfXVal(nRasterXSize);
    6047          16 :             for (int i = 0; i < nRasterXSize; i++)
    6048          12 :                 adfXVal[i] = i;
    6049             : 
    6050           8 :             std::vector<double> adfYVal(nRasterYSize);
    6051          12 :             for (int i = 0; i < nRasterYSize; i++)
    6052           8 :                 adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
    6053             : 
    6054           4 :             CPLDebug("GDAL_netCDF", "Writing X values");
    6055           4 :             int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
    6056           4 :                                             adfXVal.data());
    6057           4 :             NCDF_ERR(status);
    6058             : 
    6059           4 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    6060           4 :             status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
    6061           4 :                                         adfYVal.data());
    6062           4 :             NCDF_ERR(status);
    6063             : 
    6064           4 :             if (pfnProgress)
    6065           0 :                 pfnProgress(0.20, nullptr, pProgressData);
    6066             : 
    6067           4 :             size_t start[] = {0, 0};
    6068           4 :             size_t count[] = {1, (size_t)nRasterXSize};
    6069             :             padLatVal =
    6070           4 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    6071             :             padLonVal =
    6072           4 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    6073             : 
    6074          12 :             for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
    6075             :             {
    6076           8 :                 start[0] = j;
    6077             : 
    6078           8 :                 CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
    6079           8 :                                            bBottomUp ? nRasterYSize - 1 - j : j,
    6080             :                                            nRasterXSize, 1, padLatVal,
    6081             :                                            nRasterXSize, 1, GDT_Float64, 0, 0);
    6082           8 :                 if (eErr == CE_None)
    6083             :                 {
    6084           8 :                     eErr = GDALRasterIO(hBand_X, GF_Read, 0,
    6085           8 :                                         bBottomUp ? nRasterYSize - 1 - j : j,
    6086             :                                         nRasterXSize, 1, padLonVal,
    6087             :                                         nRasterXSize, 1, GDT_Float64, 0, 0);
    6088             :                 }
    6089             : 
    6090           8 :                 if (eErr == CE_None)
    6091             :                 {
    6092           8 :                     bOK = true;
    6093             :                 }
    6094             :                 else
    6095             :                 {
    6096           0 :                     bOK = false;
    6097           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    6098             :                              "Unable to get scanline %d", j);
    6099             :                 }
    6100             : 
    6101             :                 // Write data.
    6102           8 :                 if (bOK)
    6103             :                 {
    6104           8 :                     status = nc_put_vara_double(cdfid, nVarLatID, start, count,
    6105             :                                                 padLatVal);
    6106           8 :                     NCDF_ERR(status);
    6107           8 :                     status = nc_put_vara_double(cdfid, nVarLonID, start, count,
    6108             :                                                 padLonVal);
    6109           8 :                     NCDF_ERR(status);
    6110             :                 }
    6111             : 
    6112           8 :                 if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6113           0 :                     (j % (nRasterYSize / 10) == 0))
    6114             :                 {
    6115           0 :                     dfProgress += 0.08;
    6116           0 :                     pfnProgress(dfProgress, nullptr, pProgressData);
    6117             :                 }
    6118           4 :             }
    6119             :         }
    6120             : 
    6121             :         // If not projected, assume geographic to catch grids without Datum.
    6122          53 :         else if (bWriteLonLat)
    6123             :         {
    6124             :             // Get latitude values.
    6125          53 :             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
    6126             :                                              // Invert latitude values.
    6127          53 :                                     m_adfGeoTransform[3] +
    6128          53 :                                         (m_adfGeoTransform[5] * nRasterYSize);
    6129          53 :             const double dfDY = m_adfGeoTransform[5];
    6130             : 
    6131             :             // Override lat values with the ones in GEOLOCATION/Y_VALUES.
    6132          53 :             if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
    6133             :                 nullptr)
    6134             :             {
    6135           0 :                 int nTemp = 0;
    6136           0 :                 padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
    6137             :                 // Make sure we got the correct amount, if not fallback to GT */
    6138             :                 // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
    6139           0 :                 if (nTemp == nRasterYSize)
    6140             :                 {
    6141           0 :                     CPLDebug(
    6142             :                         "GDAL_netCDF",
    6143             :                         "Using Y_VALUES geolocation metadata for lat values");
    6144             :                 }
    6145             :                 else
    6146             :                 {
    6147           0 :                     CPLDebug("GDAL_netCDF",
    6148             :                              "Got %d elements from Y_VALUES geolocation "
    6149             :                              "metadata, need %d",
    6150             :                              nTemp, nRasterYSize);
    6151           0 :                     if (padLatVal)
    6152             :                     {
    6153           0 :                         CPLFree(padLatVal);
    6154           0 :                         padLatVal = nullptr;
    6155             :                     }
    6156             :                 }
    6157             :             }
    6158             : 
    6159          53 :             if (padLatVal == nullptr)
    6160             :             {
    6161             :                 padLatVal = static_cast<double *>(
    6162          53 :                     CPLMalloc(nRasterYSize * sizeof(double)));
    6163        7105 :                 for (int i = 0; i < nRasterYSize; i++)
    6164             :                 {
    6165             :                     // The data point is centered inside the pixel.
    6166        7052 :                     if (!bBottomUp)
    6167           0 :                         padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
    6168             :                     else  // Invert latitude values.
    6169        7052 :                         padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
    6170             :                 }
    6171             :             }
    6172             : 
    6173          53 :             size_t startLat[1] = {0};
    6174          53 :             size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
    6175             : 
    6176             :             // Get longitude values.
    6177          53 :             const double dfX0 = m_adfGeoTransform[0];
    6178          53 :             const double dfDX = m_adfGeoTransform[1];
    6179             : 
    6180             :             padLonVal =
    6181          53 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    6182        7157 :             for (int i = 0; i < nRasterXSize; i++)
    6183             :             {
    6184             :                 // The data point is centered inside the pixel.
    6185        7104 :                 padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
    6186             :             }
    6187             : 
    6188          53 :             size_t startLon[1] = {0};
    6189          53 :             size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
    6190             : 
    6191             :             // Write latitude and longitude values.
    6192             : 
    6193             :             // Make sure we are in data mode.
    6194          53 :             SetDefineMode(false);
    6195             : 
    6196             :             // Write values.
    6197          53 :             CPLDebug("GDAL_netCDF", "Writing lat values");
    6198             : 
    6199          53 :             int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
    6200             :                                             countLat, padLatVal);
    6201          53 :             NCDF_ERR(status);
    6202             : 
    6203          53 :             CPLDebug("GDAL_netCDF", "Writing lon values");
    6204          53 :             status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
    6205             :                                         padLonVal);
    6206          53 :             NCDF_ERR(status);
    6207             : 
    6208             :         }  // Not projected.
    6209             : 
    6210          82 :         CPLFree(padLatVal);
    6211          82 :         CPLFree(padLonVal);
    6212             : 
    6213          82 :         if (pfnProgress)
    6214          41 :             pfnProgress(1.00, nullptr, pProgressData);
    6215             :     }
    6216             : 
    6217         164 :     if (hDS_X != nullptr)
    6218             :     {
    6219          10 :         GDALClose(hDS_X);
    6220             :     }
    6221         164 :     if (hDS_Y != nullptr)
    6222             :     {
    6223          10 :         GDALClose(hDS_Y);
    6224             :     }
    6225             : 
    6226         164 :     return CE_None;
    6227             : }
    6228             : 
    6229             : // Write Projection variable to band variable.
    6230             : // Moved from AddProjectionVars() for cases when bands are added after
    6231             : // projection.
    6232         375 : bool netCDFDataset::AddGridMappingRef()
    6233             : {
    6234         375 :     bool bRet = true;
    6235         375 :     bool bOldDefineMode = bDefineMode;
    6236             : 
    6237         566 :     if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
    6238         191 :         ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
    6239         185 :          (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
    6240             :     {
    6241          73 :         bAddedGridMappingRef = true;
    6242             : 
    6243             :         // Make sure we are in define mode.
    6244          73 :         SetDefineMode(true);
    6245             : 
    6246         192 :         for (int i = 1; i <= nBands; i++)
    6247             :         {
    6248             :             const int nVarId =
    6249         119 :                 static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
    6250             : 
    6251         119 :             if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
    6252             :             {
    6253             :                 int status =
    6254         230 :                     nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
    6255         115 :                                     strlen(pszCFProjection), pszCFProjection);
    6256         115 :                 NCDF_ERR(status);
    6257         115 :                 if (status != NC_NOERR)
    6258           0 :                     bRet = false;
    6259             :             }
    6260         119 :             if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
    6261             :             {
    6262             :                 int status =
    6263           6 :                     nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
    6264             :                                     strlen(pszCFCoordinates), pszCFCoordinates);
    6265           6 :                 NCDF_ERR(status);
    6266           6 :                 if (status != NC_NOERR)
    6267           0 :                     bRet = false;
    6268             :             }
    6269             :         }
    6270             : 
    6271             :         // Go back to previous define mode.
    6272          73 :         SetDefineMode(bOldDefineMode);
    6273             :     }
    6274         375 :     return bRet;
    6275             : }
    6276             : 
    6277             : /************************************************************************/
    6278             : /*                          GetGeoTransform()                           */
    6279             : /************************************************************************/
    6280             : 
    6281         117 : CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
    6282             : 
    6283             : {
    6284         117 :     memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
    6285         117 :     if (m_bHasGeoTransform)
    6286          86 :         return CE_None;
    6287             : 
    6288          31 :     return GDALPamDataset::GetGeoTransform(padfTransform);
    6289             : }
    6290             : 
    6291             : /************************************************************************/
    6292             : /*                                rint()                                */
    6293             : /************************************************************************/
    6294             : 
    6295           0 : double netCDFDataset::rint(double dfX)
    6296             : {
    6297           0 :     return std::round(dfX);
    6298             : }
    6299             : 
    6300             : /************************************************************************/
    6301             : /*                          NCDFReadIsoMetadata()                       */
    6302             : /************************************************************************/
    6303             : 
    6304          16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
    6305             : {
    6306          16 :     int nbAttr = 0;
    6307          16 :     NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
    6308             : 
    6309          32 :     std::map<std::string, CPLJSONArray> oMapNameToArray;
    6310          40 :     for (int l = 0; l < nbAttr; l++)
    6311             :     {
    6312             :         char szAttrName[NC_MAX_NAME + 1];
    6313          24 :         szAttrName[0] = 0;
    6314          24 :         NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
    6315             : 
    6316          24 :         char *pszMetaValue = nullptr;
    6317          24 :         if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
    6318             :         {
    6319          24 :             nc_type nAttrType = NC_NAT;
    6320          24 :             size_t nAttrLen = 0;
    6321             : 
    6322          24 :             NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
    6323             :                                 &nAttrLen));
    6324             : 
    6325          24 :             std::string osAttrName(szAttrName);
    6326          24 :             const auto sharpPos = osAttrName.find('#');
    6327          24 :             if (sharpPos == std::string::npos)
    6328             :             {
    6329          16 :                 if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
    6330           4 :                     obj.Add(osAttrName, CPLAtof(pszMetaValue));
    6331             :                 else
    6332          12 :                     obj.Add(osAttrName, pszMetaValue);
    6333             :             }
    6334             :             else
    6335             :             {
    6336           8 :                 osAttrName.resize(sharpPos);
    6337           8 :                 auto iter = oMapNameToArray.find(osAttrName);
    6338           8 :                 if (iter == oMapNameToArray.end())
    6339             :                 {
    6340           8 :                     CPLJSONArray array;
    6341           4 :                     obj.Add(osAttrName, array);
    6342           4 :                     oMapNameToArray[osAttrName] = array;
    6343           4 :                     array.Add(pszMetaValue);
    6344             :                 }
    6345             :                 else
    6346             :                 {
    6347           4 :                     iter->second.Add(pszMetaValue);
    6348             :                 }
    6349             :             }
    6350          24 :             CPLFree(pszMetaValue);
    6351          24 :             pszMetaValue = nullptr;
    6352             :         }
    6353             :     }
    6354             : 
    6355          16 :     int nSubGroups = 0;
    6356          16 :     int *panSubGroupIds = nullptr;
    6357          16 :     NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
    6358          16 :     oMapNameToArray.clear();
    6359          28 :     for (int i = 0; i < nSubGroups; i++)
    6360             :     {
    6361          24 :         CPLJSONObject subObj;
    6362          12 :         NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
    6363             : 
    6364          24 :         std::string osGroupName;
    6365          12 :         osGroupName.resize(NC_MAX_NAME);
    6366          12 :         NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
    6367          12 :         osGroupName.resize(strlen(osGroupName.data()));
    6368          12 :         const auto sharpPos = osGroupName.find('#');
    6369          12 :         if (sharpPos == std::string::npos)
    6370             :         {
    6371           4 :             obj.Add(osGroupName, subObj);
    6372             :         }
    6373             :         else
    6374             :         {
    6375           8 :             osGroupName.resize(sharpPos);
    6376           8 :             auto iter = oMapNameToArray.find(osGroupName);
    6377           8 :             if (iter == oMapNameToArray.end())
    6378             :             {
    6379           8 :                 CPLJSONArray array;
    6380           4 :                 obj.Add(osGroupName, array);
    6381           4 :                 oMapNameToArray[osGroupName] = array;
    6382           4 :                 array.Add(subObj);
    6383             :             }
    6384             :             else
    6385             :             {
    6386           4 :                 iter->second.Add(subObj);
    6387             :             }
    6388             :         }
    6389             :     }
    6390          16 :     CPLFree(panSubGroupIds);
    6391          16 : }
    6392             : 
    6393           4 : std::string NCDFReadMetadataAsJson(int cdfid)
    6394             : {
    6395           8 :     CPLJSONDocument oDoc;
    6396           8 :     CPLJSONObject oRoot = oDoc.GetRoot();
    6397           4 :     NCDFReadMetadataAsJson(cdfid, oRoot);
    6398           8 :     return oDoc.SaveAsString();
    6399             : }
    6400             : 
    6401             : /************************************************************************/
    6402             : /*                        ReadAttributes()                              */
    6403             : /************************************************************************/
    6404        1795 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
    6405             : 
    6406             : {
    6407        1795 :     char *pszVarFullName = nullptr;
    6408        1795 :     ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
    6409             : 
    6410             :     // For metadata in Sentinel 5
    6411        1795 :     if (STARTS_WITH(pszVarFullName, "/METADATA/"))
    6412             :     {
    6413           6 :         for (const char *key :
    6414             :              {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
    6415           8 :               "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
    6416             :         {
    6417          14 :             if (var == NC_GLOBAL &&
    6418           7 :                 strcmp(pszVarFullName,
    6419             :                        CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
    6420             :             {
    6421           1 :                 CPLFree(pszVarFullName);
    6422           1 :                 CPLStringList aosList;
    6423           2 :                 aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
    6424           1 :                                       .replaceAll("\\/", '/'));
    6425           1 :                 m_oMapDomainToJSon[key] = std::move(aosList);
    6426           1 :                 return CE_None;
    6427             :             }
    6428             :         }
    6429             :     }
    6430        1794 :     if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
    6431             :     {
    6432           0 :         CPLFree(pszVarFullName);
    6433           0 :         CPLStringList aosList;
    6434             :         aosList.AddString(
    6435           0 :             CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
    6436           0 :         m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
    6437           0 :         return CE_None;
    6438             :     }
    6439             : 
    6440        1794 :     size_t nMetaNameSize =
    6441        1794 :         sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
    6442        1794 :     char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
    6443             : 
    6444        1794 :     int nbAttr = 0;
    6445        1794 :     NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
    6446             : 
    6447        9029 :     for (int l = 0; l < nbAttr; l++)
    6448             :     {
    6449             :         char szAttrName[NC_MAX_NAME + 1];
    6450        7235 :         szAttrName[0] = 0;
    6451        7235 :         NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
    6452        7235 :         snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
    6453             :                  szAttrName);
    6454             : 
    6455        7235 :         char *pszMetaTemp = nullptr;
    6456        7235 :         if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
    6457             :         {
    6458        7234 :             papszMetadata =
    6459        7234 :                 CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
    6460        7234 :             CPLFree(pszMetaTemp);
    6461        7234 :             pszMetaTemp = nullptr;
    6462             :         }
    6463             :         else
    6464             :         {
    6465           1 :             CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
    6466             :         }
    6467             :     }
    6468             : 
    6469        1794 :     CPLFree(pszVarFullName);
    6470        1794 :     CPLFree(pszMetaName);
    6471             : 
    6472        1794 :     if (var == NC_GLOBAL)
    6473             :     {
    6474             :         // Recurse on sub-groups.
    6475         522 :         int nSubGroups = 0;
    6476         522 :         int *panSubGroupIds = nullptr;
    6477         522 :         NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
    6478         552 :         for (int i = 0; i < nSubGroups; i++)
    6479             :         {
    6480          30 :             ReadAttributes(panSubGroupIds[i], var);
    6481             :         }
    6482         522 :         CPLFree(panSubGroupIds);
    6483             :     }
    6484             : 
    6485        1794 :     return CE_None;
    6486             : }
    6487             : 
    6488             : /************************************************************************/
    6489             : /*                netCDFDataset::CreateSubDatasetList()                 */
    6490             : /************************************************************************/
    6491          51 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
    6492             : {
    6493             :     char szVarStdName[NC_MAX_NAME + 1];
    6494          51 :     int *ponDimIds = nullptr;
    6495             :     nc_type nAttype;
    6496             :     size_t nAttlen;
    6497             : 
    6498          51 :     netCDFDataset *poDS = this;
    6499             : 
    6500             :     int nVarCount;
    6501          51 :     nc_inq_nvars(nGroupId, &nVarCount);
    6502             : 
    6503          51 :     const bool bListAllArrays = CPLTestBool(
    6504          51 :         CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    6505             : 
    6506         329 :     for (int nVar = 0; nVar < nVarCount; nVar++)
    6507             :     {
    6508             : 
    6509             :         int nDims;
    6510         278 :         nc_inq_varndims(nGroupId, nVar, &nDims);
    6511             : 
    6512         278 :         if ((bListAllArrays && nDims > 0) || nDims >= 2)
    6513             :         {
    6514         158 :             ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
    6515         158 :             nc_inq_vardimid(nGroupId, nVar, ponDimIds);
    6516             : 
    6517             :             // Create Sub dataset list.
    6518         158 :             CPLString osDim;
    6519         487 :             for (int i = 0; i < nDims; i++)
    6520             :             {
    6521             :                 size_t nDimLen;
    6522         329 :                 nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
    6523         329 :                 if (!osDim.empty())
    6524         171 :                     osDim += 'x';
    6525         329 :                 osDim += CPLSPrintf("%d", (int)nDimLen);
    6526             :             }
    6527         158 :             CPLFree(ponDimIds);
    6528             : 
    6529             :             nc_type nVarType;
    6530         158 :             nc_inq_vartype(nGroupId, nVar, &nVarType);
    6531         158 :             const char *pszType = "";
    6532         158 :             switch (nVarType)
    6533             :             {
    6534          38 :                 case NC_BYTE:
    6535          38 :                     pszType = "8-bit integer";
    6536          38 :                     break;
    6537           2 :                 case NC_CHAR:
    6538           2 :                     pszType = "8-bit character";
    6539           2 :                     break;
    6540           6 :                 case NC_SHORT:
    6541           6 :                     pszType = "16-bit integer";
    6542           6 :                     break;
    6543          10 :                 case NC_INT:
    6544          10 :                     pszType = "32-bit integer";
    6545          10 :                     break;
    6546          51 :                 case NC_FLOAT:
    6547          51 :                     pszType = "32-bit floating-point";
    6548          51 :                     break;
    6549          33 :                 case NC_DOUBLE:
    6550          33 :                     pszType = "64-bit floating-point";
    6551          33 :                     break;
    6552           4 :                 case NC_UBYTE:
    6553           4 :                     pszType = "8-bit unsigned integer";
    6554           4 :                     break;
    6555           1 :                 case NC_USHORT:
    6556           1 :                     pszType = "16-bit unsigned integer";
    6557           1 :                     break;
    6558           1 :                 case NC_UINT:
    6559           1 :                     pszType = "32-bit unsigned integer";
    6560           1 :                     break;
    6561           1 :                 case NC_INT64:
    6562           1 :                     pszType = "64-bit integer";
    6563           1 :                     break;
    6564           1 :                 case NC_UINT64:
    6565           1 :                     pszType = "64-bit unsigned integer";
    6566           1 :                     break;
    6567          10 :                 default:
    6568          10 :                     break;
    6569             :             }
    6570             : 
    6571         158 :             char *pszName = nullptr;
    6572         158 :             if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
    6573           0 :                 continue;
    6574             : 
    6575         158 :             nSubDatasets++;
    6576             : 
    6577         158 :             nAttlen = 0;
    6578         158 :             nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
    6579         316 :             if (nAttlen < sizeof(szVarStdName) &&
    6580         158 :                 nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
    6581             :                     NC_NOERR)
    6582             :             {
    6583          53 :                 szVarStdName[nAttlen] = '\0';
    6584             :             }
    6585             :             else
    6586             :             {
    6587         105 :                 snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
    6588             :             }
    6589             : 
    6590             :             char szTemp[NC_MAX_NAME + 1];
    6591         158 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
    6592             :                      nSubDatasets);
    6593             : 
    6594         158 :             if (strchr(pszName, ' ') || strchr(pszName, ':'))
    6595             :             {
    6596           1 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6597             :                     poDS->papszSubDatasets, szTemp,
    6598             :                     CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
    6599             :                                pszName));
    6600             :             }
    6601             :             else
    6602             :             {
    6603         157 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6604             :                     poDS->papszSubDatasets, szTemp,
    6605             :                     CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
    6606             :                                pszName));
    6607             :             }
    6608             : 
    6609         158 :             CPLFree(pszName);
    6610             : 
    6611         158 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
    6612             :                      nSubDatasets);
    6613             : 
    6614         158 :             poDS->papszSubDatasets =
    6615         158 :                 CSLSetNameValue(poDS->papszSubDatasets, szTemp,
    6616             :                                 CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
    6617             :                                            szVarStdName, pszType));
    6618             :         }
    6619             :     }
    6620             : 
    6621             :     // Recurse on sub groups.
    6622          51 :     int nSubGroups = 0;
    6623          51 :     int *panSubGroupIds = nullptr;
    6624          51 :     NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
    6625          56 :     for (int i = 0; i < nSubGroups; i++)
    6626             :     {
    6627           5 :         CreateSubDatasetList(panSubGroupIds[i]);
    6628             :     }
    6629          51 :     CPLFree(panSubGroupIds);
    6630          51 : }
    6631             : 
    6632             : /************************************************************************/
    6633             : /*                            TestCapability()                          */
    6634             : /************************************************************************/
    6635             : 
    6636         248 : int netCDFDataset::TestCapability(const char *pszCap)
    6637             : {
    6638         248 :     if (EQUAL(pszCap, ODsCCreateLayer))
    6639             :     {
    6640         223 :         return eAccess == GA_Update && nBands == 0 &&
    6641         218 :                (eMultipleLayerBehavior != SINGLE_LAYER ||
    6642         229 :                 this->GetLayerCount() == 0 || bSGSupport);
    6643             :     }
    6644         136 :     else if (EQUAL(pszCap, ODsCZGeometries))
    6645           2 :         return true;
    6646             : 
    6647         134 :     return false;
    6648             : }
    6649             : 
    6650             : /************************************************************************/
    6651             : /*                            GetLayer()                                */
    6652             : /************************************************************************/
    6653             : 
    6654         384 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
    6655             : {
    6656         384 :     if (nIdx < 0 || nIdx >= this->GetLayerCount())
    6657           2 :         return nullptr;
    6658         382 :     return papoLayers[nIdx].get();
    6659             : }
    6660             : 
    6661             : /************************************************************************/
    6662             : /*                            ICreateLayer()                            */
    6663             : /************************************************************************/
    6664             : 
    6665          59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
    6666             :                                       const OGRGeomFieldDefn *poGeomFieldDefn,
    6667             :                                       CSLConstList papszOptions)
    6668             : {
    6669          59 :     int nLayerCDFId = cdfid;
    6670          59 :     if (!TestCapability(ODsCCreateLayer))
    6671           0 :         return nullptr;
    6672             : 
    6673          59 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    6674             :     const auto poSpatialRef =
    6675          59 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6676             : 
    6677         118 :     CPLString osNetCDFLayerName(pszName);
    6678          59 :     const netCDFWriterConfigLayer *poLayerConfig = nullptr;
    6679          59 :     if (oWriterConfig.m_bIsValid)
    6680             :     {
    6681             :         std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
    6682           2 :             oLayerIter = oWriterConfig.m_oLayers.find(pszName);
    6683           2 :         if (oLayerIter != oWriterConfig.m_oLayers.end())
    6684             :         {
    6685           1 :             poLayerConfig = &(oLayerIter->second);
    6686           1 :             osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
    6687             :         }
    6688             :     }
    6689             : 
    6690          59 :     netCDFDataset *poLayerDataset = nullptr;
    6691          59 :     if (eMultipleLayerBehavior == SEPARATE_FILES)
    6692             :     {
    6693           2 :         char **papszDatasetOptions = nullptr;
    6694           2 :         papszDatasetOptions = CSLSetNameValue(
    6695             :             papszDatasetOptions, "CONFIG_FILE",
    6696           2 :             CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
    6697             :         papszDatasetOptions =
    6698           2 :             CSLSetNameValue(papszDatasetOptions, "FORMAT",
    6699           2 :                             CSLFetchNameValue(papszCreationOptions, "FORMAT"));
    6700           2 :         papszDatasetOptions = CSLSetNameValue(
    6701             :             papszDatasetOptions, "WRITE_GDAL_TAGS",
    6702           2 :             CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
    6703             :         const CPLString osLayerFilename(
    6704           2 :             CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
    6705           2 :         CPLAcquireMutex(hNCMutex, 1000.0);
    6706           2 :         poLayerDataset =
    6707           2 :             CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
    6708           2 :         CPLReleaseMutex(hNCMutex);
    6709           2 :         CSLDestroy(papszDatasetOptions);
    6710           2 :         if (poLayerDataset == nullptr)
    6711           0 :             return nullptr;
    6712             : 
    6713           2 :         nLayerCDFId = poLayerDataset->cdfid;
    6714           2 :         NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
    6715           2 :                            bWriteGDALHistory, "", "Create",
    6716             :                            NCDF_CONVENTIONS_CF_V1_6);
    6717             :     }
    6718          57 :     else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
    6719             :     {
    6720           2 :         SetDefineMode(true);
    6721             : 
    6722           2 :         nLayerCDFId = -1;
    6723           2 :         int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
    6724           2 :         NCDF_ERR(status);
    6725           2 :         if (status != NC_NOERR)
    6726           0 :             return nullptr;
    6727             : 
    6728           2 :         NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
    6729           2 :                            bWriteGDALHistory, "", "Create",
    6730             :                            NCDF_CONVENTIONS_CF_V1_6);
    6731             :     }
    6732             : 
    6733             :     // Make a clone to workaround a bug in released MapServer versions
    6734             :     // that destroys the passed SRS instead of releasing it .
    6735          59 :     OGRSpatialReference *poSRS = nullptr;
    6736          59 :     if (poSpatialRef)
    6737             :     {
    6738          43 :         poSRS = poSpatialRef->Clone();
    6739          43 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6740             :     }
    6741             :     std::shared_ptr<netCDFLayer> poLayer(
    6742          59 :         new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
    6743         118 :                         osNetCDFLayerName, eGType, poSRS));
    6744          59 :     if (poSRS != nullptr)
    6745          43 :         poSRS->Release();
    6746             : 
    6747             :     // Fetch layer creation options coming from config file
    6748          59 :     char **papszNewOptions = CSLDuplicate(papszOptions);
    6749          59 :     if (oWriterConfig.m_bIsValid)
    6750             :     {
    6751           2 :         std::map<CPLString, CPLString>::const_iterator oIter;
    6752           3 :         for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
    6753           3 :              oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
    6754             :         {
    6755             :             papszNewOptions =
    6756           1 :                 CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
    6757             :         }
    6758           2 :         if (poLayerConfig != nullptr)
    6759             :         {
    6760           3 :             for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
    6761           3 :                  oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
    6762             :             {
    6763           2 :                 papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
    6764           2 :                                                   oIter->second);
    6765             :             }
    6766             :         }
    6767             :     }
    6768             : 
    6769          59 :     const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
    6770          59 :     CSLDestroy(papszNewOptions);
    6771             : 
    6772          59 :     if (!bRet)
    6773             :     {
    6774           0 :         return nullptr;
    6775             :     }
    6776             : 
    6777          59 :     if (poLayerDataset != nullptr)
    6778           2 :         apoVectorDatasets.push_back(poLayerDataset);
    6779             : 
    6780          59 :     papoLayers.push_back(poLayer);
    6781          59 :     return poLayer.get();
    6782             : }
    6783             : 
    6784             : /************************************************************************/
    6785             : /*                           CloneAttributes()                          */
    6786             : /************************************************************************/
    6787             : 
    6788         137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
    6789             :                                     int nDstVarId)
    6790             : {
    6791         137 :     int nAttCount = -1;
    6792         137 :     int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
    6793         137 :     NCDF_ERR(status);
    6794             : 
    6795         693 :     for (int i = 0; i < nAttCount; i++)
    6796             :     {
    6797             :         char szName[NC_MAX_NAME + 1];
    6798         556 :         szName[0] = 0;
    6799         556 :         status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
    6800         556 :         NCDF_ERR(status);
    6801             : 
    6802             :         status =
    6803         556 :             nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
    6804         556 :         NCDF_ERR(status);
    6805         556 :         if (status != NC_NOERR)
    6806           0 :             return false;
    6807             :     }
    6808             : 
    6809         137 :     return true;
    6810             : }
    6811             : 
    6812             : /************************************************************************/
    6813             : /*                          CloneVariableContent()                      */
    6814             : /************************************************************************/
    6815             : 
    6816         121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
    6817             :                                          int nSrcVarId, int nDstVarId)
    6818             : {
    6819         121 :     int nVarDimCount = -1;
    6820         121 :     int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
    6821         121 :     NCDF_ERR(status);
    6822         121 :     int anDimIds[] = {-1, 1};
    6823         121 :     status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
    6824         121 :     NCDF_ERR(status);
    6825         121 :     nc_type nc_datatype = NC_NAT;
    6826         121 :     status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
    6827         121 :     NCDF_ERR(status);
    6828         121 :     size_t nTypeSize = 0;
    6829         121 :     switch (nc_datatype)
    6830             :     {
    6831          35 :         case NC_BYTE:
    6832             :         case NC_CHAR:
    6833          35 :             nTypeSize = 1;
    6834          35 :             break;
    6835           4 :         case NC_SHORT:
    6836           4 :             nTypeSize = 2;
    6837           4 :             break;
    6838          24 :         case NC_INT:
    6839          24 :             nTypeSize = 4;
    6840          24 :             break;
    6841           4 :         case NC_FLOAT:
    6842           4 :             nTypeSize = 4;
    6843           4 :             break;
    6844          43 :         case NC_DOUBLE:
    6845          43 :             nTypeSize = 8;
    6846          43 :             break;
    6847           2 :         case NC_UBYTE:
    6848           2 :             nTypeSize = 1;
    6849           2 :             break;
    6850           2 :         case NC_USHORT:
    6851           2 :             nTypeSize = 2;
    6852           2 :             break;
    6853           2 :         case NC_UINT:
    6854           2 :             nTypeSize = 4;
    6855           2 :             break;
    6856           4 :         case NC_INT64:
    6857             :         case NC_UINT64:
    6858           4 :             nTypeSize = 8;
    6859           4 :             break;
    6860           1 :         case NC_STRING:
    6861           1 :             nTypeSize = sizeof(char *);
    6862           1 :             break;
    6863           0 :         default:
    6864             :         {
    6865           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
    6866             :                      nc_datatype);
    6867           0 :             return false;
    6868             :         }
    6869             :     }
    6870             : 
    6871         121 :     size_t nElems = 1;
    6872             :     size_t anStart[NC_MAX_DIMS];
    6873             :     size_t anCount[NC_MAX_DIMS];
    6874         121 :     size_t nRecords = 1;
    6875         261 :     for (int i = 0; i < nVarDimCount; i++)
    6876             :     {
    6877         140 :         anStart[i] = 0;
    6878         140 :         if (i == 0)
    6879             :         {
    6880         116 :             anCount[i] = 1;
    6881         116 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
    6882         116 :             NCDF_ERR(status);
    6883             :         }
    6884             :         else
    6885             :         {
    6886          24 :             anCount[i] = 0;
    6887          24 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
    6888          24 :             NCDF_ERR(status);
    6889          24 :             nElems *= anCount[i];
    6890             :         }
    6891             :     }
    6892             : 
    6893             :     /* Workaround in some cases a netCDF bug:
    6894             :      * https://github.com/Unidata/netcdf-c/pull/1442 */
    6895         121 :     if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
    6896             :     {
    6897         119 :         nElems *= nRecords;
    6898         119 :         anCount[0] = nRecords;
    6899         119 :         nRecords = 1;
    6900             :     }
    6901             : 
    6902         121 :     void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
    6903         121 :     if (pBuffer == nullptr)
    6904           0 :         return false;
    6905             : 
    6906         240 :     for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
    6907             :     {
    6908         119 :         anStart[0] = iRecord;
    6909             : 
    6910         119 :         switch (nc_datatype)
    6911             :         {
    6912           5 :             case NC_BYTE:
    6913             :                 status =
    6914           5 :                     nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
    6915             :                                       static_cast<signed char *>(pBuffer));
    6916           5 :                 if (!status)
    6917           5 :                     status = nc_put_vara_schar(
    6918             :                         new_cdfid, nDstVarId, anStart, anCount,
    6919             :                         static_cast<signed char *>(pBuffer));
    6920           5 :                 break;
    6921          28 :             case NC_CHAR:
    6922             :                 status =
    6923          28 :                     nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
    6924             :                                      static_cast<char *>(pBuffer));
    6925          28 :                 if (!status)
    6926             :                     status =
    6927          28 :                         nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
    6928             :                                          static_cast<char *>(pBuffer));
    6929          28 :                 break;
    6930           4 :             case NC_SHORT:
    6931             :                 status =
    6932           4 :                     nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
    6933             :                                       static_cast<short *>(pBuffer));
    6934           4 :                 if (!status)
    6935           4 :                     status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
    6936             :                                                anCount,
    6937             :                                                static_cast<short *>(pBuffer));
    6938           4 :                 break;
    6939          24 :             case NC_INT:
    6940          24 :                 status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
    6941             :                                          static_cast<int *>(pBuffer));
    6942          24 :                 if (!status)
    6943             :                     status =
    6944          24 :                         nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
    6945             :                                         static_cast<int *>(pBuffer));
    6946          24 :                 break;
    6947           4 :             case NC_FLOAT:
    6948             :                 status =
    6949           4 :                     nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
    6950             :                                       static_cast<float *>(pBuffer));
    6951           4 :                 if (!status)
    6952           4 :                     status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
    6953             :                                                anCount,
    6954             :                                                static_cast<float *>(pBuffer));
    6955           4 :                 break;
    6956          43 :             case NC_DOUBLE:
    6957             :                 status =
    6958          43 :                     nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
    6959             :                                        static_cast<double *>(pBuffer));
    6960          43 :                 if (!status)
    6961          43 :                     status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
    6962             :                                                 anCount,
    6963             :                                                 static_cast<double *>(pBuffer));
    6964          43 :                 break;
    6965           1 :             case NC_STRING:
    6966             :                 status =
    6967           1 :                     nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
    6968             :                                        static_cast<char **>(pBuffer));
    6969           1 :                 if (!status)
    6970             :                 {
    6971           1 :                     status = nc_put_vara_string(
    6972             :                         new_cdfid, nDstVarId, anStart, anCount,
    6973             :                         static_cast<const char **>(pBuffer));
    6974           1 :                     nc_free_string(nElems, static_cast<char **>(pBuffer));
    6975             :                 }
    6976           1 :                 break;
    6977             : 
    6978           2 :             case NC_UBYTE:
    6979             :                 status =
    6980           2 :                     nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
    6981             :                                       static_cast<unsigned char *>(pBuffer));
    6982           2 :                 if (!status)
    6983           2 :                     status = nc_put_vara_uchar(
    6984             :                         new_cdfid, nDstVarId, anStart, anCount,
    6985             :                         static_cast<unsigned char *>(pBuffer));
    6986           2 :                 break;
    6987           2 :             case NC_USHORT:
    6988             :                 status =
    6989           2 :                     nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
    6990             :                                        static_cast<unsigned short *>(pBuffer));
    6991           2 :                 if (!status)
    6992           2 :                     status = nc_put_vara_ushort(
    6993             :                         new_cdfid, nDstVarId, anStart, anCount,
    6994             :                         static_cast<unsigned short *>(pBuffer));
    6995           2 :                 break;
    6996           2 :             case NC_UINT:
    6997             :                 status =
    6998           2 :                     nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
    6999             :                                      static_cast<unsigned int *>(pBuffer));
    7000           2 :                 if (!status)
    7001             :                     status =
    7002           2 :                         nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
    7003             :                                          static_cast<unsigned int *>(pBuffer));
    7004           2 :                 break;
    7005           2 :             case NC_INT64:
    7006             :                 status =
    7007           2 :                     nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
    7008             :                                          static_cast<long long *>(pBuffer));
    7009           2 :                 if (!status)
    7010           2 :                     status = nc_put_vara_longlong(
    7011             :                         new_cdfid, nDstVarId, anStart, anCount,
    7012             :                         static_cast<long long *>(pBuffer));
    7013           2 :                 break;
    7014           2 :             case NC_UINT64:
    7015           2 :                 status = nc_get_vara_ulonglong(
    7016             :                     old_cdfid, nSrcVarId, anStart, anCount,
    7017             :                     static_cast<unsigned long long *>(pBuffer));
    7018           2 :                 if (!status)
    7019           2 :                     status = nc_put_vara_ulonglong(
    7020             :                         new_cdfid, nDstVarId, anStart, anCount,
    7021             :                         static_cast<unsigned long long *>(pBuffer));
    7022           2 :                 break;
    7023           0 :             default:
    7024           0 :                 status = NC_EBADTYPE;
    7025             :         }
    7026             : 
    7027         119 :         NCDF_ERR(status);
    7028         119 :         if (status != NC_NOERR)
    7029             :         {
    7030           0 :             VSIFree(pBuffer);
    7031           0 :             return false;
    7032             :         }
    7033             :     }
    7034             : 
    7035         121 :     VSIFree(pBuffer);
    7036         121 :     return true;
    7037             : }
    7038             : 
    7039             : /************************************************************************/
    7040             : /*                         NCDFIsUnlimitedDim()                         */
    7041             : /************************************************************************/
    7042             : 
    7043          58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
    7044             : {
    7045          58 :     if (bIsNC4)
    7046             :     {
    7047          16 :         int nUnlimitedDims = 0;
    7048          16 :         nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
    7049          16 :         bool bFound = false;
    7050          16 :         if (nUnlimitedDims)
    7051             :         {
    7052             :             int *panUnlimitedDimIds =
    7053          16 :                 static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
    7054          16 :             nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
    7055          30 :             for (int i = 0; i < nUnlimitedDims; i++)
    7056             :             {
    7057          22 :                 if (panUnlimitedDimIds[i] == nDimId)
    7058             :                 {
    7059           8 :                     bFound = true;
    7060           8 :                     break;
    7061             :                 }
    7062             :             }
    7063          16 :             CPLFree(panUnlimitedDimIds);
    7064             :         }
    7065          16 :         return bFound;
    7066             :     }
    7067             :     else
    7068             :     {
    7069          42 :         int nUnlimitedDimId = -1;
    7070          42 :         nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
    7071          42 :         return nDimId == nUnlimitedDimId;
    7072             :     }
    7073             : }
    7074             : 
    7075             : /************************************************************************/
    7076             : /*                              CloneGrp()                              */
    7077             : /************************************************************************/
    7078             : 
    7079          16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
    7080             :                              int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7081             : {
    7082             :     // Clone dimensions
    7083          16 :     int nDimCount = -1;
    7084          16 :     int status = nc_inq_ndims(nOldGrpId, &nDimCount);
    7085          16 :     NCDF_ERR(status);
    7086          16 :     if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
    7087           0 :         return false;
    7088             :     int anDimIds[NC_MAX_DIMS];
    7089          16 :     int nUnlimiDimID = -1;
    7090          16 :     status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
    7091          16 :     NCDF_ERR(status);
    7092          16 :     if (bIsNC4)
    7093             :     {
    7094             :         // In NC4, the dimension ids of a group are not necessarily in
    7095             :         // [0,nDimCount-1] range
    7096           8 :         int nDimCount2 = -1;
    7097           8 :         status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
    7098           8 :         NCDF_ERR(status);
    7099           8 :         CPLAssert(nDimCount == nDimCount2);
    7100             :     }
    7101             :     else
    7102             :     {
    7103          36 :         for (int i = 0; i < nDimCount; i++)
    7104          28 :             anDimIds[i] = i;
    7105             :     }
    7106          60 :     for (int i = 0; i < nDimCount; i++)
    7107             :     {
    7108             :         char szDimName[NC_MAX_NAME + 1];
    7109          44 :         szDimName[0] = 0;
    7110          44 :         size_t nLen = 0;
    7111          44 :         const int nDimId = anDimIds[i];
    7112          44 :         status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
    7113          44 :         NCDF_ERR(status);
    7114          44 :         if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
    7115          16 :             nLen = NC_UNLIMITED;
    7116          28 :         else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
    7117          13 :             nLen = nNewSize;
    7118          44 :         int nNewDimId = -1;
    7119          44 :         status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
    7120          44 :         NCDF_ERR(status);
    7121          44 :         CPLAssert(nDimId == nNewDimId);
    7122          44 :         if (status != NC_NOERR)
    7123             :         {
    7124           0 :             return false;
    7125             :         }
    7126             :     }
    7127             : 
    7128             :     // Clone main attributes
    7129          16 :     if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
    7130             :     {
    7131           0 :         return false;
    7132             :     }
    7133             : 
    7134             :     // Clone variable definitions
    7135          16 :     int nVarCount = -1;
    7136          16 :     status = nc_inq_nvars(nOldGrpId, &nVarCount);
    7137          16 :     NCDF_ERR(status);
    7138             : 
    7139         137 :     for (int i = 0; i < nVarCount; i++)
    7140             :     {
    7141             :         char szVarName[NC_MAX_NAME + 1];
    7142         121 :         szVarName[0] = 0;
    7143         121 :         status = nc_inq_varname(nOldGrpId, i, szVarName);
    7144         121 :         NCDF_ERR(status);
    7145         121 :         nc_type nc_datatype = NC_NAT;
    7146         121 :         status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
    7147         121 :         NCDF_ERR(status);
    7148         121 :         int nVarDimCount = -1;
    7149         121 :         status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
    7150         121 :         NCDF_ERR(status);
    7151         121 :         status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
    7152         121 :         NCDF_ERR(status);
    7153         121 :         int nNewVarId = -1;
    7154         121 :         status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
    7155             :                             anDimIds, &nNewVarId);
    7156         121 :         NCDF_ERR(status);
    7157         121 :         CPLAssert(i == nNewVarId);
    7158         121 :         if (status != NC_NOERR)
    7159             :         {
    7160           0 :             return false;
    7161             :         }
    7162             : 
    7163         121 :         if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
    7164             :         {
    7165           0 :             return false;
    7166             :         }
    7167             :     }
    7168             : 
    7169          16 :     status = nc_enddef(nNewGrpId);
    7170          16 :     NCDF_ERR(status);
    7171          16 :     if (status != NC_NOERR)
    7172             :     {
    7173           0 :         return false;
    7174             :     }
    7175             : 
    7176             :     // Clone variable content
    7177         137 :     for (int i = 0; i < nVarCount; i++)
    7178             :     {
    7179         121 :         if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
    7180             :         {
    7181           0 :             return false;
    7182             :         }
    7183             :     }
    7184             : 
    7185          16 :     return true;
    7186             : }
    7187             : 
    7188             : /************************************************************************/
    7189             : /*                              GrowDim()                               */
    7190             : /************************************************************************/
    7191             : 
    7192          13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7193             : {
    7194             :     int nCreationMode;
    7195             :     // Set nCreationMode based on eFormat.
    7196          13 :     switch (eFormat)
    7197             :     {
    7198             : #ifdef NETCDF_HAS_NC2
    7199           0 :         case NCDF_FORMAT_NC2:
    7200           0 :             nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
    7201           0 :             break;
    7202             : #endif
    7203           5 :         case NCDF_FORMAT_NC4:
    7204           5 :             nCreationMode = NC_CLOBBER | NC_NETCDF4;
    7205           5 :             break;
    7206           0 :         case NCDF_FORMAT_NC4C:
    7207           0 :             nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
    7208           0 :             break;
    7209           8 :         case NCDF_FORMAT_NC:
    7210             :         default:
    7211           8 :             nCreationMode = NC_CLOBBER;
    7212           8 :             break;
    7213             :     }
    7214             : 
    7215          13 :     int new_cdfid = -1;
    7216          26 :     CPLString osTmpFilename(osFilename + ".tmp");
    7217          26 :     CPLString osFilenameForNCCreate(osTmpFilename);
    7218             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7219             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7220             :     {
    7221             :         char *pszTemp =
    7222             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    7223             :         osFilenameForNCCreate = pszTemp;
    7224             :         CPLFree(pszTemp);
    7225             :     }
    7226             : #endif
    7227          13 :     int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
    7228          13 :     NCDF_ERR(status);
    7229          13 :     if (status != NC_NOERR)
    7230           0 :         return false;
    7231             : 
    7232          13 :     if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
    7233             :                   nDimIdToGrow, nNewSize))
    7234             :     {
    7235           0 :         GDAL_nc_close(new_cdfid);
    7236           0 :         return false;
    7237             :     }
    7238             : 
    7239          13 :     int nGroupCount = 0;
    7240          26 :     std::vector<CPLString> oListGrpName;
    7241          31 :     if (eFormat == NCDF_FORMAT_NC4 &&
    7242          18 :         nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
    7243           5 :         nGroupCount > 0)
    7244             :     {
    7245             :         int *panGroupIds =
    7246           2 :             static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
    7247           2 :         status = nc_inq_grps(cdfid, nullptr, panGroupIds);
    7248           2 :         NCDF_ERR(status);
    7249           5 :         for (int i = 0; i < nGroupCount; i++)
    7250             :         {
    7251             :             char szGroupName[NC_MAX_NAME + 1];
    7252           3 :             szGroupName[0] = 0;
    7253           3 :             NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
    7254           3 :             int nNewGrpId = -1;
    7255           3 :             status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
    7256           3 :             NCDF_ERR(status);
    7257           3 :             if (status != NC_NOERR)
    7258             :             {
    7259           0 :                 CPLFree(panGroupIds);
    7260           0 :                 GDAL_nc_close(new_cdfid);
    7261           0 :                 return false;
    7262             :             }
    7263           3 :             if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
    7264             :                           nDimIdToGrow, nNewSize))
    7265             :             {
    7266           0 :                 CPLFree(panGroupIds);
    7267           0 :                 GDAL_nc_close(new_cdfid);
    7268           0 :                 return false;
    7269             :             }
    7270             :         }
    7271           2 :         CPLFree(panGroupIds);
    7272             : 
    7273           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7274             :         {
    7275           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7276           3 :             if (poLayer)
    7277             :             {
    7278             :                 char szGroupName[NC_MAX_NAME + 1];
    7279           3 :                 szGroupName[0] = 0;
    7280           3 :                 status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
    7281           3 :                 NCDF_ERR(status);
    7282           3 :                 oListGrpName.push_back(szGroupName);
    7283             :             }
    7284             :         }
    7285             :     }
    7286             : 
    7287          13 :     GDAL_nc_close(cdfid);
    7288          13 :     cdfid = -1;
    7289          13 :     GDAL_nc_close(new_cdfid);
    7290             : 
    7291          26 :     CPLString osOriFilename(osFilename + ".ori");
    7292          26 :     if (VSIRename(osFilename, osOriFilename) != 0 ||
    7293          13 :         VSIRename(osTmpFilename, osFilename) != 0)
    7294             :     {
    7295           0 :         CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
    7296           0 :         return false;
    7297             :     }
    7298          13 :     VSIUnlink(osOriFilename);
    7299             : 
    7300          26 :     CPLString osFilenameForNCOpen(osFilename);
    7301             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7302             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7303             :     {
    7304             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    7305             :         osFilenameForNCOpen = pszTemp;
    7306             :         CPLFree(pszTemp);
    7307             :     }
    7308             : #endif
    7309          13 :     status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
    7310          13 :     NCDF_ERR(status);
    7311          13 :     if (status != NC_NOERR)
    7312           0 :         return false;
    7313          13 :     bDefineMode = false;
    7314             : 
    7315          13 :     if (!oListGrpName.empty())
    7316             :     {
    7317           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7318             :         {
    7319           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7320           3 :             if (poLayer)
    7321             :             {
    7322           3 :                 int nNewLayerCDFID = -1;
    7323           3 :                 status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
    7324             :                                      &nNewLayerCDFID);
    7325           3 :                 NCDF_ERR(status);
    7326           3 :                 poLayer->SetCDFID(nNewLayerCDFID);
    7327             :             }
    7328             :         }
    7329             :     }
    7330             :     else
    7331             :     {
    7332          22 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7333             :         {
    7334          11 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7335          11 :             if (poLayer)
    7336          11 :                 poLayer->SetCDFID(cdfid);
    7337             :         }
    7338             :     }
    7339             : 
    7340          13 :     return true;
    7341             : }
    7342             : 
    7343             : #ifdef ENABLE_NCDUMP
    7344             : 
    7345             : /************************************************************************/
    7346             : /*                      netCDFDatasetCreateTempFile()                   */
    7347             : /************************************************************************/
    7348             : 
    7349             : /* Create a netCDF file from a text dump (format of ncdump) */
    7350             : /* Mostly to easy fuzzing of the driver, while still generating valid */
    7351             : /* netCDF files. */
    7352             : /* Note: not all data types are supported ! */
    7353           4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
    7354             :                                  const char *pszTmpFilename, VSILFILE *fpSrc)
    7355             : {
    7356           4 :     CPL_IGNORE_RET_VAL(eFormat);
    7357           4 :     int nCreateMode = NC_CLOBBER;
    7358           4 :     if (eFormat == NCDF_FORMAT_NC4)
    7359           1 :         nCreateMode |= NC_NETCDF4;
    7360           3 :     else if (eFormat == NCDF_FORMAT_NC4C)
    7361           0 :         nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
    7362           4 :     int nCdfId = -1;
    7363           4 :     int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
    7364           4 :     if (status != NC_NOERR)
    7365             :     {
    7366           0 :         return false;
    7367             :     }
    7368           4 :     VSIFSeekL(fpSrc, 0, SEEK_SET);
    7369             :     const char *pszLine;
    7370           4 :     constexpr int SECTION_NONE = 0;
    7371           4 :     constexpr int SECTION_DIMENSIONS = 1;
    7372           4 :     constexpr int SECTION_VARIABLES = 2;
    7373           4 :     constexpr int SECTION_DATA = 3;
    7374           4 :     int nActiveSection = SECTION_NONE;
    7375           8 :     std::map<CPLString, int> oMapDimToId;
    7376           8 :     std::map<int, int> oMapDimIdToDimLen;
    7377           8 :     std::map<CPLString, int> oMapVarToId;
    7378           8 :     std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
    7379           8 :     std::map<int, int> oMapVarIdToType;
    7380           4 :     std::set<CPLString> oSetAttrDefined;
    7381           4 :     oMapVarToId[""] = -1;
    7382           4 :     size_t nTotalVarSize = 0;
    7383         208 :     while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
    7384             :     {
    7385         204 :         if (STARTS_WITH(pszLine, "dimensions:") &&
    7386             :             nActiveSection == SECTION_NONE)
    7387             :         {
    7388           4 :             nActiveSection = SECTION_DIMENSIONS;
    7389             :         }
    7390         200 :         else if (STARTS_WITH(pszLine, "variables:") &&
    7391             :                  nActiveSection == SECTION_DIMENSIONS)
    7392             :         {
    7393           4 :             nActiveSection = SECTION_VARIABLES;
    7394             :         }
    7395         196 :         else if (STARTS_WITH(pszLine, "data:") &&
    7396             :                  nActiveSection == SECTION_VARIABLES)
    7397             :         {
    7398           4 :             nActiveSection = SECTION_DATA;
    7399           4 :             status = nc_enddef(nCdfId);
    7400           4 :             if (status != NC_NOERR)
    7401             :             {
    7402           0 :                 CPLDebug("netCDF", "nc_enddef() failed: %s",
    7403             :                          nc_strerror(status));
    7404             :             }
    7405             :         }
    7406         192 :         else if (nActiveSection == SECTION_DIMENSIONS)
    7407             :         {
    7408           9 :             char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
    7409           9 :             if (CSLCount(papszTokens) == 2)
    7410             :             {
    7411           9 :                 const char *pszDimName = papszTokens[0];
    7412           9 :                 bool bValidName = true;
    7413           9 :                 if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
    7414             :                 {
    7415             :                     // This is an internal netcdf prefix. Using it may
    7416             :                     // cause memory leaks.
    7417           0 :                     bValidName = false;
    7418             :                 }
    7419           9 :                 if (!bValidName)
    7420             :                 {
    7421           0 :                     CPLDebug("netCDF",
    7422             :                              "nc_def_dim(%s) failed: invalid name found",
    7423             :                              pszDimName);
    7424           0 :                     CSLDestroy(papszTokens);
    7425           0 :                     continue;
    7426             :                 }
    7427             : 
    7428             :                 const bool bIsASCII =
    7429           9 :                     CPLIsASCII(pszDimName, static_cast<size_t>(-1));
    7430           9 :                 if (!bIsASCII)
    7431             :                 {
    7432             :                     // Workaround https://github.com/Unidata/netcdf-c/pull/450
    7433           0 :                     CPLDebug("netCDF",
    7434             :                              "nc_def_dim(%s) failed: rejected because "
    7435             :                              "of non-ASCII characters",
    7436             :                              pszDimName);
    7437           0 :                     CSLDestroy(papszTokens);
    7438           0 :                     continue;
    7439             :                 }
    7440           9 :                 int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
    7441             :                                    ? NC_UNLIMITED
    7442           9 :                                    : atoi(papszTokens[1]);
    7443           9 :                 if (nDimSize >= 1000)
    7444           1 :                     nDimSize = 1000;  // to avoid very long processing
    7445           9 :                 if (nDimSize >= 0)
    7446             :                 {
    7447           9 :                     int nDimId = -1;
    7448           9 :                     status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
    7449           9 :                     if (status != NC_NOERR)
    7450             :                     {
    7451           0 :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
    7452             :                                  pszDimName, nDimSize, nc_strerror(status));
    7453             :                     }
    7454             :                     else
    7455             :                     {
    7456             : #ifdef DEBUG_VERBOSE
    7457             :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
    7458             :                                  pszDimName, nDimSize, pszLine);
    7459             : #endif
    7460           9 :                         oMapDimToId[pszDimName] = nDimId;
    7461           9 :                         oMapDimIdToDimLen[nDimId] = nDimSize;
    7462             :                     }
    7463             :                 }
    7464             :             }
    7465           9 :             CSLDestroy(papszTokens);
    7466             :         }
    7467         183 :         else if (nActiveSection == SECTION_VARIABLES)
    7468             :         {
    7469         390 :             while (*pszLine == ' ' || *pszLine == '\t')
    7470         249 :                 pszLine++;
    7471         141 :             const char *pszColumn = strchr(pszLine, ':');
    7472         141 :             const char *pszEqual = strchr(pszLine, '=');
    7473         141 :             if (pszColumn == nullptr)
    7474             :             {
    7475          21 :                 char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
    7476          21 :                 if (CSLCount(papszTokens) >= 2)
    7477             :                 {
    7478          17 :                     const char *pszVarName = papszTokens[1];
    7479          17 :                     bool bValidName = true;
    7480          17 :                     if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
    7481             :                     {
    7482             :                         // This is an internal netcdf prefix. Using it may
    7483             :                         // cause memory leaks.
    7484           0 :                         bValidName = false;
    7485             :                     }
    7486         138 :                     for (int i = 0; pszVarName[i]; i++)
    7487             :                     {
    7488         121 :                         if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
    7489          28 :                               (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
    7490           9 :                               (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
    7491           6 :                               pszVarName[i] == '_'))
    7492             :                         {
    7493           0 :                             bValidName = false;
    7494             :                         }
    7495             :                     }
    7496          17 :                     if (!bValidName)
    7497             :                     {
    7498           0 :                         CPLDebug(
    7499             :                             "netCDF",
    7500             :                             "nc_def_var(%s) failed: illegal character found",
    7501             :                             pszVarName);
    7502           0 :                         CSLDestroy(papszTokens);
    7503           0 :                         continue;
    7504             :                     }
    7505          17 :                     if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
    7506             :                     {
    7507           0 :                         CPLDebug("netCDF",
    7508             :                                  "nc_def_var(%s) failed: already defined",
    7509             :                                  pszVarName);
    7510           0 :                         CSLDestroy(papszTokens);
    7511           0 :                         continue;
    7512             :                     }
    7513          17 :                     const char *pszVarType = papszTokens[0];
    7514          17 :                     int nc_datatype = NC_BYTE;
    7515          17 :                     size_t nDataTypeSize = 1;
    7516          17 :                     if (EQUAL(pszVarType, "char"))
    7517             :                     {
    7518           6 :                         nc_datatype = NC_CHAR;
    7519           6 :                         nDataTypeSize = 1;
    7520             :                     }
    7521          11 :                     else if (EQUAL(pszVarType, "byte"))
    7522             :                     {
    7523           3 :                         nc_datatype = NC_BYTE;
    7524           3 :                         nDataTypeSize = 1;
    7525             :                     }
    7526           8 :                     else if (EQUAL(pszVarType, "short"))
    7527             :                     {
    7528           0 :                         nc_datatype = NC_SHORT;
    7529           0 :                         nDataTypeSize = 2;
    7530             :                     }
    7531           8 :                     else if (EQUAL(pszVarType, "int"))
    7532             :                     {
    7533           0 :                         nc_datatype = NC_INT;
    7534           0 :                         nDataTypeSize = 4;
    7535             :                     }
    7536           8 :                     else if (EQUAL(pszVarType, "float"))
    7537             :                     {
    7538           0 :                         nc_datatype = NC_FLOAT;
    7539           0 :                         nDataTypeSize = 4;
    7540             :                     }
    7541           8 :                     else if (EQUAL(pszVarType, "double"))
    7542             :                     {
    7543           8 :                         nc_datatype = NC_DOUBLE;
    7544           8 :                         nDataTypeSize = 8;
    7545             :                     }
    7546           0 :                     else if (EQUAL(pszVarType, "ubyte"))
    7547             :                     {
    7548           0 :                         nc_datatype = NC_UBYTE;
    7549           0 :                         nDataTypeSize = 1;
    7550             :                     }
    7551           0 :                     else if (EQUAL(pszVarType, "ushort"))
    7552             :                     {
    7553           0 :                         nc_datatype = NC_USHORT;
    7554           0 :                         nDataTypeSize = 2;
    7555             :                     }
    7556           0 :                     else if (EQUAL(pszVarType, "uint"))
    7557             :                     {
    7558           0 :                         nc_datatype = NC_UINT;
    7559           0 :                         nDataTypeSize = 4;
    7560             :                     }
    7561           0 :                     else if (EQUAL(pszVarType, "int64"))
    7562             :                     {
    7563           0 :                         nc_datatype = NC_INT64;
    7564           0 :                         nDataTypeSize = 8;
    7565             :                     }
    7566           0 :                     else if (EQUAL(pszVarType, "uint64"))
    7567             :                     {
    7568           0 :                         nc_datatype = NC_UINT64;
    7569           0 :                         nDataTypeSize = 8;
    7570             :                     }
    7571             : 
    7572          17 :                     int nDims = CSLCount(papszTokens) - 2;
    7573          17 :                     if (nDims >= 32)
    7574             :                     {
    7575             :                         // The number of dimensions in a netCDFv4 file is
    7576             :                         // limited by #define H5S_MAX_RANK    32
    7577             :                         // but libnetcdf doesn't check that...
    7578           0 :                         CPLDebug("netCDF",
    7579             :                                  "nc_def_var(%s) failed: too many dimensions",
    7580             :                                  pszVarName);
    7581           0 :                         CSLDestroy(papszTokens);
    7582           0 :                         continue;
    7583             :                     }
    7584          17 :                     std::vector<int> aoDimIds;
    7585          17 :                     bool bFailed = false;
    7586          17 :                     size_t nSize = 1;
    7587          35 :                     for (int i = 0; i < nDims; i++)
    7588             :                     {
    7589          18 :                         const char *pszDimName = papszTokens[2 + i];
    7590          18 :                         if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
    7591             :                         {
    7592           0 :                             bFailed = true;
    7593           0 :                             break;
    7594             :                         }
    7595          18 :                         const int nDimId = oMapDimToId[pszDimName];
    7596          18 :                         aoDimIds.push_back(nDimId);
    7597             : 
    7598          18 :                         const size_t nDimSize = oMapDimIdToDimLen[nDimId];
    7599          18 :                         if (nDimSize != 0)
    7600             :                         {
    7601          18 :                             if (nSize >
    7602          18 :                                 std::numeric_limits<size_t>::max() / nDimSize)
    7603             :                             {
    7604           0 :                                 bFailed = true;
    7605           0 :                                 break;
    7606             :                             }
    7607             :                             else
    7608             :                             {
    7609          18 :                                 nSize *= nDimSize;
    7610             :                             }
    7611             :                         }
    7612             :                     }
    7613          17 :                     if (bFailed)
    7614             :                     {
    7615           0 :                         CPLDebug("netCDF",
    7616             :                                  "nc_def_var(%s) failed: unknown dimension(s)",
    7617             :                                  pszVarName);
    7618           0 :                         CSLDestroy(papszTokens);
    7619           0 :                         continue;
    7620             :                     }
    7621          17 :                     if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
    7622             :                     {
    7623           0 :                         CPLDebug("netCDF",
    7624             :                                  "nc_def_var(%s) failed: too large data",
    7625             :                                  pszVarName);
    7626           0 :                         CSLDestroy(papszTokens);
    7627           0 :                         continue;
    7628             :                     }
    7629          17 :                     if (nTotalVarSize >
    7630          34 :                             std::numeric_limits<size_t>::max() - nSize ||
    7631          17 :                         nTotalVarSize + nSize > 100 * 1024 * 1024)
    7632             :                     {
    7633           0 :                         CPLDebug("netCDF",
    7634             :                                  "nc_def_var(%s) failed: too large data",
    7635             :                                  pszVarName);
    7636           0 :                         CSLDestroy(papszTokens);
    7637           0 :                         continue;
    7638             :                     }
    7639          17 :                     nTotalVarSize += nSize;
    7640             : 
    7641          17 :                     int nVarId = -1;
    7642             :                     status =
    7643          30 :                         nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
    7644          13 :                                    (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
    7645          17 :                     if (status != NC_NOERR)
    7646             :                     {
    7647           0 :                         CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
    7648             :                                  pszVarName, nc_strerror(status));
    7649             :                     }
    7650             :                     else
    7651             :                     {
    7652             : #ifdef DEBUG_VERBOSE
    7653             :                         CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
    7654             :                                  pszVarName, pszLine);
    7655             : #endif
    7656          17 :                         oMapVarToId[pszVarName] = nVarId;
    7657          17 :                         oMapVarIdToType[nVarId] = nc_datatype;
    7658          17 :                         oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
    7659             :                     }
    7660             :                 }
    7661          21 :                 CSLDestroy(papszTokens);
    7662             :             }
    7663         120 :             else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
    7664             :             {
    7665         116 :                 CPLString osVarName(pszLine, pszColumn - pszLine);
    7666         116 :                 CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
    7667         116 :                 osAttrName.Trim();
    7668         116 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7669             :                 {
    7670           0 :                     CPLDebug("netCDF",
    7671             :                              "nc_put_att(%s:%s) failed: "
    7672             :                              "no corresponding variable",
    7673             :                              osVarName.c_str(), osAttrName.c_str());
    7674           0 :                     continue;
    7675             :                 }
    7676         116 :                 bool bValidName = true;
    7677        1743 :                 for (size_t i = 0; i < osAttrName.size(); i++)
    7678             :                 {
    7679        1865 :                     if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
    7680         238 :                           (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
    7681         158 :                           (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
    7682         158 :                           osAttrName[i] == '_'))
    7683             :                     {
    7684           0 :                         bValidName = false;
    7685             :                     }
    7686             :                 }
    7687         116 :                 if (!bValidName)
    7688             :                 {
    7689           0 :                     CPLDebug(
    7690             :                         "netCDF",
    7691             :                         "nc_put_att(%s:%s) failed: illegal character found",
    7692             :                         osVarName.c_str(), osAttrName.c_str());
    7693           0 :                     continue;
    7694             :                 }
    7695         116 :                 if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
    7696         232 :                     oSetAttrDefined.end())
    7697             :                 {
    7698           0 :                     CPLDebug("netCDF",
    7699             :                              "nc_put_att(%s:%s) failed: already defined",
    7700             :                              osVarName.c_str(), osAttrName.c_str());
    7701           0 :                     continue;
    7702             :                 }
    7703             : 
    7704         116 :                 const int nVarId = oMapVarToId[osVarName];
    7705         116 :                 const char *pszValue = pszEqual + 1;
    7706         232 :                 while (*pszValue == ' ')
    7707         116 :                     pszValue++;
    7708             : 
    7709         116 :                 status = NC_EBADTYPE;
    7710         116 :                 if (*pszValue == '"')
    7711             :                 {
    7712             :                     // For _FillValue, the attribute type should match
    7713             :                     // the variable type. Leaks memory with NC4 otherwise
    7714          74 :                     if (osAttrName == "_FillValue")
    7715             :                     {
    7716           0 :                         CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7717             :                                  osVarName.c_str(), osAttrName.c_str(),
    7718             :                                  nc_strerror(status));
    7719           0 :                         continue;
    7720             :                     }
    7721             : 
    7722             :                     // Unquote and unescape string value
    7723          74 :                     CPLString osVal(pszValue + 1);
    7724         222 :                     while (!osVal.empty())
    7725             :                     {
    7726         222 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7727             :                         {
    7728         148 :                             osVal.pop_back();
    7729             :                         }
    7730          74 :                         else if (osVal.back() == '"')
    7731             :                         {
    7732          74 :                             osVal.pop_back();
    7733          74 :                             break;
    7734             :                         }
    7735             :                         else
    7736             :                         {
    7737           0 :                             break;
    7738             :                         }
    7739             :                     }
    7740          74 :                     osVal.replaceAll("\\\"", '"');
    7741          74 :                     status = nc_put_att_text(nCdfId, nVarId, osAttrName,
    7742             :                                              osVal.size(), osVal.c_str());
    7743             :                 }
    7744             :                 else
    7745             :                 {
    7746          84 :                     CPLString osVal(pszValue);
    7747         126 :                     while (!osVal.empty())
    7748             :                     {
    7749         126 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7750             :                         {
    7751          84 :                             osVal.pop_back();
    7752             :                         }
    7753             :                         else
    7754             :                         {
    7755          42 :                             break;
    7756             :                         }
    7757             :                     }
    7758          42 :                     int nc_datatype = -1;
    7759          42 :                     if (!osVal.empty() && osVal.back() == 'b')
    7760             :                     {
    7761           3 :                         nc_datatype = NC_BYTE;
    7762           3 :                         osVal.pop_back();
    7763             :                     }
    7764          39 :                     else if (!osVal.empty() && osVal.back() == 's')
    7765             :                     {
    7766           3 :                         nc_datatype = NC_SHORT;
    7767           3 :                         osVal.pop_back();
    7768             :                     }
    7769          42 :                     if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
    7770             :                     {
    7771           7 :                         if (nc_datatype < 0)
    7772           4 :                             nc_datatype = NC_INT;
    7773             :                     }
    7774          35 :                     else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
    7775             :                     {
    7776          32 :                         nc_datatype = NC_DOUBLE;
    7777             :                     }
    7778             :                     else
    7779             :                     {
    7780           3 :                         nc_datatype = -1;
    7781             :                     }
    7782             : 
    7783             :                     // For _FillValue, check that the attribute type matches
    7784             :                     // the variable type. Leaks memory with NC4 otherwise
    7785          42 :                     if (osAttrName == "_FillValue")
    7786             :                     {
    7787           6 :                         if (nVarId < 0 ||
    7788           3 :                             nc_datatype != oMapVarIdToType[nVarId])
    7789             :                         {
    7790           0 :                             nc_datatype = -1;
    7791             :                         }
    7792             :                     }
    7793             : 
    7794          42 :                     if (nc_datatype == NC_BYTE)
    7795             :                     {
    7796             :                         signed char chVal =
    7797           3 :                             static_cast<signed char>(atoi(osVal));
    7798           3 :                         status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
    7799             :                                                   NC_BYTE, 1, &chVal);
    7800             :                     }
    7801          39 :                     else if (nc_datatype == NC_SHORT)
    7802             :                     {
    7803           0 :                         short nVal = static_cast<short>(atoi(osVal));
    7804           0 :                         status = nc_put_att_short(nCdfId, nVarId, osAttrName,
    7805             :                                                   NC_SHORT, 1, &nVal);
    7806             :                     }
    7807          39 :                     else if (nc_datatype == NC_INT)
    7808             :                     {
    7809           4 :                         int nVal = static_cast<int>(atoi(osVal));
    7810           4 :                         status = nc_put_att_int(nCdfId, nVarId, osAttrName,
    7811             :                                                 NC_INT, 1, &nVal);
    7812             :                     }
    7813          35 :                     else if (nc_datatype == NC_DOUBLE)
    7814             :                     {
    7815          32 :                         double dfVal = CPLAtof(osVal);
    7816          32 :                         status = nc_put_att_double(nCdfId, nVarId, osAttrName,
    7817             :                                                    NC_DOUBLE, 1, &dfVal);
    7818             :                     }
    7819             :                 }
    7820         116 :                 if (status != NC_NOERR)
    7821             :                 {
    7822           3 :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7823             :                              osVarName.c_str(), osAttrName.c_str(),
    7824             :                              nc_strerror(status));
    7825             :                 }
    7826             :                 else
    7827             :                 {
    7828         113 :                     oSetAttrDefined.insert(osVarName + ":" + osAttrName);
    7829             : #ifdef DEBUG_VERBOSE
    7830             :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
    7831             :                              osVarName.c_str(), osAttrName.c_str(), pszLine);
    7832             : #endif
    7833             :                 }
    7834             :             }
    7835             :         }
    7836          42 :         else if (nActiveSection == SECTION_DATA)
    7837             :         {
    7838          55 :             while (*pszLine == ' ' || *pszLine == '\t')
    7839          17 :                 pszLine++;
    7840          38 :             const char *pszEqual = strchr(pszLine, '=');
    7841          38 :             if (pszEqual)
    7842             :             {
    7843          17 :                 CPLString osVarName(pszLine, pszEqual - pszLine);
    7844          17 :                 osVarName.Trim();
    7845          17 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7846           0 :                     continue;
    7847          17 :                 const int nVarId = oMapVarToId[osVarName];
    7848          17 :                 CPLString osAccVal(pszEqual + 1);
    7849          17 :                 osAccVal.Trim();
    7850         153 :                 while (osAccVal.empty() || osAccVal.back() != ';')
    7851             :                 {
    7852         136 :                     pszLine = CPLReadLineL(fpSrc);
    7853         136 :                     if (pszLine == nullptr)
    7854           0 :                         break;
    7855         272 :                     CPLString osVal(pszLine);
    7856         136 :                     osVal.Trim();
    7857         136 :                     osAccVal += osVal;
    7858             :                 }
    7859          17 :                 if (pszLine == nullptr)
    7860           0 :                     break;
    7861          17 :                 osAccVal.pop_back();
    7862             : 
    7863             :                 const std::vector<int> aoDimIds =
    7864          34 :                     oMapVarIdToVectorOfDimId[nVarId];
    7865          17 :                 size_t nSize = 1;
    7866          34 :                 std::vector<size_t> aoStart, aoEdge;
    7867          17 :                 aoStart.resize(aoDimIds.size());
    7868          17 :                 aoEdge.resize(aoDimIds.size());
    7869          35 :                 for (size_t i = 0; i < aoDimIds.size(); ++i)
    7870             :                 {
    7871          18 :                     const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
    7872          36 :                     if (nDimSize != 0 &&
    7873          18 :                         nSize > std::numeric_limits<size_t>::max() / nDimSize)
    7874             :                     {
    7875           0 :                         nSize = 0;
    7876             :                     }
    7877             :                     else
    7878             :                     {
    7879          18 :                         nSize *= nDimSize;
    7880             :                     }
    7881          18 :                     aoStart[i] = 0;
    7882          18 :                     aoEdge[i] = nDimSize;
    7883             :                 }
    7884             : 
    7885          17 :                 status = NC_EBADTYPE;
    7886          17 :                 if (nSize == 0)
    7887             :                 {
    7888             :                     // Might happen with a unlimited dimension
    7889             :                 }
    7890          17 :                 else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
    7891             :                 {
    7892           8 :                     if (!aoStart.empty())
    7893             :                     {
    7894             :                         char **papszTokens =
    7895           8 :                             CSLTokenizeString2(osAccVal, " ,;", 0);
    7896           8 :                         size_t nTokens = CSLCount(papszTokens);
    7897           8 :                         if (nTokens >= nSize)
    7898             :                         {
    7899             :                             double *padfVals = static_cast<double *>(
    7900           8 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
    7901           8 :                             if (padfVals)
    7902             :                             {
    7903         132 :                                 for (size_t i = 0; i < nSize; i++)
    7904             :                                 {
    7905         124 :                                     padfVals[i] = CPLAtof(papszTokens[i]);
    7906             :                                 }
    7907           8 :                                 status = nc_put_vara_double(
    7908           8 :                                     nCdfId, nVarId, &aoStart[0], &aoEdge[0],
    7909             :                                     padfVals);
    7910           8 :                                 VSIFree(padfVals);
    7911             :                             }
    7912             :                         }
    7913           8 :                         CSLDestroy(papszTokens);
    7914             :                     }
    7915             :                 }
    7916           9 :                 else if (oMapVarIdToType[nVarId] == NC_BYTE)
    7917             :                 {
    7918           3 :                     if (!aoStart.empty())
    7919             :                     {
    7920             :                         char **papszTokens =
    7921           3 :                             CSLTokenizeString2(osAccVal, " ,;", 0);
    7922           3 :                         size_t nTokens = CSLCount(papszTokens);
    7923           3 :                         if (nTokens >= nSize)
    7924             :                         {
    7925             :                             signed char *panVals = static_cast<signed char *>(
    7926           3 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
    7927           3 :                             if (panVals)
    7928             :                             {
    7929        1203 :                                 for (size_t i = 0; i < nSize; i++)
    7930             :                                 {
    7931        1200 :                                     panVals[i] = static_cast<signed char>(
    7932        1200 :                                         atoi(papszTokens[i]));
    7933             :                                 }
    7934           3 :                                 status = nc_put_vara_schar(nCdfId, nVarId,
    7935           3 :                                                            &aoStart[0],
    7936           3 :                                                            &aoEdge[0], panVals);
    7937           3 :                                 VSIFree(panVals);
    7938             :                             }
    7939             :                         }
    7940           3 :                         CSLDestroy(papszTokens);
    7941             :                     }
    7942             :                 }
    7943           6 :                 else if (oMapVarIdToType[nVarId] == NC_CHAR)
    7944             :                 {
    7945           6 :                     if (aoStart.size() == 2)
    7946             :                     {
    7947           4 :                         std::vector<CPLString> aoStrings;
    7948           2 :                         bool bInString = false;
    7949           4 :                         CPLString osCurString;
    7950         935 :                         for (size_t i = 0; i < osAccVal.size();)
    7951             :                         {
    7952         933 :                             if (!bInString)
    7953             :                             {
    7954           8 :                                 if (osAccVal[i] == '"')
    7955             :                                 {
    7956           4 :                                     bInString = true;
    7957           4 :                                     osCurString.clear();
    7958             :                                 }
    7959           8 :                                 i++;
    7960             :                             }
    7961         926 :                             else if (osAccVal[i] == '\\' &&
    7962         926 :                                      i + 1 < osAccVal.size() &&
    7963           1 :                                      osAccVal[i + 1] == '"')
    7964             :                             {
    7965           1 :                                 osCurString += '"';
    7966           1 :                                 i += 2;
    7967             :                             }
    7968         924 :                             else if (osAccVal[i] == '"')
    7969             :                             {
    7970           4 :                                 aoStrings.push_back(osCurString);
    7971           4 :                                 osCurString.clear();
    7972           4 :                                 bInString = false;
    7973           4 :                                 i++;
    7974             :                             }
    7975             :                             else
    7976             :                             {
    7977         920 :                                 osCurString += osAccVal[i];
    7978         920 :                                 i++;
    7979             :                             }
    7980             :                         }
    7981           2 :                         const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
    7982           2 :                         const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
    7983           2 :                         size_t nIters = aoStrings.size();
    7984           2 :                         if (nIters > nRecords)
    7985           0 :                             nIters = nRecords;
    7986           6 :                         for (size_t i = 0; i < nIters; i++)
    7987             :                         {
    7988             :                             size_t anIndex[2];
    7989           4 :                             anIndex[0] = i;
    7990           4 :                             anIndex[1] = 0;
    7991             :                             size_t anCount[2];
    7992           4 :                             anCount[0] = 1;
    7993           4 :                             anCount[1] = aoStrings[i].size();
    7994           4 :                             if (anCount[1] > nWidth)
    7995           0 :                                 anCount[1] = nWidth;
    7996             :                             status =
    7997           4 :                                 nc_put_vara_text(nCdfId, nVarId, anIndex,
    7998           4 :                                                  anCount, aoStrings[i].c_str());
    7999           4 :                             if (status != NC_NOERR)
    8000           0 :                                 break;
    8001             :                         }
    8002             :                     }
    8003             :                 }
    8004          17 :                 if (status != NC_NOERR)
    8005             :                 {
    8006           4 :                     CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
    8007             :                              osVarName.c_str(), nc_strerror(status));
    8008             :                 }
    8009             :             }
    8010             :         }
    8011             :     }
    8012             : 
    8013           4 :     GDAL_nc_close(nCdfId);
    8014           4 :     return true;
    8015             : }
    8016             : 
    8017             : #endif  // ENABLE_NCDUMP
    8018             : 
    8019             : /************************************************************************/
    8020             : /*                                Open()                                */
    8021             : /************************************************************************/
    8022             : 
    8023         678 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
    8024             : 
    8025             : {
    8026             : #ifdef NCDF_DEBUG
    8027             :     CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
    8028             :              poOpenInfo->pszFilename);
    8029             : #endif
    8030             : 
    8031             :     // Does this appear to be a netcdf file?
    8032         678 :     NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
    8033         678 :     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8034             :     {
    8035         618 :         eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
    8036             : #ifdef NCDF_DEBUG
    8037             :         CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
    8038             : #endif
    8039             :         // Note: not calling Identify() directly, because we want the file type.
    8040             :         // Only support NCDF_FORMAT* formats.
    8041         618 :         if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
    8042           2 :             NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
    8043             :         {
    8044             :             // ok
    8045             :         }
    8046           2 :         else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
    8047           0 :                  poOpenInfo->IsSingleAllowedDriver("netCDF"))
    8048             :         {
    8049             :             // ok
    8050             :         }
    8051             :         else
    8052             :         {
    8053           2 :             return nullptr;
    8054             :         }
    8055             :     }
    8056             :     else
    8057             :     {
    8058             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    8059             :         // We don't necessarily want to catch bugs in libnetcdf ...
    8060             :         if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
    8061             :         {
    8062             :             return nullptr;
    8063             :         }
    8064             : #endif
    8065             :     }
    8066             : 
    8067         676 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
    8068             :     {
    8069         183 :         return OpenMultiDim(poOpenInfo);
    8070             :     }
    8071             : 
    8072         986 :     CPLMutexHolderD(&hNCMutex);
    8073             : 
    8074         493 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    8075             :         // GDALDataset own mutex.
    8076         493 :     netCDFDataset *poDS = new netCDFDataset();
    8077         493 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    8078         493 :     CPLAcquireMutex(hNCMutex, 1000.0);
    8079             : 
    8080         493 :     poDS->SetDescription(poOpenInfo->pszFilename);
    8081             : 
    8082             :     // Check if filename start with NETCDF: tag.
    8083         493 :     bool bTreatAsSubdataset = false;
    8084         986 :     CPLString osSubdatasetName;
    8085             : 
    8086             : #ifdef ENABLE_NCDUMP
    8087         493 :     const char *pszHeader =
    8088             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    8089         493 :     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
    8090           3 :         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
    8091             :     {
    8092             :         // By default create a temporary file that will be destroyed,
    8093             :         // unless NETCDF_TMP_FILE is defined. Can be useful to see which
    8094             :         // netCDF file has been generated from a potential fuzzed input.
    8095           3 :         poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
    8096           3 :         if (poDS->osFilename.empty())
    8097             :         {
    8098           3 :             poDS->bFileToDestroyAtClosing = true;
    8099           3 :             poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
    8100             :         }
    8101           3 :         if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
    8102             :                                          poOpenInfo->fpL))
    8103             :         {
    8104           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8105             :                 // deadlock with GDALDataset own mutex.
    8106           0 :             delete poDS;
    8107           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8108           0 :             return nullptr;
    8109             :         }
    8110           3 :         bTreatAsSubdataset = false;
    8111           3 :         poDS->eFormat = eTmpFormat;
    8112             :     }
    8113             :     else
    8114             : #endif
    8115             : 
    8116         490 :         if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8117             :     {
    8118             :         char **papszName =
    8119          60 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    8120             :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
    8121             : 
    8122         120 :         if (CSLCount(papszName) >= 3 &&
    8123          60 :             ((strlen(papszName[1]) == 1 && /* D:\\bla */
    8124           0 :               (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
    8125          60 :              EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
    8126          60 :              EQUAL(papszName[1], "/vsicurl/http") ||
    8127          60 :              EQUAL(papszName[1], "/vsicurl/https") ||
    8128          60 :              EQUAL(papszName[1], "/vsicurl_streaming/http") ||
    8129          60 :              EQUAL(papszName[1], "/vsicurl_streaming/https")))
    8130             :         {
    8131           0 :             const int nCountBefore = CSLCount(papszName);
    8132           0 :             CPLString osTmp = papszName[1];
    8133           0 :             osTmp += ':';
    8134           0 :             osTmp += papszName[2];
    8135           0 :             CPLFree(papszName[1]);
    8136           0 :             CPLFree(papszName[2]);
    8137           0 :             papszName[1] = CPLStrdup(osTmp);
    8138           0 :             memmove(papszName + 2, papszName + 3,
    8139           0 :                     (nCountBefore - 2) * sizeof(char *));
    8140             :         }
    8141             : 
    8142          60 :         if (CSLCount(papszName) == 3)
    8143             :         {
    8144          60 :             poDS->osFilename = papszName[1];
    8145          60 :             osSubdatasetName = papszName[2];
    8146          60 :             bTreatAsSubdataset = true;
    8147          60 :             CSLDestroy(papszName);
    8148             :         }
    8149           0 :         else if (CSLCount(papszName) == 2)
    8150             :         {
    8151           0 :             poDS->osFilename = papszName[1];
    8152           0 :             osSubdatasetName = "";
    8153           0 :             bTreatAsSubdataset = false;
    8154           0 :             CSLDestroy(papszName);
    8155             :         }
    8156             :         else
    8157             :         {
    8158           0 :             CSLDestroy(papszName);
    8159           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8160             :                 // deadlock with GDALDataset own mutex.
    8161           0 :             delete poDS;
    8162           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8163           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8164             :                      "Failed to parse NETCDF: prefix string into expected 2, 3 "
    8165             :                      "or 4 fields.");
    8166           0 :             return nullptr;
    8167             :         }
    8168             : 
    8169         120 :         if (!STARTS_WITH(poDS->osFilename, "http://") &&
    8170          60 :             !STARTS_WITH(poDS->osFilename, "https://"))
    8171             :         {
    8172             :             // Identify Format from real file, with bCheckExt=FALSE.
    8173             :             GDALOpenInfo *poOpenInfo2 =
    8174          60 :                 new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
    8175          60 :             poDS->eFormat =
    8176          60 :                 netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
    8177          60 :             delete poOpenInfo2;
    8178          60 :             if (NCDF_FORMAT_NONE == poDS->eFormat ||
    8179          60 :                 NCDF_FORMAT_UNKNOWN == poDS->eFormat)
    8180             :             {
    8181           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8182             :                     // deadlock with GDALDataset own mutex.
    8183           0 :                 delete poDS;
    8184           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    8185           0 :                 return nullptr;
    8186             :             }
    8187             :         }
    8188             :     }
    8189             :     else
    8190             :     {
    8191         430 :         poDS->osFilename = poOpenInfo->pszFilename;
    8192         430 :         bTreatAsSubdataset = false;
    8193         430 :         poDS->eFormat = eTmpFormat;
    8194             :     }
    8195             : 
    8196             : // Try opening the dataset.
    8197             : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
    8198             :     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
    8199             :              poDS->osFilename.c_str());
    8200             : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
    8201             :     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
    8202             : #endif
    8203         493 :     int cdfid = -1;
    8204         493 :     const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
    8205             :                           ? NC_WRITE
    8206             :                           : NC_NOWRITE;
    8207         986 :     CPLString osFilenameForNCOpen(poDS->osFilename);
    8208             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    8209             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    8210             :     {
    8211             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    8212             :         osFilenameForNCOpen = pszTemp;
    8213             :         CPLFree(pszTemp);
    8214             :     }
    8215             : #endif
    8216         493 :     int status2 = -1;
    8217             : 
    8218             : #ifdef ENABLE_UFFD
    8219         493 :     cpl_uffd_context *pCtx = nullptr;
    8220             : #endif
    8221             : 
    8222         508 :     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
    8223          15 :         poOpenInfo->eAccess == GA_ReadOnly)
    8224             :     {
    8225          15 :         vsi_l_offset nLength = 0;
    8226          15 :         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
    8227          15 :         if (poDS->fpVSIMEM)
    8228             :         {
    8229             :             // We assume that the file will not be modified. If it is, then
    8230             :             // pabyBuffer might become invalid.
    8231             :             GByte *pabyBuffer =
    8232          15 :                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
    8233          15 :             if (pabyBuffer)
    8234             :             {
    8235          15 :                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
    8236             :                                       nMode, static_cast<size_t>(nLength),
    8237             :                                       pabyBuffer, &cdfid);
    8238             :             }
    8239             :         }
    8240             :     }
    8241             :     else
    8242             :     {
    8243             :         const bool bVsiFile =
    8244         478 :             !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
    8245             : #ifdef ENABLE_UFFD
    8246         478 :         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
    8247         478 :         void *pVma = nullptr;
    8248         478 :         uint64_t nVmaSize = 0;
    8249             : 
    8250         478 :         if (bVsiFile)
    8251             :         {
    8252           2 :             if (bReadOnly)
    8253             :             {
    8254           2 :                 if (CPLIsUserFaultMappingSupported())
    8255             :                 {
    8256           2 :                     pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
    8257             :                                                      &nVmaSize);
    8258             :                 }
    8259             :                 else
    8260             :                 {
    8261           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    8262             :                              "Opening a /vsi file with the netCDF driver "
    8263             :                              "requires Linux userfaultfd to be available. "
    8264             :                              "If running from Docker, "
    8265             :                              "--security-opt seccomp=unconfined might be "
    8266             :                              "needed.%s",
    8267           0 :                              ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8268           0 :                                poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8269           0 :                               GDALGetDriverByName("HDF5"))
    8270             :                                  ? " Or you may set the GDAL_SKIP=netCDF "
    8271             :                                    "configuration option to force the use of "
    8272             :                                    "the HDF5 driver."
    8273             :                                  : "");
    8274             :                 }
    8275             :             }
    8276             :             else
    8277             :             {
    8278           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    8279             :                          "Opening a /vsi file with the netCDF driver is only "
    8280             :                          "supported in read-only mode");
    8281             :             }
    8282             :         }
    8283         478 :         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
    8284             :         {
    8285             :             // netCDF code, at least for netCDF 4.7.0, is confused by filenames
    8286             :             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
    8287             :             // final part
    8288           2 :             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
    8289             :                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);
    8290             :         }
    8291             :         else
    8292         476 :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8293             : #else
    8294             :         if (bVsiFile)
    8295             :         {
    8296             :             CPLError(
    8297             :                 CE_Failure, CPLE_AppDefined,
    8298             :                 "Opening a /vsi file with the netCDF driver requires Linux "
    8299             :                 "userfaultfd to be available.%s",
    8300             :                 ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8301             :                   poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8302             :                  GDALGetDriverByName("HDF5"))
    8303             :                     ? " Or you may set the GDAL_SKIP=netCDF "
    8304             :                       "configuration option to force the use of the HDF5 "
    8305             :                       "driver."
    8306             :                     : "");
    8307             :             status2 = NC_EIO;
    8308             :         }
    8309             :         else
    8310             :         {
    8311             :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8312             :         }
    8313             : #endif
    8314             :     }
    8315         493 :     if (status2 != NC_NOERR)
    8316             :     {
    8317             : #ifdef NCDF_DEBUG
    8318             :         CPLDebug("GDAL_netCDF", "error opening");
    8319             : #endif
    8320           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8321             :             // with GDALDataset own mutex.
    8322           0 :         delete poDS;
    8323           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8324           0 :         return nullptr;
    8325             :     }
    8326             : #ifdef NCDF_DEBUG
    8327             :     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
    8328             : #endif
    8329             : 
    8330             : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
    8331             :     // Try to destroy the temporary file right now on Unix
    8332         493 :     if (poDS->bFileToDestroyAtClosing)
    8333             :     {
    8334           3 :         if (VSIUnlink(poDS->osFilename) == 0)
    8335             :         {
    8336           3 :             poDS->bFileToDestroyAtClosing = false;
    8337             :         }
    8338             :     }
    8339             : #endif
    8340             : 
    8341             :     // Is this a real netCDF file?
    8342             :     int ndims;
    8343             :     int ngatts;
    8344             :     int nvars;
    8345             :     int unlimdimid;
    8346         493 :     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
    8347         493 :     if (status != NC_NOERR)
    8348             :     {
    8349           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8350             :             // with GDALDataset own mutex.
    8351           0 :         delete poDS;
    8352           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8353           0 :         return nullptr;
    8354             :     }
    8355             : 
    8356             :     // Get file type from netcdf.
    8357         493 :     int nTmpFormat = NCDF_FORMAT_NONE;
    8358         493 :     status = nc_inq_format(cdfid, &nTmpFormat);
    8359         493 :     if (status != NC_NOERR)
    8360             :     {
    8361           0 :         NCDF_ERR(status);
    8362             :     }
    8363             :     else
    8364             :     {
    8365         493 :         CPLDebug("GDAL_netCDF",
    8366             :                  "driver detected file type=%d, libnetcdf detected type=%d",
    8367         493 :                  poDS->eFormat, nTmpFormat);
    8368         493 :         if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
    8369             :         {
    8370             :             // Warn if file detection conflicts with that from libnetcdf
    8371             :             // except for NC4C, which we have no way of detecting initially.
    8372          26 :             if (nTmpFormat != NCDF_FORMAT_NC4C &&
    8373          13 :                 !STARTS_WITH(poDS->osFilename, "http://") &&
    8374           0 :                 !STARTS_WITH(poDS->osFilename, "https://"))
    8375             :             {
    8376           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    8377             :                          "NetCDF driver detected file type=%d, but libnetcdf "
    8378             :                          "detected type=%d",
    8379           0 :                          poDS->eFormat, nTmpFormat);
    8380             :             }
    8381          13 :             CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
    8382          13 :                      nTmpFormat, poDS->eFormat);
    8383          13 :             poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
    8384             :         }
    8385             :     }
    8386             : 
    8387             :     // Does the request variable exist?
    8388         493 :     if (bTreatAsSubdataset)
    8389             :     {
    8390             :         int dummy;
    8391          60 :         if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
    8392          60 :                                &dummy) != CE_None)
    8393             :         {
    8394           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    8395             :                      "%s is a netCDF file, but %s is not a variable.",
    8396             :                      poOpenInfo->pszFilename, osSubdatasetName.c_str());
    8397             : 
    8398           0 :             GDAL_nc_close(cdfid);
    8399             : #ifdef ENABLE_UFFD
    8400           0 :             NETCDF_UFFD_UNMAP(pCtx);
    8401             : #endif
    8402           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8403             :                 // deadlock with GDALDataset own mutex.
    8404           0 :             delete poDS;
    8405           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8406           0 :             return nullptr;
    8407             :         }
    8408             :     }
    8409             : 
    8410             :     // Figure out whether or not the listed dataset has support for simple
    8411             :     // geometries (CF-1.8)
    8412         493 :     poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
    8413         493 :     bool bHasSimpleGeometries = false;  // but not necessarily valid
    8414         493 :     if (poDS->nCFVersion >= 1.8)
    8415             :     {
    8416          75 :         bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
    8417          75 :         if (bHasSimpleGeometries)
    8418             :         {
    8419          67 :             poDS->bSGSupport = true;
    8420          67 :             poDS->vcdf.enableFullVirtualMode();
    8421             :         }
    8422             :     }
    8423             : 
    8424             :     char szConventions[NC_MAX_NAME + 1];
    8425         493 :     szConventions[0] = '\0';
    8426         493 :     nc_type nAttype = NC_NAT;
    8427         493 :     size_t nAttlen = 0;
    8428         493 :     nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
    8429         986 :     if (nAttlen >= sizeof(szConventions) ||
    8430         493 :         nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
    8431             :             NC_NOERR)
    8432             :     {
    8433          57 :         CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
    8434             :         // Note that 'Conventions' is always capital 'C' in CF spec.
    8435             :     }
    8436             :     else
    8437             :     {
    8438         436 :         szConventions[nAttlen] = '\0';
    8439             :     }
    8440             : 
    8441             :     // Create band information objects.
    8442         493 :     CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
    8443             : 
    8444             :     // Create a corresponding GDALDataset.
    8445             :     // Create Netcdf Subdataset if filename as NETCDF tag.
    8446         493 :     poDS->cdfid = cdfid;
    8447             : #ifdef ENABLE_UFFD
    8448         493 :     poDS->pCtx = pCtx;
    8449             : #endif
    8450         493 :     poDS->eAccess = poOpenInfo->eAccess;
    8451         493 :     poDS->bDefineMode = false;
    8452             : 
    8453         493 :     poDS->ReadAttributes(cdfid, NC_GLOBAL);
    8454             : 
    8455             :     // Identify coordinate and boundary variables that we should
    8456             :     // ignore as Raster Bands.
    8457         493 :     char **papszIgnoreVars = nullptr;
    8458         493 :     NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
    8459             :     // Filter variables to keep only valid 2+D raster bands and vector fields.
    8460         493 :     int nRasterVars = 0;
    8461         493 :     int nIgnoredVars = 0;
    8462         493 :     int nGroupID = -1;
    8463         493 :     int nVarID = -1;
    8464             : 
    8465             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
    8466         986 :         oMap2DDimsToGroupAndVar;
    8467        1138 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8468         152 :         STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
    8469             :                                          "NC_GLOBAL#mission_name", ""),
    8470           1 :                     "Sentinel 3") &&
    8471           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8472             :                                    "NC_GLOBAL#altimeter_sensor_name", ""),
    8473         645 :               "SRAL") &&
    8474           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8475             :                                    "NC_GLOBAL#radiometer_sensor_name", ""),
    8476             :               "MWR"))
    8477             :     {
    8478           1 :         if (poDS->eAccess == GA_Update)
    8479             :         {
    8480           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8481             :                 // deadlock with GDALDataset own mutex.
    8482           0 :             delete poDS;
    8483           0 :             return nullptr;
    8484             :         }
    8485           1 :         poDS->ProcessSentinel3_SRAL_MWR();
    8486             :     }
    8487             :     else
    8488             :     {
    8489         492 :         poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
    8490         643 :                          (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8491         151 :                              !bHasSimpleGeometries,
    8492             :                          papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
    8493             :                          &nIgnoredVars, oMap2DDimsToGroupAndVar);
    8494             :     }
    8495         493 :     CSLDestroy(papszIgnoreVars);
    8496             : 
    8497             :     // Case where there is no raster variable
    8498         493 :     if (nRasterVars == 0 && !bTreatAsSubdataset)
    8499             :     {
    8500         119 :         poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8501         119 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8502             :             // with GDALDataset own mutex.
    8503         119 :         poDS->TryLoadXML();
    8504             :         // If the dataset has been opened in raster mode only, exit
    8505         119 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    8506           9 :             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    8507             :         {
    8508           4 :             delete poDS;
    8509           4 :             poDS = nullptr;
    8510             :         }
    8511             :         // Otherwise if the dataset is opened in vector mode, that there is
    8512             :         // no vector layer and we are in read-only, exit too.
    8513         115 :         else if (poDS->GetLayerCount() == 0 &&
    8514         123 :                  (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8515           8 :                  poOpenInfo->eAccess == GA_ReadOnly)
    8516             :         {
    8517           8 :             delete poDS;
    8518           8 :             poDS = nullptr;
    8519             :         }
    8520         119 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8521         119 :         return poDS;
    8522             :     }
    8523             : 
    8524             :     // We have more than one variable with 2 dimensions in the
    8525             :     // file, then treat this as a subdataset container dataset.
    8526         374 :     bool bSeveralVariablesAsBands = false;
    8527         374 :     const bool bListAllArrays = CPLTestBool(
    8528         374 :         CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    8529         374 :     if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
    8530             :     {
    8531          28 :         if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
    8532          34 :                          false) &&
    8533           6 :             oMap2DDimsToGroupAndVar.size() == 1)
    8534             :         {
    8535           6 :             std::tie(nGroupID, nVarID) =
    8536          12 :                 oMap2DDimsToGroupAndVar.begin()->second.front();
    8537           6 :             bSeveralVariablesAsBands = true;
    8538             :         }
    8539             :         else
    8540             :         {
    8541          22 :             poDS->CreateSubDatasetList(cdfid);
    8542          22 :             poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8543          22 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8544             :                 // deadlock with GDALDataset own mutex.
    8545          22 :             poDS->TryLoadXML();
    8546          22 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8547          22 :             return poDS;
    8548             :         }
    8549             :     }
    8550             : 
    8551             :     // If we are not treating things as a subdataset, then capture
    8552             :     // the name of the single available variable as the subdataset.
    8553         352 :     if (!bTreatAsSubdataset)
    8554             :     {
    8555         292 :         char *pszVarName = nullptr;
    8556         292 :         NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
    8557         292 :         osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
    8558         292 :         CPLFree(pszVarName);
    8559             :     }
    8560             : 
    8561             :     // We have ignored at least one variable, so we should report them
    8562             :     // as subdatasets for reference.
    8563         352 :     if (nIgnoredVars > 0 && !bTreatAsSubdataset)
    8564             :     {
    8565          24 :         CPLDebug("GDAL_netCDF",
    8566             :                  "As %d variables were ignored, creating subdataset list "
    8567             :                  "for reference. Variable #%d [%s] is the main variable",
    8568             :                  nIgnoredVars, nVarID, osSubdatasetName.c_str());
    8569          24 :         poDS->CreateSubDatasetList(cdfid);
    8570             :     }
    8571             : 
    8572             :     // Open the NETCDF subdataset NETCDF:"filename":subdataset.
    8573         352 :     int var = -1;
    8574         352 :     NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
    8575             :     // Now we can forget the root cdfid and only use the selected group.
    8576         352 :     cdfid = nGroupID;
    8577         352 :     int nd = 0;
    8578         352 :     nc_inq_varndims(cdfid, var, &nd);
    8579             : 
    8580         352 :     poDS->m_anDimIds.resize(nd);
    8581             : 
    8582             :     // X, Y, Z position in array
    8583         704 :     std::vector<int> anBandDimPos(nd);
    8584             : 
    8585         352 :     nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
    8586             : 
    8587             :     // Check if somebody tried to pass a variable with less than 1D.
    8588         352 :     if (nd < 1)
    8589             :     {
    8590           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8591             :                  "Variable has %d dimension(s) - not supported.", nd);
    8592           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8593             :             // with GDALDataset own mutex.
    8594           0 :         delete poDS;
    8595           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8596           0 :         return nullptr;
    8597             :     }
    8598             : 
    8599             :     // CF-1 Convention
    8600             :     //
    8601             :     // Dimensions to appear in the relative order T, then Z, then Y,
    8602             :     // then X  to the file. All other dimensions should, whenever
    8603             :     // possible, be placed to the left of the spatiotemporal
    8604             :     // dimensions.
    8605             : 
    8606             :     // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
    8607             :     // Ideally we should detect for other ordering and act accordingly
    8608             :     // Only done if file has Conventions=CF-* and only prints warning
    8609             :     // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
    8610             :     // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
    8611             :     const bool bCheckDims =
    8612         704 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
    8613         352 :         STARTS_WITH_CI(szConventions, "CF");
    8614             : 
    8615         352 :     if (nd >= 2 && bCheckDims)
    8616             :     {
    8617         267 :         char szDimName1[NC_MAX_NAME + 1] = {};
    8618         267 :         char szDimName2[NC_MAX_NAME + 1] = {};
    8619         267 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
    8620         267 :         NCDF_ERR(status);
    8621         267 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
    8622         267 :         NCDF_ERR(status);
    8623         425 :         if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
    8624         158 :             NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
    8625             :         {
    8626           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8627             :                      "dimension #%d (%s) is not a Longitude/X dimension.",
    8628             :                      nd - 1, szDimName1);
    8629             :         }
    8630         425 :         if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
    8631         158 :             NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
    8632             :         {
    8633           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8634             :                      "dimension #%d (%s) is not a Latitude/Y dimension.",
    8635             :                      nd - 2, szDimName2);
    8636             :         }
    8637         267 :         if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
    8638         269 :              NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
    8639           2 :             (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
    8640           0 :              NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
    8641             :         {
    8642           2 :             poDS->bSwitchedXY = true;
    8643             :         }
    8644         267 :         if (nd >= 3)
    8645             :         {
    8646          52 :             char szDimName3[NC_MAX_NAME + 1] = {};
    8647             :             status =
    8648          52 :                 nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
    8649          52 :             NCDF_ERR(status);
    8650          52 :             if (nd >= 4)
    8651             :             {
    8652          13 :                 char szDimName4[NC_MAX_NAME + 1] = {};
    8653             :                 status =
    8654          13 :                     nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
    8655          13 :                 NCDF_ERR(status);
    8656          13 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
    8657             :                 {
    8658           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8659             :                              "dimension #%d (%s) is not a Vertical dimension.",
    8660             :                              nd - 3, szDimName3);
    8661             :                 }
    8662          13 :                 if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
    8663             :                 {
    8664           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8665             :                              "dimension #%d (%s) is not a Time dimension.",
    8666             :                              nd - 4, szDimName4);
    8667             :                 }
    8668             :             }
    8669             :             else
    8670             :             {
    8671          75 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
    8672          36 :                     NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
    8673             :                 {
    8674           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8675             :                              "dimension #%d (%s) is not a "
    8676             :                              "Time or Vertical dimension.",
    8677             :                              nd - 3, szDimName3);
    8678             :                 }
    8679             :             }
    8680             :         }
    8681             :     }
    8682             : 
    8683             :     // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
    8684             :     // dimension order is downtrack, crosstrack, bands
    8685         352 :     bool bYXBandOrder = false;
    8686         352 :     if (nd == 3)
    8687             :     {
    8688          44 :         char szDimName[NC_MAX_NAME + 1] = {};
    8689          44 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
    8690          44 :         NCDF_ERR(status);
    8691          44 :         bYXBandOrder =
    8692          44 :             strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
    8693             :     }
    8694             : 
    8695             :     // Get X dimensions information.
    8696             :     size_t xdim;
    8697         352 :     poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
    8698         352 :     nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
    8699             : 
    8700             :     // Get Y dimension information.
    8701             :     size_t ydim;
    8702         352 :     if (nd >= 2)
    8703             :     {
    8704         348 :         poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
    8705         348 :         nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
    8706             :     }
    8707             :     else
    8708             :     {
    8709           4 :         poDS->nYDimID = -1;
    8710           4 :         ydim = 1;
    8711             :     }
    8712             : 
    8713         352 :     if (xdim > INT_MAX || ydim > INT_MAX)
    8714             :     {
    8715           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8716             :                  "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
    8717             :                  static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
    8718           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8719             :             // with GDALDataset own mutex.
    8720           0 :         delete poDS;
    8721           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8722           0 :         return nullptr;
    8723             :     }
    8724             : 
    8725         352 :     poDS->nRasterXSize = static_cast<int>(xdim);
    8726         352 :     poDS->nRasterYSize = static_cast<int>(ydim);
    8727             : 
    8728         352 :     unsigned int k = 0;
    8729        1131 :     for (int j = 0; j < nd; j++)
    8730             :     {
    8731         779 :         if (poDS->m_anDimIds[j] == poDS->nXDimID)
    8732             :         {
    8733         352 :             anBandDimPos[0] = j;  // Save Position of XDim
    8734         352 :             k++;
    8735             :         }
    8736         779 :         if (poDS->m_anDimIds[j] == poDS->nYDimID)
    8737             :         {
    8738         348 :             anBandDimPos[1] = j;  // Save Position of YDim
    8739         348 :             k++;
    8740             :         }
    8741             :     }
    8742             :     // X and Y Dimension Ids were not found!
    8743         352 :     if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
    8744             :     {
    8745           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8746             :             // with GDALDataset own mutex.
    8747           0 :         delete poDS;
    8748           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8749           0 :         return nullptr;
    8750             :     }
    8751             : 
    8752             :     // Read Metadata for this variable.
    8753             : 
    8754             :     // Should disable as is also done at band level, except driver needs the
    8755             :     // variables as metadata (e.g. projection).
    8756         352 :     poDS->ReadAttributes(cdfid, var);
    8757             : 
    8758             :     // Read Metadata for each dimension.
    8759         352 :     int *panDimIds = nullptr;
    8760         352 :     NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
    8761             :     // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
    8762             :     // in NetCDF-3 because we see only the dimensions of the selected group
    8763             :     // and its parents.
    8764             :     // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
    8765             :     // [0..max(panDimIds)], but they are not all useful so we fill names
    8766             :     // of useless dims with empty string.
    8767         352 :     if (panDimIds)
    8768             :     {
    8769         352 :         const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
    8770         352 :         std::set<int> oSetExistingDimIds;
    8771        1171 :         for (int i = 0; i < ndims; i++)
    8772             :         {
    8773         819 :             oSetExistingDimIds.insert(panDimIds[i]);
    8774             :         }
    8775         352 :         std::set<int> oSetDimIdsUsedByVar;
    8776        1131 :         for (int i = 0; i < nd; i++)
    8777             :         {
    8778         779 :             oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
    8779             :         }
    8780        1173 :         for (int j = 0; j <= nMaxDimId; j++)
    8781             :         {
    8782             :             // Is j dim used?
    8783         821 :             if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
    8784             :             {
    8785             :                 // Useful dim.
    8786         819 :                 char szTemp[NC_MAX_NAME + 1] = {};
    8787         819 :                 status = nc_inq_dimname(cdfid, j, szTemp);
    8788         819 :                 if (status != NC_NOERR)
    8789             :                 {
    8790           0 :                     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8791             :                         // deadlock with GDALDataset own
    8792             :                         // mutex.
    8793           0 :                     delete poDS;
    8794           0 :                     CPLAcquireMutex(hNCMutex, 1000.0);
    8795           0 :                     return nullptr;
    8796             :                 }
    8797         819 :                 poDS->papszDimName.AddString(szTemp);
    8798             : 
    8799         819 :                 if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
    8800             :                 {
    8801         779 :                     int nDimGroupId = -1;
    8802         779 :                     int nDimVarId = -1;
    8803         779 :                     if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
    8804         779 :                                        &nDimGroupId, &nDimVarId) == CE_None)
    8805             :                     {
    8806         579 :                         poDS->ReadAttributes(nDimGroupId, nDimVarId);
    8807             :                     }
    8808             :                 }
    8809             :             }
    8810             :             else
    8811             :             {
    8812             :                 // Useless dim.
    8813           2 :                 poDS->papszDimName.AddString("");
    8814             :             }
    8815             :         }
    8816         352 :         CPLFree(panDimIds);
    8817             :     }
    8818             : 
    8819             :     // Set projection info.
    8820         704 :     std::vector<std::string> aosRemovedMDItems;
    8821         352 :     if (nd > 1)
    8822             :     {
    8823         348 :         poDS->SetProjectionFromVar(cdfid, var,
    8824             :                                    /*bReadSRSOnly=*/false,
    8825             :                                    /* pszGivenGM = */ nullptr,
    8826             :                                    /* returnProjStr = */ nullptr,
    8827             :                                    /* sg = */ nullptr, &aosRemovedMDItems);
    8828             :     }
    8829             : 
    8830             :     // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
    8831         352 :     const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
    8832         352 :     if (pszValue)
    8833             :     {
    8834          24 :         poDS->bBottomUp = CPLTestBool(pszValue);
    8835          24 :         CPLDebug("GDAL_netCDF",
    8836             :                  "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
    8837          24 :                  static_cast<int>(poDS->bBottomUp), pszValue);
    8838             :     }
    8839             : 
    8840             :     // Save non-spatial dimension info.
    8841             : 
    8842         352 :     int *panBandZLev = nullptr;
    8843         352 :     int nDim = (nd >= 2) ? 2 : 1;
    8844             :     size_t lev_count;
    8845         352 :     size_t nTotLevCount = 1;
    8846         352 :     nc_type nType = NC_NAT;
    8847             : 
    8848         704 :     CPLString osExtraDimNames;
    8849             : 
    8850         352 :     if (nd > 2)
    8851             :     {
    8852          60 :         nDim = 2;
    8853          60 :         panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
    8854             : 
    8855          60 :         osExtraDimNames = "{";
    8856             : 
    8857          60 :         char szDimName[NC_MAX_NAME + 1] = {};
    8858             : 
    8859          60 :         bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
    8860         259 :         for (int j = 0; j < nd; j++)
    8861             :         {
    8862         338 :             if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
    8863         139 :                 (poDS->m_anDimIds[j] != poDS->nYDimID))
    8864             :             {
    8865          79 :                 nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
    8866          79 :                 nTotLevCount *= lev_count;
    8867          79 :                 panBandZLev[nDim - 2] = static_cast<int>(lev_count);
    8868          79 :                 anBandDimPos[nDim] = j;  // Save Position of ZDim
    8869             :                 // Save non-spatial dimension names.
    8870          79 :                 if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
    8871             :                     NC_NOERR)
    8872             :                 {
    8873          79 :                     osExtraDimNames += szDimName;
    8874          79 :                     if (j < nd - 3)
    8875             :                     {
    8876          19 :                         osExtraDimNames += ",";
    8877             :                     }
    8878             : 
    8879          79 :                     int nIdxGroupID = -1;
    8880          79 :                     int nIdxVarID = Get1DVariableIndexedByDimension(
    8881          79 :                         cdfid, poDS->m_anDimIds[j], szDimName, true,
    8882          79 :                         &nIdxGroupID);
    8883          79 :                     poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
    8884          79 :                     poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
    8885             : 
    8886          79 :                     if (nIdxVarID >= 0)
    8887             :                     {
    8888          70 :                         nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
    8889             :                         char szExtraDimDef[NC_MAX_NAME + 1];
    8890          70 :                         snprintf(szExtraDimDef, sizeof(szExtraDimDef),
    8891             :                                  "{%ld,%d}", (long)lev_count, nType);
    8892             :                         char szTemp[NC_MAX_NAME + 32 + 1];
    8893          70 :                         snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    8894             :                                  szDimName);
    8895          70 :                         poDS->papszMetadata = CSLSetNameValue(
    8896             :                             poDS->papszMetadata, szTemp, szExtraDimDef);
    8897             : 
    8898             :                         // Retrieving data for unlimited dimensions might be
    8899             :                         // costly on network storage, so don't do it.
    8900             :                         // Each band will capture the value along the extra
    8901             :                         // dimension in its NETCDF_DIM_xxxx band metadata item
    8902             :                         // Addresses use case of
    8903             :                         // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
    8904             :                         const bool bIsLocal =
    8905          70 :                             VSIIsLocal(osFilenameForNCOpen.c_str());
    8906             :                         bool bListDimValues =
    8907          71 :                             bIsLocal || lev_count == 1 ||
    8908           1 :                             !NCDFIsUnlimitedDim(poDS->eFormat ==
    8909             :                                                     NCDF_FORMAT_NC4,
    8910           1 :                                                 cdfid, poDS->m_anDimIds[j]);
    8911             :                         const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
    8912          70 :                             CPLGetConfigOption(
    8913             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
    8914          70 :                         if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
    8915             :                         {
    8916           2 :                             bListDimValues = CPLTestBool(
    8917             :                                 pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
    8918             :                         }
    8919          68 :                         else if (!bListDimValues && !bIsLocal &&
    8920           1 :                                  !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
    8921             :                         {
    8922           1 :                             bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
    8923           1 :                             CPLDebug(
    8924             :                                 "GDAL_netCDF",
    8925             :                                 "Listing extra dimension values is skipped "
    8926             :                                 "because this dataset is hosted on a network "
    8927             :                                 "file system, and such an operation could be "
    8928             :                                 "slow. If you still want to proceed, set the "
    8929             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
    8930             :                                 "configuration option to YES");
    8931             :                         }
    8932          70 :                         if (bListDimValues)
    8933             :                         {
    8934          68 :                             char *pszTemp = nullptr;
    8935          68 :                             if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
    8936          68 :                                              &pszTemp) == CE_None)
    8937             :                             {
    8938          68 :                                 snprintf(szTemp, sizeof(szTemp),
    8939             :                                          "NETCDF_DIM_%s_VALUES", szDimName);
    8940          68 :                                 poDS->papszMetadata = CSLSetNameValue(
    8941             :                                     poDS->papszMetadata, szTemp, pszTemp);
    8942          68 :                                 CPLFree(pszTemp);
    8943             :                             }
    8944             :                         }
    8945             :                     }
    8946             :                 }
    8947             :                 else
    8948             :                 {
    8949           0 :                     poDS->m_anExtraDimGroupIds.push_back(-1);
    8950           0 :                     poDS->m_anExtraDimVarIds.push_back(-1);
    8951             :                 }
    8952             : 
    8953          79 :                 nDim++;
    8954             :             }
    8955             :         }
    8956          60 :         osExtraDimNames += "}";
    8957          60 :         poDS->papszMetadata = CSLSetNameValue(
    8958             :             poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
    8959             :     }
    8960             : 
    8961             :     // Store Metadata.
    8962         362 :     for (const auto &osStr : aosRemovedMDItems)
    8963          10 :         poDS->papszMetadata =
    8964          10 :             CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
    8965             : 
    8966         352 :     poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8967             : 
    8968             :     // Create bands.
    8969             : 
    8970             :     // Arbitrary threshold.
    8971             :     int nMaxBandCount =
    8972         352 :         atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
    8973         352 :     if (nMaxBandCount <= 0)
    8974           0 :         nMaxBandCount = 32768;
    8975         352 :     if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
    8976             :     {
    8977           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8978             :                  "Limiting number of bands to %d instead of %u", nMaxBandCount,
    8979             :                  static_cast<unsigned int>(nTotLevCount));
    8980           0 :         nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
    8981             :     }
    8982         352 :     if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
    8983             :     {
    8984           0 :         poDS->nRasterXSize = 0;
    8985           0 :         poDS->nRasterYSize = 0;
    8986           0 :         nTotLevCount = 0;
    8987           0 :         if (poDS->GetLayerCount() == 0)
    8988             :         {
    8989           0 :             CPLFree(panBandZLev);
    8990           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8991             :                 // deadlock with GDALDataset own mutex.
    8992           0 :             delete poDS;
    8993           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8994           0 :             return nullptr;
    8995             :         }
    8996             :     }
    8997         352 :     if (bSeveralVariablesAsBands)
    8998             :     {
    8999           6 :         const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
    9000          24 :         for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
    9001             :              ++iBand)
    9002             :         {
    9003          18 :             int bandVarGroupId = listVariables[iBand].first;
    9004          18 :             int bandVarId = listVariables[iBand].second;
    9005             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    9006           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
    9007          18 :                 bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
    9008          18 :             poDS->SetBand(iBand + 1, poBand);
    9009             :         }
    9010             :     }
    9011             :     else
    9012             :     {
    9013         800 :         for (unsigned int lev = 0; lev < nTotLevCount; lev++)
    9014             :         {
    9015             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    9016           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
    9017         454 :                 lev, panBandZLev, anBandDimPos.data(), lev + 1);
    9018         454 :             poDS->SetBand(lev + 1, poBand);
    9019             :         }
    9020             :     }
    9021             : 
    9022         352 :     if (panBandZLev)
    9023          60 :         CPLFree(panBandZLev);
    9024             :     // Handle angular geographic coordinates here
    9025             : 
    9026             :     // Initialize any PAM information.
    9027         352 :     if (bTreatAsSubdataset)
    9028             :     {
    9029          60 :         poDS->SetPhysicalFilename(poDS->osFilename);
    9030          60 :         poDS->SetSubdatasetName(osSubdatasetName);
    9031             :     }
    9032             : 
    9033         352 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9034             :         // GDALDataset own mutex.
    9035         352 :     poDS->TryLoadXML();
    9036             : 
    9037         352 :     if (bTreatAsSubdataset)
    9038          60 :         poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
    9039             :     else
    9040         292 :         poDS->oOvManager.Initialize(poDS, poDS->osFilename);
    9041             : 
    9042         352 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9043             : 
    9044         352 :     return poDS;
    9045             : }
    9046             : 
    9047             : /************************************************************************/
    9048             : /*                            CopyMetadata()                            */
    9049             : /*                                                                      */
    9050             : /*      Create a copy of metadata for NC_GLOBAL or a variable           */
    9051             : /************************************************************************/
    9052             : 
    9053         157 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
    9054             :                          GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
    9055             :                          const char *pszPrefix)
    9056             : {
    9057             :     // Remove the following band meta but set them later from band data.
    9058         157 :     const char *const papszIgnoreBand[] = {
    9059             :         CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    9060             :         NCDF_FillValue, "coordinates",   nullptr};
    9061         157 :     const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
    9062             : 
    9063         157 :     CSLConstList papszMetadata = nullptr;
    9064         157 :     if (poSrcDS)
    9065             :     {
    9066          66 :         papszMetadata = poSrcDS->GetMetadata();
    9067             :     }
    9068          91 :     else if (poSrcBand)
    9069             :     {
    9070          91 :         papszMetadata = poSrcBand->GetMetadata();
    9071             :     }
    9072             : 
    9073         637 :     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
    9074             :     {
    9075             : #ifdef NCDF_DEBUG
    9076             :         CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
    9077             : #endif
    9078             : 
    9079         480 :         CPLString osMetaName(pszKey);
    9080             : 
    9081             :         // Check for items that match pszPrefix if applicable.
    9082         480 :         if (pszPrefix && !EQUAL(pszPrefix, ""))
    9083             :         {
    9084             :             // Remove prefix.
    9085         115 :             if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
    9086             :             {
    9087          17 :                 osMetaName = osMetaName.substr(strlen(pszPrefix));
    9088             :             }
    9089             :             // Only copy items that match prefix.
    9090             :             else
    9091             :             {
    9092          98 :                 continue;
    9093             :             }
    9094             :         }
    9095             : 
    9096             :         // Fix various issues with metadata translation.
    9097         382 :         if (CDFVarID == NC_GLOBAL)
    9098             :         {
    9099             :             // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
    9100         481 :             if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
    9101         238 :                 (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
    9102          21 :                 continue;
    9103             :             // Remove NC_GLOBAL prefix for netcdf global Metadata.
    9104         222 :             else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
    9105             :             {
    9106          33 :                 osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
    9107             :             }
    9108             :             // GDAL Metadata renamed as GDAL-[meta].
    9109         189 :             else if (strstr(osMetaName, "#") == nullptr)
    9110             :             {
    9111          16 :                 osMetaName = "GDAL_" + osMetaName;
    9112             :             }
    9113             :             // Keep time, lev and depth information for safe-keeping.
    9114             :             // Time and vertical coordinate handling need improvements.
    9115             :             /*
    9116             :             else if( STARTS_WITH(szMetaName, "time#") )
    9117             :             {
    9118             :                 szMetaName[4] = '-';
    9119             :             }
    9120             :             else if( STARTS_WITH(szMetaName, "lev#") )
    9121             :             {
    9122             :                 szMetaName[3] = '-';
    9123             :             }
    9124             :             else if( STARTS_WITH(szMetaName, "depth#") )
    9125             :             {
    9126             :                 szMetaName[5] = '-';
    9127             :             }
    9128             :             */
    9129             :             // Only copy data without # (previously all data was copied).
    9130         222 :             if (strstr(osMetaName, "#") != nullptr)
    9131         173 :                 continue;
    9132             :             // netCDF attributes do not like the '#' character.
    9133             :             // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
    9134             :             //     if( szMetaName[h] == '#') szMetaName[h] = '-';
    9135             :             // }
    9136             :         }
    9137             :         else
    9138             :         {
    9139             :             // Do not copy varname, stats, NETCDF_DIM_*, nodata
    9140             :             // and items in papszIgnoreBand.
    9141         139 :             if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
    9142         107 :                 STARTS_WITH(osMetaName, "STATISTICS_") ||
    9143         107 :                 STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
    9144          74 :                 STARTS_WITH(osMetaName, "missing_value") ||
    9145         293 :                 STARTS_WITH(osMetaName, "_FillValue") ||
    9146          47 :                 CSLFindString(papszIgnoreBand, osMetaName) != -1)
    9147          97 :                 continue;
    9148             :         }
    9149             : 
    9150             : #ifdef NCDF_DEBUG
    9151             :         CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
    9152             :                  pszValue);
    9153             : #endif
    9154          91 :         if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
    9155             :         {
    9156           0 :             CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
    9157             :                      nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
    9158             :         }
    9159             :     }
    9160             : 
    9161             :     // Set add_offset and scale_factor here if present.
    9162         157 :     if (poSrcBand && poDstBand)
    9163             :     {
    9164             : 
    9165          91 :         int bGotAddOffset = FALSE;
    9166          91 :         const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
    9167          91 :         int bGotScale = FALSE;
    9168          91 :         const double dfScale = poSrcBand->GetScale(&bGotScale);
    9169             : 
    9170          91 :         if (bGotAddOffset && dfAddOffset != 0.0)
    9171           1 :             poDstBand->SetOffset(dfAddOffset);
    9172          91 :         if (bGotScale && dfScale != 1.0)
    9173           1 :             poDstBand->SetScale(dfScale);
    9174             :     }
    9175         157 : }
    9176             : 
    9177             : /************************************************************************/
    9178             : /*                            CreateLL()                                */
    9179             : /*                                                                      */
    9180             : /*      Shared functionality between netCDFDataset::Create() and        */
    9181             : /*      netCDF::CreateCopy() for creating netcdf file based on a set of */
    9182             : /*      options and a configuration.                                    */
    9183             : /************************************************************************/
    9184             : 
    9185         198 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
    9186             :                                        int nYSize, int nBandsIn,
    9187             :                                        char **papszOptions)
    9188             : {
    9189         198 :     if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
    9190         126 :           (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
    9191             :     {
    9192           1 :         return nullptr;
    9193             :     }
    9194             : 
    9195         197 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9196             :         // GDALDataset own mutex.
    9197         197 :     netCDFDataset *poDS = new netCDFDataset();
    9198         197 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9199             : 
    9200         197 :     poDS->nRasterXSize = nXSize;
    9201         197 :     poDS->nRasterYSize = nYSize;
    9202         197 :     poDS->eAccess = GA_Update;
    9203         197 :     poDS->osFilename = pszFilename;
    9204             : 
    9205             :     // From gtiff driver, is this ok?
    9206             :     /*
    9207             :     poDS->nBlockXSize = nXSize;
    9208             :     poDS->nBlockYSize = 1;
    9209             :     poDS->nBlocksPerBand =
    9210             :         DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
    9211             :         * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
    9212             :         */
    9213             : 
    9214             :     // process options.
    9215         197 :     poDS->papszCreationOptions = CSLDuplicate(papszOptions);
    9216         197 :     poDS->ProcessCreationOptions();
    9217             : 
    9218         197 :     if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
    9219             :     {
    9220             :         VSIStatBuf sStat;
    9221           2 :         if (VSIStat(pszFilename, &sStat) == 0)
    9222             :         {
    9223           0 :             if (!VSI_ISDIR(sStat.st_mode))
    9224             :             {
    9225           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    9226             :                          "%s is an existing file, but not a directory",
    9227             :                          pszFilename);
    9228           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9229             :                     // deadlock with GDALDataset own
    9230             :                     // mutex.
    9231           0 :                 delete poDS;
    9232           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    9233           0 :                 return nullptr;
    9234             :             }
    9235             :         }
    9236           2 :         else if (VSIMkdir(pszFilename, 0755) != 0)
    9237             :         {
    9238           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
    9239             :                      pszFilename);
    9240           1 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9241             :                 // deadlock with GDALDataset own mutex.
    9242           1 :             delete poDS;
    9243           1 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9244           1 :             return nullptr;
    9245             :         }
    9246             : 
    9247           1 :         return poDS;
    9248             :     }
    9249             :     // Create the dataset.
    9250         390 :     CPLString osFilenameForNCCreate(pszFilename);
    9251             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    9252             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    9253             :     {
    9254             :         char *pszTemp =
    9255             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    9256             :         osFilenameForNCCreate = pszTemp;
    9257             :         CPLFree(pszTemp);
    9258             :     }
    9259             : #endif
    9260             : 
    9261             : #if defined(_WIN32)
    9262             :     {
    9263             :         // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
    9264             :         // crashes
    9265             :         VSIStatBuf sStat;
    9266             :         const std::string osDirname =
    9267             :             CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
    9268             :         if (VSIStat(osDirname.c_str(), &sStat) != 0)
    9269             :         {
    9270             :             CPLError(CE_Failure, CPLE_OpenFailed,
    9271             :                      "Unable to create netCDF file %s: non existing output "
    9272             :                      "directory",
    9273             :                      pszFilename);
    9274             :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9275             :                 // deadlock with GDALDataset own mutex.
    9276             :             delete poDS;
    9277             :             CPLAcquireMutex(hNCMutex, 1000.0);
    9278             :             return nullptr;
    9279             :         }
    9280             :     }
    9281             : #endif
    9282             : 
    9283             :     int status =
    9284         195 :         nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
    9285             : 
    9286             :     // Put into define mode.
    9287         195 :     poDS->SetDefineMode(true);
    9288             : 
    9289         195 :     if (status != NC_NOERR)
    9290             :     {
    9291          30 :         CPLError(CE_Failure, CPLE_OpenFailed,
    9292             :                  "Unable to create netCDF file %s (Error code %d): %s .",
    9293             :                  pszFilename, status, nc_strerror(status));
    9294          30 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    9295             :             // with GDALDataset own mutex.
    9296          30 :         delete poDS;
    9297          30 :         CPLAcquireMutex(hNCMutex, 1000.0);
    9298          30 :         return nullptr;
    9299             :     }
    9300             : 
    9301             :     // Define dimensions.
    9302         165 :     if (nXSize > 0 && nYSize > 0)
    9303             :     {
    9304         112 :         poDS->papszDimName.AddString(NCDF_DIMNAME_X);
    9305             :         status =
    9306         112 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
    9307         112 :         NCDF_ERR(status);
    9308         112 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9309             :                  poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
    9310             : 
    9311         112 :         poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
    9312             :         status =
    9313         112 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
    9314         112 :         NCDF_ERR(status);
    9315         112 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9316             :                  poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
    9317             :     }
    9318             : 
    9319         165 :     return poDS;
    9320             : }
    9321             : 
    9322             : /************************************************************************/
    9323             : /*                            Create()                                  */
    9324             : /************************************************************************/
    9325             : 
    9326         126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
    9327             :                                    int nYSize, int nBandsIn, GDALDataType eType,
    9328             :                                    char **papszOptions)
    9329             : {
    9330         126 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
    9331             :              pszFilename);
    9332             : 
    9333             :     const char *legacyCreationOp =
    9334         126 :         CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9335         252 :     std::string legacyCreationOp_s = std::string(legacyCreationOp);
    9336             : 
    9337             :     // Check legacy creation op FIRST
    9338             : 
    9339         126 :     bool legacyCreateMode = false;
    9340             : 
    9341         126 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
    9342             :     {
    9343          56 :         legacyCreateMode = true;
    9344             :     }
    9345          70 :     else if (legacyCreationOp_s == "CF_1.8")
    9346             :     {
    9347          54 :         legacyCreateMode = false;
    9348             :     }
    9349             : 
    9350          16 :     else if (legacyCreationOp_s == "WKT")
    9351             :     {
    9352          16 :         legacyCreateMode = true;
    9353             :     }
    9354             : 
    9355             :     else
    9356             :     {
    9357           0 :         CPLError(
    9358             :             CE_Failure, CPLE_NotSupported,
    9359             :             "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
    9360             :             legacyCreationOp_s.c_str());
    9361           0 :         return nullptr;
    9362             :     }
    9363             : 
    9364         252 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9365         238 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9366         112 :         (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
    9367             :          eType == GDT_Int64))
    9368             :     {
    9369          10 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9370          10 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9371             :     }
    9372             : 
    9373         252 :     CPLStringList aosBandNames;
    9374         126 :     if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
    9375             :     {
    9376             :         aosBandNames =
    9377           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9378             : 
    9379           2 :         if (aosBandNames.Count() != nBandsIn)
    9380             :         {
    9381           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9382             :                      "Attempted to create netCDF with %d bands but %d names "
    9383             :                      "provided in BAND_NAMES.",
    9384             :                      nBandsIn, aosBandNames.Count());
    9385             : 
    9386           1 :             return nullptr;
    9387             :         }
    9388             :     }
    9389             : 
    9390         250 :     CPLMutexHolderD(&hNCMutex);
    9391             : 
    9392         125 :     auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
    9393             :                                         aosOptions.List());
    9394             : 
    9395         125 :     if (!poDS)
    9396          19 :         return nullptr;
    9397             : 
    9398         106 :     if (!legacyCreateMode)
    9399             :     {
    9400          37 :         poDS->bSGSupport = true;
    9401          37 :         poDS->vcdf.enableFullVirtualMode();
    9402             :     }
    9403             : 
    9404             :     else
    9405             :     {
    9406          69 :         poDS->bSGSupport = false;
    9407             :     }
    9408             : 
    9409             :     // Should we write signed or unsigned byte?
    9410             :     // TODO should this only be done in Create()
    9411         106 :     poDS->bSignedData = true;
    9412         106 :     const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
    9413         106 :     if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
    9414          15 :         poDS->bSignedData = false;
    9415             : 
    9416             :     // Add Conventions, GDAL info and history.
    9417         106 :     if (poDS->cdfid >= 0)
    9418             :     {
    9419             :         const char *CF_Vector_Conv =
    9420         173 :             poDS->bSGSupport ||
    9421             :                     // Use of variable length strings require CF-1.8
    9422          68 :                     EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
    9423             :                 ? NCDF_CONVENTIONS_CF_V1_8
    9424         173 :                 : NCDF_CONVENTIONS_CF_V1_6;
    9425         105 :         poDS->bWriteGDALVersion = CPLTestBool(
    9426             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9427         105 :         poDS->bWriteGDALHistory = CPLTestBool(
    9428             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9429         105 :         NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
    9430         105 :                            poDS->bWriteGDALHistory, "", "Create",
    9431             :                            (nBandsIn == 0) ? CF_Vector_Conv
    9432             :                                            : GDAL_DEFAULT_NCDF_CONVENTIONS);
    9433             :     }
    9434             : 
    9435             :     // Define bands.
    9436         197 :     for (int iBand = 1; iBand <= nBandsIn; iBand++)
    9437             :     {
    9438             :         const char *pszBandName =
    9439          91 :             aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
    9440             : 
    9441          91 :         poDS->SetBand(iBand, new netCDFRasterBand(
    9442          91 :                                  netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
    9443          91 :                                  eType, iBand, poDS->bSignedData, pszBandName));
    9444             :     }
    9445             : 
    9446         106 :     CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
    9447             :     // Return same dataset.
    9448         106 :     return poDS;
    9449             : }
    9450             : 
    9451             : template <class T>
    9452          91 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
    9453             :                            int nXSize, int nYSize, GDALProgressFunc pfnProgress,
    9454             :                            void *pProgressData)
    9455             : {
    9456          91 :     GDALDataType eDT = poSrcBand->GetRasterDataType();
    9457          91 :     CPLErr eErr = CE_None;
    9458          91 :     T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
    9459             : 
    9460        6308 :     for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
    9461             :     {
    9462        6217 :         eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
    9463             :                                    nXSize, 1, eDT, 0, 0, nullptr);
    9464        6217 :         if (eErr != CE_None)
    9465             :         {
    9466           0 :             CPLDebug(
    9467             :                 "GDAL_netCDF",
    9468             :                 "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
    9469             :                 eErr);
    9470             :         }
    9471             :         else
    9472             :         {
    9473        6217 :             eErr =
    9474             :                 poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
    9475             :                                     nXSize, 1, eDT, 0, 0, nullptr);
    9476        6217 :             if (eErr != CE_None)
    9477           0 :                 CPLDebug("GDAL_netCDF",
    9478             :                          "NCDFCopyBand(), poDstBand->RasterIO() returned error "
    9479             :                          "code %d",
    9480             :                          eErr);
    9481             :         }
    9482             : 
    9483        6217 :         if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
    9484             :         {
    9485         277 :             if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
    9486             :             {
    9487           0 :                 eErr = CE_Failure;
    9488           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt,
    9489             :                          "User terminated CreateCopy()");
    9490             :             }
    9491             :         }
    9492             :     }
    9493             : 
    9494          91 :     CPLFree(patScanline);
    9495             : 
    9496          91 :     pfnProgress(1.0, nullptr, pProgressData);
    9497             : 
    9498          91 :     return eErr;
    9499             : }
    9500             : 
    9501             : /************************************************************************/
    9502             : /*                            CreateCopy()                              */
    9503             : /************************************************************************/
    9504             : 
    9505             : GDALDataset *
    9506          82 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
    9507             :                           CPL_UNUSED int bStrict, char **papszOptions,
    9508             :                           GDALProgressFunc pfnProgress, void *pProgressData)
    9509             : {
    9510         164 :     CPLMutexHolderD(&hNCMutex);
    9511             : 
    9512          82 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
    9513             :              pszFilename);
    9514             : 
    9515          82 :     if (poSrcDS->GetRootGroup())
    9516             :     {
    9517           5 :         auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
    9518           5 :         if (poDrv)
    9519             :         {
    9520           5 :             return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    9521             :                                             papszOptions, pfnProgress,
    9522           5 :                                             pProgressData);
    9523             :         }
    9524             :     }
    9525             : 
    9526          77 :     const int nBands = poSrcDS->GetRasterCount();
    9527          77 :     const int nXSize = poSrcDS->GetRasterXSize();
    9528          77 :     const int nYSize = poSrcDS->GetRasterYSize();
    9529          77 :     const char *pszWKT = poSrcDS->GetProjectionRef();
    9530             : 
    9531             :     // Check input bands for errors.
    9532          77 :     if (nBands == 0)
    9533             :     {
    9534           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    9535             :                  "NetCDF driver does not support "
    9536             :                  "source dataset with zero band.");
    9537           1 :         return nullptr;
    9538             :     }
    9539             : 
    9540          76 :     GDALDataType eDT = GDT_Unknown;
    9541          76 :     GDALRasterBand *poSrcBand = nullptr;
    9542         181 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9543             :     {
    9544         109 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9545         109 :         eDT = poSrcBand->GetRasterDataType();
    9546         109 :         if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
    9547             :         {
    9548           4 :             CPLError(CE_Failure, CPLE_NotSupported,
    9549             :                      "NetCDF driver does not support source dataset with band "
    9550             :                      "of complex type.");
    9551           4 :             return nullptr;
    9552             :         }
    9553             :     }
    9554             : 
    9555         144 :     CPLStringList aosBandNames;
    9556          72 :     if (const char *pszBandNames =
    9557          72 :             CSLFetchNameValue(papszOptions, "BAND_NAMES"))
    9558             :     {
    9559             :         aosBandNames =
    9560           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9561             : 
    9562           2 :         if (aosBandNames.Count() != nBands)
    9563             :         {
    9564           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9565             :                      "Attempted to create netCDF with %d bands but %d names "
    9566             :                      "provided in BAND_NAMES.",
    9567             :                      nBands, aosBandNames.Count());
    9568             : 
    9569           1 :             return nullptr;
    9570             :         }
    9571             :     }
    9572             : 
    9573          71 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    9574           0 :         return nullptr;
    9575             : 
    9576             :     // Same as in Create().
    9577         142 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9578         133 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9579          62 :         (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
    9580             :          eDT == GDT_Int64))
    9581             :     {
    9582           6 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9583           6 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9584             :     }
    9585          71 :     netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
    9586             :                                                   nBands, aosOptions.List());
    9587          71 :     if (!poDS)
    9588          13 :         return nullptr;
    9589             : 
    9590             :     // Copy global metadata.
    9591             :     // Add Conventions, GDAL info and history.
    9592          58 :     CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
    9593          58 :     const bool bWriteGDALVersion = CPLTestBool(
    9594             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9595          58 :     const bool bWriteGDALHistory = CPLTestBool(
    9596             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9597          58 :     NCDFAddGDALHistory(
    9598             :         poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
    9599          58 :         poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
    9600          58 :         poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
    9601             : 
    9602          58 :     pfnProgress(0.1, nullptr, pProgressData);
    9603             : 
    9604             :     // Check for extra dimensions.
    9605          58 :     int nDim = 2;
    9606             :     char **papszExtraDimNames =
    9607          58 :         NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9608          58 :     char **papszExtraDimValues = nullptr;
    9609             : 
    9610          58 :     if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
    9611             :     {
    9612           5 :         size_t nDimSizeTot = 1;
    9613             :         // first make sure dimensions lengths compatible with band count
    9614             :         // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
    9615          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9616             :         {
    9617             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9618           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9619           8 :                      papszExtraDimNames[i]);
    9620             :             papszExtraDimValues =
    9621           8 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9622           8 :             const size_t nDimSize = atol(papszExtraDimValues[0]);
    9623           8 :             CSLDestroy(papszExtraDimValues);
    9624           8 :             nDimSizeTot *= nDimSize;
    9625             :         }
    9626           5 :         if (nDimSizeTot == (size_t)nBands)
    9627             :         {
    9628           5 :             nDim = 2 + CSLCount(papszExtraDimNames);
    9629             :         }
    9630             :         else
    9631             :         {
    9632             :             // if nBands != #bands computed raise a warning
    9633             :             // just issue a debug message, because it was probably intentional
    9634           0 :             CPLDebug("GDAL_netCDF",
    9635             :                      "Warning: Number of bands (%d) is not compatible with "
    9636             :                      "dimensions "
    9637             :                      "(total=%ld names=%s)",
    9638             :                      nBands, (long)nDimSizeTot,
    9639           0 :                      poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9640           0 :             CSLDestroy(papszExtraDimNames);
    9641           0 :             papszExtraDimNames = nullptr;
    9642             :         }
    9643             :     }
    9644             : 
    9645          58 :     int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9646          58 :     int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9647             : 
    9648             :     nc_type nVarType;
    9649          58 :     int *panBandZLev = nullptr;
    9650          58 :     int *panDimVarIds = nullptr;
    9651             : 
    9652          58 :     if (nDim > 2)
    9653             :     {
    9654           5 :         panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9655           5 :         panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9656             : 
    9657             :         // Define all dims.
    9658          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9659             :         {
    9660           8 :             poDS->papszDimName.AddString(papszExtraDimNames[i]);
    9661             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9662           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9663           8 :                      papszExtraDimNames[i]);
    9664             :             papszExtraDimValues =
    9665           8 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9666           8 :             const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
    9667          16 :                                      ? atoi(papszExtraDimValues[0])
    9668             :                                      : 0;
    9669             :             // nc_type is an enum in netcdf-3, needs casting.
    9670           8 :             nVarType = static_cast<nc_type>(papszExtraDimValues &&
    9671           8 :                                                     papszExtraDimValues[0] &&
    9672           8 :                                                     papszExtraDimValues[1]
    9673           8 :                                                 ? atol(papszExtraDimValues[1])
    9674             :                                                 : 0);
    9675           8 :             CSLDestroy(papszExtraDimValues);
    9676           8 :             panBandZLev[i] = nDimSize;
    9677           8 :             panBandDimPos[i + 2] = i;  // Save Position of ZDim.
    9678             : 
    9679             :             // Define dim.
    9680          16 :             int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
    9681           8 :                                     nDimSize, &(panDimIds[i]));
    9682           8 :             NCDF_ERR(status);
    9683             : 
    9684             :             // Define dim var.
    9685           8 :             int anDim[1] = {panDimIds[i]};
    9686          16 :             status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
    9687           8 :                                 anDim, &(panDimVarIds[i]));
    9688           8 :             NCDF_ERR(status);
    9689             : 
    9690             :             // Add dim metadata, using global var# items.
    9691           8 :             snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
    9692           8 :             CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
    9693           8 :                          panDimVarIds[i], szTemp);
    9694             :         }
    9695             :     }
    9696             : 
    9697             :     // Copy GeoTransform and Projection.
    9698             : 
    9699             :     // Copy geolocation info.
    9700          58 :     char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
    9701          58 :     if (papszGeolocationInfo != nullptr)
    9702           5 :         poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
    9703             : 
    9704             :     // Copy geotransform.
    9705          58 :     bool bGotGeoTransform = false;
    9706             :     double adfGeoTransform[6];
    9707          58 :     CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
    9708          58 :     if (eErr == CE_None)
    9709             :     {
    9710          40 :         poDS->SetGeoTransform(adfGeoTransform);
    9711             :         // Disable AddProjectionVars() from being called.
    9712          40 :         bGotGeoTransform = true;
    9713          40 :         poDS->m_bHasGeoTransform = false;
    9714             :     }
    9715             : 
    9716             :     // Copy projection.
    9717          58 :     void *pScaledProgress = nullptr;
    9718          58 :     if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
    9719             :     {
    9720          41 :         poDS->SetProjection(pszWKT ? pszWKT : "");
    9721             : 
    9722             :         // Now we can call AddProjectionVars() directly.
    9723          41 :         poDS->m_bHasGeoTransform = bGotGeoTransform;
    9724          41 :         poDS->AddProjectionVars(true, nullptr, nullptr);
    9725             :         pScaledProgress =
    9726          41 :             GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
    9727          41 :         poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
    9728          41 :         GDALDestroyScaledProgress(pScaledProgress);
    9729             :     }
    9730             :     else
    9731             :     {
    9732          17 :         poDS->bBottomUp =
    9733          17 :             CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
    9734          17 :         if (papszGeolocationInfo)
    9735             :         {
    9736           4 :             poDS->AddProjectionVars(true, nullptr, nullptr);
    9737           4 :             poDS->AddProjectionVars(false, nullptr, nullptr);
    9738             :         }
    9739             :     }
    9740             : 
    9741             :     // Save X,Y dim positions.
    9742          58 :     panDimIds[nDim - 1] = poDS->nXDimID;
    9743          58 :     panBandDimPos[0] = nDim - 1;
    9744          58 :     panDimIds[nDim - 2] = poDS->nYDimID;
    9745          58 :     panBandDimPos[1] = nDim - 2;
    9746             : 
    9747             :     // Write extra dim values - after projection for optimization.
    9748          58 :     if (nDim > 2)
    9749             :     {
    9750             :         // Make sure we are in data mode.
    9751           5 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    9752          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9753             :         {
    9754             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9755           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
    9756           8 :                      papszExtraDimNames[i]);
    9757           8 :             if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
    9758             :             {
    9759           8 :                 NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
    9760           8 :                              poSrcDS->GetMetadataItem(szTemp));
    9761             :             }
    9762             :         }
    9763             :     }
    9764             : 
    9765          58 :     pfnProgress(0.25, nullptr, pProgressData);
    9766             : 
    9767             :     // Define Bands.
    9768          58 :     netCDFRasterBand *poBand = nullptr;
    9769          58 :     int nBandID = -1;
    9770             : 
    9771         149 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9772             :     {
    9773          91 :         CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
    9774             :                  nBands, nDim);
    9775             : 
    9776          91 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9777          91 :         eDT = poSrcBand->GetRasterDataType();
    9778             : 
    9779             :         // Get var name from NETCDF_VARNAME.
    9780             :         const char *pszNETCDF_VARNAME =
    9781          91 :             poSrcBand->GetMetadataItem("NETCDF_VARNAME");
    9782             :         char szBandName[NC_MAX_NAME + 1];
    9783          91 :         if (!aosBandNames.empty())
    9784             :         {
    9785           2 :             snprintf(szBandName, sizeof(szBandName), "%s",
    9786             :                      aosBandNames[iBand - 1]);
    9787             :         }
    9788          89 :         else if (pszNETCDF_VARNAME)
    9789             :         {
    9790          32 :             if (nBands > 1 && papszExtraDimNames == nullptr)
    9791           0 :                 snprintf(szBandName, sizeof(szBandName), "%s%d",
    9792             :                          pszNETCDF_VARNAME, iBand);
    9793             :             else
    9794          32 :                 snprintf(szBandName, sizeof(szBandName), "%s",
    9795             :                          pszNETCDF_VARNAME);
    9796             :         }
    9797             :         else
    9798             :         {
    9799          57 :             szBandName[0] = '\0';
    9800             :         }
    9801             : 
    9802             :         // Get long_name from <var>#long_name.
    9803          91 :         const char *pszLongName = "";
    9804          91 :         if (pszNETCDF_VARNAME)
    9805             :         {
    9806             :             pszLongName =
    9807          64 :                 poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
    9808          32 :                                              .append("#")
    9809          32 :                                              .append(CF_LNG_NAME)
    9810          32 :                                              .c_str());
    9811          32 :             if (!pszLongName)
    9812          25 :                 pszLongName = "";
    9813             :         }
    9814             : 
    9815          91 :         constexpr bool bSignedData = false;
    9816             : 
    9817          91 :         if (nDim > 2)
    9818          27 :             poBand = new netCDFRasterBand(
    9819          27 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9820             :                 bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
    9821          27 :                 panBandZLev, panBandDimPos, panDimIds);
    9822             :         else
    9823          64 :             poBand = new netCDFRasterBand(
    9824          64 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9825          64 :                 bSignedData, szBandName, pszLongName);
    9826             : 
    9827          91 :         poDS->SetBand(iBand, poBand);
    9828             : 
    9829             :         // Set nodata value, if any.
    9830          91 :         GDALCopyNoDataValue(poBand, poSrcBand);
    9831             : 
    9832             :         // Copy Metadata for band.
    9833          91 :         CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
    9834             :                      poDS->cdfid, poBand->nZId);
    9835             : 
    9836             :         // If more than 2D pass the first band's netcdf var ID to subsequent
    9837             :         // bands.
    9838          91 :         if (nDim > 2)
    9839          27 :             nBandID = poBand->nZId;
    9840             :     }
    9841             : 
    9842             :     // Write projection variable to band variable.
    9843          58 :     poDS->AddGridMappingRef();
    9844             : 
    9845          58 :     pfnProgress(0.5, nullptr, pProgressData);
    9846             : 
    9847             :     // Write bands.
    9848             : 
    9849             :     // Make sure we are in data mode.
    9850          58 :     poDS->SetDefineMode(false);
    9851             : 
    9852          58 :     double dfTemp = 0.5;
    9853             : 
    9854          58 :     eErr = CE_None;
    9855             : 
    9856         149 :     for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
    9857             :     {
    9858          91 :         const double dfTemp2 = dfTemp + 0.4 / nBands;
    9859          91 :         pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
    9860             :                                                    pProgressData);
    9861          91 :         dfTemp = dfTemp2;
    9862             : 
    9863          91 :         CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
    9864             : 
    9865          91 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9866          91 :         eDT = poSrcBand->GetRasterDataType();
    9867             : 
    9868          91 :         GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
    9869             : 
    9870             :         // Copy band data.
    9871          91 :         if (eDT == GDT_Byte)
    9872             :         {
    9873          51 :             CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
    9874          51 :             eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
    9875             :                                        GDALScaledProgress, pScaledProgress);
    9876             :         }
    9877          40 :         else if (eDT == GDT_Int8)
    9878             :         {
    9879           1 :             CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
    9880           1 :             eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
    9881             :                                        GDALScaledProgress, pScaledProgress);
    9882             :         }
    9883          39 :         else if (eDT == GDT_UInt16)
    9884             :         {
    9885           2 :             CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
    9886           2 :             eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9887             :                                         GDALScaledProgress, pScaledProgress);
    9888             :         }
    9889          37 :         else if (eDT == GDT_Int16)
    9890             :         {
    9891           5 :             CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
    9892           5 :             eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9893             :                                          GDALScaledProgress, pScaledProgress);
    9894             :         }
    9895          32 :         else if (eDT == GDT_UInt32)
    9896             :         {
    9897           2 :             CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
    9898           2 :             eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9899             :                                          GDALScaledProgress, pScaledProgress);
    9900             :         }
    9901          30 :         else if (eDT == GDT_Int32)
    9902             :         {
    9903          18 :             CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
    9904          18 :             eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9905             :                                         GDALScaledProgress, pScaledProgress);
    9906             :         }
    9907          12 :         else if (eDT == GDT_UInt64)
    9908             :         {
    9909           2 :             CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
    9910           2 :             eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
    9911             :                                                nYSize, GDALScaledProgress,
    9912             :                                                pScaledProgress);
    9913             :         }
    9914          10 :         else if (eDT == GDT_Int64)
    9915             :         {
    9916           2 :             CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
    9917             :             eErr =
    9918           2 :                 NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
    9919             :                                            GDALScaledProgress, pScaledProgress);
    9920             :         }
    9921           8 :         else if (eDT == GDT_Float32)
    9922             :         {
    9923           6 :             CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
    9924           6 :             eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
    9925             :                                        GDALScaledProgress, pScaledProgress);
    9926             :         }
    9927           2 :         else if (eDT == GDT_Float64)
    9928             :         {
    9929           2 :             CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
    9930           2 :             eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
    9931             :                                         GDALScaledProgress, pScaledProgress);
    9932             :         }
    9933             :         else
    9934             :         {
    9935           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9936             :                      "The NetCDF driver does not support GDAL data type %d",
    9937             :                      eDT);
    9938             :         }
    9939             : 
    9940          91 :         GDALDestroyScaledProgress(pScaledProgress);
    9941             :     }
    9942             : 
    9943          58 :     delete (poDS);
    9944             : 
    9945          58 :     CPLFree(panDimIds);
    9946          58 :     CPLFree(panBandDimPos);
    9947          58 :     CPLFree(panBandZLev);
    9948          58 :     CPLFree(panDimVarIds);
    9949          58 :     if (papszExtraDimNames)
    9950           5 :         CSLDestroy(papszExtraDimNames);
    9951             : 
    9952          58 :     if (eErr != CE_None)
    9953           0 :         return nullptr;
    9954             : 
    9955          58 :     pfnProgress(0.95, nullptr, pProgressData);
    9956             : 
    9957             :     // Re-open dataset so we can return it.
    9958         116 :     CPLStringList aosOpenOptions;
    9959          58 :     aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
    9960          58 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
    9961          58 :     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
    9962          58 :     oOpenInfo.papszOpenOptions = aosOpenOptions.List();
    9963          58 :     auto poRetDS = Open(&oOpenInfo);
    9964             : 
    9965             :     // PAM cloning is disabled. See bug #4244.
    9966             :     // if( poDS )
    9967             :     //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    9968             : 
    9969          58 :     pfnProgress(1.0, nullptr, pProgressData);
    9970             : 
    9971          58 :     return poRetDS;
    9972             : }
    9973             : 
    9974             : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
    9975             : // May not be known when Create() is called, see AddProjectionVars().
    9976         254 : void netCDFDataset::ProcessCreationOptions()
    9977             : {
    9978             :     const char *pszConfig =
    9979         254 :         CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
    9980         254 :     if (pszConfig != nullptr)
    9981             :     {
    9982           4 :         if (oWriterConfig.Parse(pszConfig))
    9983             :         {
    9984             :             // Override dataset creation options from the config file
    9985           2 :             std::map<CPLString, CPLString>::iterator oIter;
    9986           3 :             for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
    9987           3 :                  oIter != oWriterConfig.m_oDatasetCreationOptions.end();
    9988           1 :                  ++oIter)
    9989             :             {
    9990           2 :                 papszCreationOptions = CSLSetNameValue(
    9991           2 :                     papszCreationOptions, oIter->first, oIter->second);
    9992             :             }
    9993             :         }
    9994             :     }
    9995             : 
    9996             :     // File format.
    9997         254 :     eFormat = NCDF_FORMAT_NC;
    9998         254 :     const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
    9999         254 :     if (pszValue != nullptr)
   10000             :     {
   10001          93 :         if (EQUAL(pszValue, "NC"))
   10002             :         {
   10003           3 :             eFormat = NCDF_FORMAT_NC;
   10004             :         }
   10005             : #ifdef NETCDF_HAS_NC2
   10006          90 :         else if (EQUAL(pszValue, "NC2"))
   10007             :         {
   10008           1 :             eFormat = NCDF_FORMAT_NC2;
   10009             :         }
   10010             : #endif
   10011          89 :         else if (EQUAL(pszValue, "NC4"))
   10012             :         {
   10013          85 :             eFormat = NCDF_FORMAT_NC4;
   10014             :         }
   10015           4 :         else if (EQUAL(pszValue, "NC4C"))
   10016             :         {
   10017           4 :             eFormat = NCDF_FORMAT_NC4C;
   10018             :         }
   10019             :         else
   10020             :         {
   10021           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10022             :                      "FORMAT=%s in not supported, using the default NC format.",
   10023             :                      pszValue);
   10024             :         }
   10025             :     }
   10026             : 
   10027             :     // COMPRESS option.
   10028         254 :     pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
   10029         254 :     if (pszValue != nullptr)
   10030             :     {
   10031           3 :         if (EQUAL(pszValue, "NONE"))
   10032             :         {
   10033           1 :             eCompress = NCDF_COMPRESS_NONE;
   10034             :         }
   10035           2 :         else if (EQUAL(pszValue, "DEFLATE"))
   10036             :         {
   10037           2 :             eCompress = NCDF_COMPRESS_DEFLATE;
   10038           2 :             if (!((eFormat == NCDF_FORMAT_NC4) ||
   10039           2 :                   (eFormat == NCDF_FORMAT_NC4C)))
   10040             :             {
   10041           1 :                 CPLError(CE_Warning, CPLE_IllegalArg,
   10042             :                          "NOTICE: Format set to NC4C because compression is "
   10043             :                          "set to DEFLATE.");
   10044           1 :                 eFormat = NCDF_FORMAT_NC4C;
   10045             :             }
   10046             :         }
   10047             :         else
   10048             :         {
   10049           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10050             :                      "COMPRESS=%s is not supported.", pszValue);
   10051             :         }
   10052             :     }
   10053             : 
   10054             :     // ZLEVEL option.
   10055         254 :     pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
   10056         254 :     if (pszValue != nullptr)
   10057             :     {
   10058           1 :         nZLevel = atoi(pszValue);
   10059           1 :         if (!(nZLevel >= 1 && nZLevel <= 9))
   10060             :         {
   10061           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10062             :                      "ZLEVEL=%s value not recognised, ignoring.", pszValue);
   10063           0 :             nZLevel = NCDF_DEFLATE_LEVEL;
   10064             :         }
   10065             :     }
   10066             : 
   10067             :     // CHUNKING option.
   10068         254 :     bChunking =
   10069         254 :         CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
   10070             : 
   10071             :     // MULTIPLE_LAYERS option.
   10072             :     const char *pszMultipleLayerBehavior =
   10073         254 :         CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
   10074         508 :     const char *pszGeometryEnc = CSLFetchNameValueDef(
   10075         254 :         papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
   10076         254 :     if (EQUAL(pszMultipleLayerBehavior, "NO") ||
   10077           3 :         EQUAL(pszGeometryEnc, "CF_1.8"))
   10078             :     {
   10079         251 :         eMultipleLayerBehavior = SINGLE_LAYER;
   10080             :     }
   10081           3 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
   10082             :     {
   10083           2 :         eMultipleLayerBehavior = SEPARATE_FILES;
   10084             :     }
   10085           1 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
   10086             :     {
   10087           1 :         if (eFormat == NCDF_FORMAT_NC4)
   10088             :         {
   10089           1 :             eMultipleLayerBehavior = SEPARATE_GROUPS;
   10090             :         }
   10091             :         else
   10092             :         {
   10093           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10094             :                      "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
   10095             :                      pszMultipleLayerBehavior);
   10096             :         }
   10097             :     }
   10098             :     else
   10099             :     {
   10100           0 :         CPLError(CE_Warning, CPLE_IllegalArg,
   10101             :                  "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
   10102             :     }
   10103             : 
   10104             :     // Set nCreateMode based on eFormat.
   10105         254 :     switch (eFormat)
   10106             :     {
   10107             : #ifdef NETCDF_HAS_NC2
   10108           1 :         case NCDF_FORMAT_NC2:
   10109           1 :             nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
   10110           1 :             break;
   10111             : #endif
   10112          85 :         case NCDF_FORMAT_NC4:
   10113          85 :             nCreateMode = NC_CLOBBER | NC_NETCDF4;
   10114          85 :             break;
   10115           5 :         case NCDF_FORMAT_NC4C:
   10116           5 :             nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
   10117           5 :             break;
   10118         163 :         case NCDF_FORMAT_NC:
   10119             :         default:
   10120         163 :             nCreateMode = NC_CLOBBER;
   10121         163 :             break;
   10122             :     }
   10123             : 
   10124         254 :     CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
   10125         254 :              eFormat, eCompress, nZLevel);
   10126         254 : }
   10127             : 
   10128         278 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
   10129             : {
   10130         278 :     if (eCompress == NCDF_COMPRESS_DEFLATE)
   10131             :     {
   10132             :         // Must set chunk size to avoid huge performance hit (set
   10133             :         // bChunkingArg=TRUE)
   10134             :         // perhaps another solution it to change the chunk cache?
   10135             :         // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
   10136             :         // TODO: make sure this is okay.
   10137           2 :         CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
   10138             :                  static_cast<int>(bChunkingArg), nZLevel);
   10139             : 
   10140           2 :         int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
   10141           2 :         NCDF_ERR(status);
   10142             : 
   10143           2 :         if (status == NC_NOERR && bChunkingArg && bChunking)
   10144             :         {
   10145             :             // set chunking to be 1 for all dims, except X dim
   10146             :             // size_t chunksize[] = { 1, (size_t)nRasterXSize };
   10147             :             size_t chunksize[MAX_NC_DIMS];
   10148             :             int nd;
   10149           2 :             nc_inq_varndims(cdfid, nVarId, &nd);
   10150           2 :             chunksize[0] = (size_t)1;
   10151           2 :             chunksize[1] = (size_t)1;
   10152           2 :             for (int i = 2; i < nd; i++)
   10153           0 :                 chunksize[i] = (size_t)1;
   10154           2 :             chunksize[nd - 1] = (size_t)nRasterXSize;
   10155             : 
   10156             :             // Config options just for testing purposes
   10157             :             const char *pszBlockXSize =
   10158           2 :                 CPLGetConfigOption("BLOCKXSIZE", nullptr);
   10159           2 :             if (pszBlockXSize)
   10160           0 :                 chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
   10161             : 
   10162             :             const char *pszBlockYSize =
   10163           2 :                 CPLGetConfigOption("BLOCKYSIZE", nullptr);
   10164           2 :             if (nd >= 2 && pszBlockYSize)
   10165           0 :                 chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
   10166             : 
   10167           2 :             CPLDebug("GDAL_netCDF",
   10168             :                      "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
   10169           2 :                      (long)chunksize[0], (long)chunksize[1],
   10170           2 :                      (long)chunksize[nd - 1], nd);
   10171             : #ifdef NCDF_DEBUG
   10172             :             for (int i = 0; i < nd; i++)
   10173             :                 CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
   10174             :                          chunksize[i]);
   10175             : #endif
   10176             : 
   10177           2 :             status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
   10178           2 :             NCDF_ERR(status);
   10179             :         }
   10180             :         else
   10181             :         {
   10182           0 :             CPLDebug("GDAL_netCDF", "chunksize not set");
   10183             :         }
   10184           2 :         return status;
   10185             :     }
   10186         276 :     return NC_NOERR;
   10187             : }
   10188             : 
   10189             : /************************************************************************/
   10190             : /*                           NCDFUnloadDriver()                         */
   10191             : /************************************************************************/
   10192             : 
   10193           8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
   10194             : {
   10195           8 :     if (hNCMutex != nullptr)
   10196           4 :         CPLDestroyMutex(hNCMutex);
   10197           8 :     hNCMutex = nullptr;
   10198           8 : }
   10199             : 
   10200             : /************************************************************************/
   10201             : /*                          GDALRegister_netCDF()                       */
   10202             : /************************************************************************/
   10203             : 
   10204             : class GDALnetCDFDriver final : public GDALDriver
   10205             : {
   10206             :   public:
   10207          18 :     GDALnetCDFDriver() = default;
   10208             : 
   10209             :     const char *GetMetadataItem(const char *pszName,
   10210             :                                 const char *pszDomain) override;
   10211             : 
   10212          89 :     char **GetMetadata(const char *pszDomain) override
   10213             :     {
   10214         178 :         std::lock_guard oLock(m_oMutex);
   10215          89 :         InitializeDCAPVirtualIO();
   10216         178 :         return GDALDriver::GetMetadata(pszDomain);
   10217             :     }
   10218             : 
   10219             :   private:
   10220             :     std::mutex m_oMutex{};
   10221             :     bool m_bInitialized = false;
   10222             : 
   10223         102 :     void InitializeDCAPVirtualIO()
   10224             :     {
   10225         102 :         if (!m_bInitialized)
   10226             :         {
   10227          12 :             m_bInitialized = true;
   10228             : 
   10229             : #ifdef ENABLE_UFFD
   10230          12 :             if (CPLIsUserFaultMappingSupported())
   10231             :             {
   10232          12 :                 SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
   10233             :             }
   10234             : #endif
   10235             :         }
   10236         102 :     }
   10237             : };
   10238             : 
   10239        1339 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
   10240             :                                               const char *pszDomain)
   10241             : {
   10242        2678 :     std::lock_guard oLock(m_oMutex);
   10243        1339 :     if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
   10244             :     {
   10245          13 :         InitializeDCAPVirtualIO();
   10246             :     }
   10247        2678 :     return GDALDriver::GetMetadataItem(pszName, pszDomain);
   10248             : }
   10249             : 
   10250          18 : void GDALRegister_netCDF()
   10251             : 
   10252             : {
   10253          18 :     if (!GDAL_CHECK_VERSION("netCDF driver"))
   10254           0 :         return;
   10255             : 
   10256          18 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
   10257           0 :         return;
   10258             : 
   10259          18 :     GDALDriver *poDriver = new GDALnetCDFDriver();
   10260          18 :     netCDFDriverSetCommonMetadata(poDriver);
   10261             : 
   10262          18 :     poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
   10263          18 :                               GDAL_DEFAULT_NCDF_CONVENTIONS);
   10264          18 :     poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
   10265             : 
   10266             :     // Set pfns and register driver.
   10267          18 :     poDriver->pfnOpen = netCDFDataset::Open;
   10268          18 :     poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
   10269          18 :     poDriver->pfnCreate = netCDFDataset::Create;
   10270          18 :     poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
   10271          18 :     poDriver->pfnUnloadDriver = NCDFUnloadDriver;
   10272             : 
   10273          18 :     GetGDALDriverManager()->RegisterDriver(poDriver);
   10274             : }
   10275             : 
   10276             : /************************************************************************/
   10277             : /*                          New functions                               */
   10278             : /************************************************************************/
   10279             : 
   10280             : /* Test for GDAL version string >= target */
   10281         241 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
   10282             : {
   10283             : 
   10284             :     // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
   10285         241 :     if (pszVersion == nullptr || EQUAL(pszVersion, ""))
   10286           0 :         return false;
   10287         241 :     else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
   10288           0 :         return false;
   10289             :     // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
   10290         241 :     else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
   10291           0 :         return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
   10292         241 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
   10293           2 :         return nTarget <= 1900;
   10294         239 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
   10295           0 :         return nTarget <= 1800;
   10296             : 
   10297         239 :     char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
   10298             : 
   10299         239 :     int nVersions[] = {0, 0, 0, 0};
   10300         956 :     for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
   10301             :          iToken++)
   10302             :     {
   10303         717 :         nVersions[iToken] = atoi(papszTokens[iToken]);
   10304         717 :         if (nVersions[iToken] < 0)
   10305           0 :             nVersions[iToken] = 0;
   10306         717 :         else if (nVersions[iToken] > 99)
   10307           0 :             nVersions[iToken] = 99;
   10308             :     }
   10309             : 
   10310         239 :     int nVersion = 0;
   10311         239 :     if (nVersions[0] > 1 || nVersions[1] >= 10)
   10312         239 :         nVersion =
   10313         239 :             GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
   10314             :     else
   10315           0 :         nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
   10316           0 :                    nVersions[2] * 10 + nVersions[3];
   10317             : 
   10318         239 :     CSLDestroy(papszTokens);
   10319         239 :     return nTarget <= nVersion;
   10320             : }
   10321             : 
   10322             : // Add Conventions, GDAL version and history.
   10323         167 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
   10324             :                                bool bWriteGDALVersion, bool bWriteGDALHistory,
   10325             :                                const char *pszOldHist,
   10326             :                                const char *pszFunctionName,
   10327             :                                const char *pszCFVersion)
   10328             : {
   10329         167 :     if (pszCFVersion == nullptr)
   10330             :     {
   10331          42 :         pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
   10332             :     }
   10333         167 :     int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
   10334             :                                  strlen(pszCFVersion), pszCFVersion);
   10335         167 :     NCDF_ERR(status);
   10336             : 
   10337         167 :     if (bWriteGDALVersion)
   10338             :     {
   10339         165 :         const char *pszNCDF_GDAL = GDALVersionInfo("--version");
   10340         165 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
   10341             :                                  strlen(pszNCDF_GDAL), pszNCDF_GDAL);
   10342         165 :         NCDF_ERR(status);
   10343             :     }
   10344             : 
   10345         167 :     if (bWriteGDALHistory)
   10346             :     {
   10347             :         // Add history.
   10348         330 :         CPLString osTmp;
   10349             : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
   10350             :         if (!EQUAL(GDALGetCmdLine(), ""))
   10351             :             osTmp = GDALGetCmdLine();
   10352             :         else
   10353             :             osTmp =
   10354             :                 CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10355             : #else
   10356         165 :         osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10357             : #endif
   10358             : 
   10359         165 :         NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
   10360             :     }
   10361           2 :     else if (pszOldHist != nullptr)
   10362             :     {
   10363           0 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10364             :                                  strlen(pszOldHist), pszOldHist);
   10365           0 :         NCDF_ERR(status);
   10366             :     }
   10367         167 : }
   10368             : 
   10369             : // Code taken from cdo and libcdi, used for writing the history attribute.
   10370             : 
   10371             : // void cdoDefHistory(int fileID, char *histstring)
   10372         165 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
   10373             :                            const char *pszOldHist)
   10374             : {
   10375             :     // Check pszOldHist - as if there was no previous history, it will be
   10376             :     // a null pointer - if so set as empty.
   10377         165 :     if (nullptr == pszOldHist)
   10378             :     {
   10379          53 :         pszOldHist = "";
   10380             :     }
   10381             : 
   10382             :     char strtime[32];
   10383         165 :     strtime[0] = '\0';
   10384             : 
   10385         165 :     time_t tp = time(nullptr);
   10386         165 :     if (tp != -1)
   10387             :     {
   10388             :         struct tm ltime;
   10389         165 :         VSILocalTime(&tp, &ltime);
   10390         165 :         (void)strftime(strtime, sizeof(strtime),
   10391             :                        "%a %b %d %H:%M:%S %Y: ", &ltime);
   10392             :     }
   10393             : 
   10394             :     // status = nc_get_att_text(fpImage, NC_GLOBAL,
   10395             :     //                           "history", pszOldHist);
   10396             :     // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
   10397             : 
   10398         165 :     size_t nNewHistSize =
   10399         165 :         strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
   10400             :     char *pszNewHist =
   10401         165 :         static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
   10402             : 
   10403         165 :     strcpy(pszNewHist, strtime);
   10404         165 :     strcat(pszNewHist, pszAddHist);
   10405             : 
   10406             :     // int disableHistory = FALSE;
   10407             :     // if( !disableHistory )
   10408             :     {
   10409         165 :         if (!EQUAL(pszOldHist, ""))
   10410           3 :             strcat(pszNewHist, "\n");
   10411         165 :         strcat(pszNewHist, pszOldHist);
   10412             :     }
   10413             : 
   10414         165 :     const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10415             :                                        strlen(pszNewHist), pszNewHist);
   10416         165 :     NCDF_ERR(status);
   10417             : 
   10418         165 :     CPLFree(pszNewHist);
   10419         165 : }
   10420             : 
   10421        6133 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
   10422             :                              size_t *nDestSize)
   10423             : {
   10424             :     /* Reallocate the data string until the content fits */
   10425        6133 :     while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
   10426             :     {
   10427         408 :         (*nDestSize) *= 2;
   10428         408 :         *ppszDest = static_cast<char *>(
   10429         408 :             CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
   10430             : #ifdef NCDF_DEBUG
   10431             :         CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
   10432             :                  (*nDestSize) / 2, *nDestSize);
   10433             : #endif
   10434             :     }
   10435        5725 :     strcat(*ppszDest, pszSrc);
   10436             : 
   10437        5725 :     return CE_None;
   10438             : }
   10439             : 
   10440             : /* helper function for NCDFGetAttr() */
   10441             : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
   10442             : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
   10443             : /* *ppszValue is the responsibility of the caller and must be freed */
   10444       63223 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
   10445             :                            double *pdfValue, char **ppszValue)
   10446             : {
   10447       63223 :     nc_type nAttrType = NC_NAT;
   10448       63223 :     size_t nAttrLen = 0;
   10449             : 
   10450       63223 :     if (ppszValue)
   10451       62076 :         *ppszValue = nullptr;
   10452             : 
   10453       63223 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10454       63223 :     if (status != NC_NOERR)
   10455       33970 :         return CE_Failure;
   10456             : 
   10457             : #ifdef NCDF_DEBUG
   10458             :     CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
   10459             :              nAttrLen, nAttrType);
   10460             : #endif
   10461       29253 :     if (nAttrLen == 0 && nAttrType != NC_CHAR)
   10462           1 :         return CE_Failure;
   10463             : 
   10464             :     /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
   10465       29252 :     size_t nAttrValueSize = nAttrLen + 1;
   10466       29252 :     if (nAttrType != NC_CHAR && nAttrValueSize < 10)
   10467        3210 :         nAttrValueSize = 10;
   10468       29252 :     if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
   10469        1562 :         nAttrValueSize = 20;
   10470       29252 :     if (nAttrType == NC_INT64 && nAttrValueSize < 20)
   10471          22 :         nAttrValueSize = 22;
   10472             :     char *pszAttrValue =
   10473       29252 :         static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
   10474       29252 :     *pszAttrValue = '\0';
   10475             : 
   10476       29252 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10477         596 :         NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
   10478             : 
   10479       29252 :     double dfValue = 0.0;
   10480       29252 :     size_t m = 0;
   10481             :     char szTemp[256];
   10482       29252 :     bool bSetDoubleFromStr = false;
   10483             : 
   10484       29252 :     switch (nAttrType)
   10485             :     {
   10486       26040 :         case NC_CHAR:
   10487       26040 :             CPL_IGNORE_RET_VAL(
   10488       26040 :                 nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
   10489       26040 :             pszAttrValue[nAttrLen] = '\0';
   10490       26040 :             bSetDoubleFromStr = true;
   10491       26040 :             dfValue = 0.0;
   10492       26040 :             break;
   10493          94 :         case NC_BYTE:
   10494             :         {
   10495             :             signed char *pscTemp = static_cast<signed char *>(
   10496          94 :                 CPLCalloc(nAttrLen, sizeof(signed char)));
   10497          94 :             nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
   10498          94 :             dfValue = static_cast<double>(pscTemp[0]);
   10499          94 :             if (nAttrLen > 1)
   10500             :             {
   10501          24 :                 for (m = 0; m < nAttrLen - 1; m++)
   10502             :                 {
   10503          13 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10504          13 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10505             :                 }
   10506             :             }
   10507          94 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10508          94 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10509          94 :             CPLFree(pscTemp);
   10510          94 :             break;
   10511             :         }
   10512         483 :         case NC_SHORT:
   10513             :         {
   10514             :             short *psTemp =
   10515         483 :                 static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
   10516         483 :             nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
   10517         483 :             dfValue = static_cast<double>(psTemp[0]);
   10518         483 :             if (nAttrLen > 1)
   10519             :             {
   10520         760 :                 for (m = 0; m < nAttrLen - 1; m++)
   10521             :                 {
   10522         380 :                     snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10523         380 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10524             :                 }
   10525             :             }
   10526         483 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10527         483 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10528         483 :             CPLFree(psTemp);
   10529         483 :             break;
   10530             :         }
   10531         528 :         case NC_INT:
   10532             :         {
   10533         528 :             int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10534         528 :             nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
   10535         528 :             dfValue = static_cast<double>(pnTemp[0]);
   10536         528 :             if (nAttrLen > 1)
   10537             :             {
   10538         218 :                 for (m = 0; m < nAttrLen - 1; m++)
   10539             :                 {
   10540         139 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10541         139 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10542             :                 }
   10543             :             }
   10544         528 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10545         528 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10546         528 :             CPLFree(pnTemp);
   10547         528 :             break;
   10548             :         }
   10549         393 :         case NC_FLOAT:
   10550             :         {
   10551             :             float *pfTemp =
   10552         393 :                 static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10553         393 :             nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
   10554         393 :             dfValue = static_cast<double>(pfTemp[0]);
   10555         393 :             if (nAttrLen > 1)
   10556             :             {
   10557          56 :                 for (m = 0; m < nAttrLen - 1; m++)
   10558             :                 {
   10559          28 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10560          28 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10561             :                 }
   10562             :             }
   10563         393 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10564         393 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10565         393 :             CPLFree(pfTemp);
   10566         393 :             break;
   10567             :         }
   10568        1562 :         case NC_DOUBLE:
   10569             :         {
   10570             :             double *pdfTemp =
   10571        1562 :                 static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10572        1562 :             nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
   10573        1562 :             dfValue = pdfTemp[0];
   10574        1562 :             if (nAttrLen > 1)
   10575             :             {
   10576         166 :                 for (m = 0; m < nAttrLen - 1; m++)
   10577             :                 {
   10578          90 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10579          90 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10580             :                 }
   10581             :             }
   10582        1562 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10583        1562 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10584        1562 :             CPLFree(pdfTemp);
   10585        1562 :             break;
   10586             :         }
   10587          10 :         case NC_STRING:
   10588             :         {
   10589             :             char **ppszTemp =
   10590          10 :                 static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
   10591          10 :             nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
   10592          10 :             bSetDoubleFromStr = true;
   10593          10 :             dfValue = 0.0;
   10594          10 :             if (nAttrLen > 1)
   10595             :             {
   10596          19 :                 for (m = 0; m < nAttrLen - 1; m++)
   10597             :                 {
   10598          12 :                     NCDFSafeStrcat(&pszAttrValue,
   10599          12 :                                    ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10600             :                                    &nAttrValueSize);
   10601          12 :                     NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
   10602             :                 }
   10603             :             }
   10604          10 :             NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10605             :                            &nAttrValueSize);
   10606          10 :             nc_free_string(nAttrLen, ppszTemp);
   10607          10 :             CPLFree(ppszTemp);
   10608          10 :             break;
   10609             :         }
   10610          28 :         case NC_UBYTE:
   10611             :         {
   10612             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   10613          28 :                 CPLCalloc(nAttrLen, sizeof(unsigned char)));
   10614          28 :             nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
   10615          28 :             dfValue = static_cast<double>(pucTemp[0]);
   10616          28 :             if (nAttrLen > 1)
   10617             :             {
   10618           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10619             :                 {
   10620           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10621           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10622             :                 }
   10623             :             }
   10624          28 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10625          28 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10626          28 :             CPLFree(pucTemp);
   10627          28 :             break;
   10628             :         }
   10629          26 :         case NC_USHORT:
   10630             :         {
   10631             :             unsigned short *pusTemp;
   10632             :             pusTemp = static_cast<unsigned short *>(
   10633          26 :                 CPLCalloc(nAttrLen, sizeof(unsigned short)));
   10634          26 :             nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
   10635          26 :             dfValue = static_cast<double>(pusTemp[0]);
   10636          26 :             if (nAttrLen > 1)
   10637             :             {
   10638          10 :                 for (m = 0; m < nAttrLen - 1; m++)
   10639             :                 {
   10640           5 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10641           5 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10642             :                 }
   10643             :             }
   10644          26 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10645          26 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10646          26 :             CPLFree(pusTemp);
   10647          26 :             break;
   10648             :         }
   10649          18 :         case NC_UINT:
   10650             :         {
   10651             :             unsigned int *punTemp =
   10652          18 :                 static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10653          18 :             nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
   10654          18 :             dfValue = static_cast<double>(punTemp[0]);
   10655          18 :             if (nAttrLen > 1)
   10656             :             {
   10657           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10658             :                 {
   10659           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   10660           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10661             :                 }
   10662             :             }
   10663          18 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   10664          18 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10665          18 :             CPLFree(punTemp);
   10666          18 :             break;
   10667             :         }
   10668          22 :         case NC_INT64:
   10669             :         {
   10670             :             GIntBig *panTemp =
   10671          22 :                 static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
   10672          22 :             nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
   10673          22 :             dfValue = static_cast<double>(panTemp[0]);
   10674          22 :             if (nAttrLen > 1)
   10675             :             {
   10676           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10677             :                 {
   10678           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
   10679           0 :                                 panTemp[m]);
   10680           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10681             :                 }
   10682             :             }
   10683          22 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
   10684          22 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10685          22 :             CPLFree(panTemp);
   10686          22 :             break;
   10687             :         }
   10688          22 :         case NC_UINT64:
   10689             :         {
   10690             :             GUIntBig *panTemp =
   10691          22 :                 static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
   10692          22 :             nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
   10693          22 :             dfValue = static_cast<double>(panTemp[0]);
   10694          22 :             if (nAttrLen > 1)
   10695             :             {
   10696           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10697             :                 {
   10698           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
   10699           0 :                                 panTemp[m]);
   10700           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10701             :                 }
   10702             :             }
   10703          22 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
   10704          22 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10705          22 :             CPLFree(panTemp);
   10706          22 :             break;
   10707             :         }
   10708          26 :         default:
   10709          26 :             CPLDebug("GDAL_netCDF",
   10710             :                      "NCDFGetAttr unsupported type %d for attribute %s",
   10711             :                      nAttrType, pszAttrName);
   10712          26 :             break;
   10713             :     }
   10714             : 
   10715       29252 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10716         596 :         NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
   10717             : 
   10718       29252 :     if (bSetDoubleFromStr)
   10719             :     {
   10720       26050 :         if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
   10721             :         {
   10722       25868 :             if (ppszValue == nullptr && pdfValue != nullptr)
   10723             :             {
   10724           1 :                 CPLFree(pszAttrValue);
   10725           1 :                 return CE_Failure;
   10726             :             }
   10727             :         }
   10728       26049 :         dfValue = CPLAtof(pszAttrValue);
   10729             :     }
   10730             : 
   10731             :     /* set return values */
   10732       29251 :     if (ppszValue)
   10733       28939 :         *ppszValue = pszAttrValue;
   10734             :     else
   10735         312 :         CPLFree(pszAttrValue);
   10736             : 
   10737       29251 :     if (pdfValue)
   10738         312 :         *pdfValue = dfValue;
   10739             : 
   10740       29251 :     return CE_None;
   10741             : }
   10742             : 
   10743             : /* sets pdfValue to first value found */
   10744        1147 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10745             :                    double *pdfValue)
   10746             : {
   10747        1147 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
   10748             : }
   10749             : 
   10750             : /* pszValue is the responsibility of the caller and must be freed */
   10751       62076 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10752             :                    char **pszValue)
   10753             : {
   10754       62076 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
   10755             : }
   10756             : 
   10757             : /* By default write NC_CHAR, but detect for int/float/double and */
   10758             : /* NC4 string arrays */
   10759         106 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10760             :                           const char *pszValue)
   10761             : {
   10762         106 :     int status = 0;
   10763         106 :     char *pszTemp = nullptr;
   10764             : 
   10765             :     /* get the attribute values as tokens */
   10766         106 :     char **papszValues = NCDFTokenizeArray(pszValue);
   10767         106 :     if (papszValues == nullptr)
   10768           0 :         return CE_Failure;
   10769             : 
   10770         106 :     size_t nAttrLen = CSLCount(papszValues);
   10771             : 
   10772             :     /* first detect type */
   10773         106 :     nc_type nAttrType = NC_CHAR;
   10774         106 :     nc_type nTmpAttrType = NC_CHAR;
   10775         225 :     for (size_t i = 0; i < nAttrLen; i++)
   10776             :     {
   10777         119 :         nTmpAttrType = NC_CHAR;
   10778         119 :         bool bFoundType = false;
   10779         119 :         errno = 0;
   10780         119 :         int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   10781             :         /* test for int */
   10782             :         /* TODO test for Byte and short - can this be done safely? */
   10783         119 :         if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
   10784             :         {
   10785             :             char szTemp[256];
   10786          19 :             CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
   10787          19 :             if (EQUAL(szTemp, papszValues[i]))
   10788             :             {
   10789          19 :                 bFoundType = true;
   10790          19 :                 nTmpAttrType = NC_INT;
   10791             :             }
   10792             :             else
   10793             :             {
   10794             :                 unsigned int unValue = static_cast<unsigned int>(
   10795           0 :                     strtoul(papszValues[i], &pszTemp, 10));
   10796           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
   10797           0 :                 if (EQUAL(szTemp, papszValues[i]))
   10798             :                 {
   10799           0 :                     bFoundType = true;
   10800           0 :                     nTmpAttrType = NC_UINT;
   10801             :                 }
   10802             :             }
   10803             :         }
   10804         119 :         if (!bFoundType)
   10805             :         {
   10806             :             /* test for double */
   10807         100 :             errno = 0;
   10808         100 :             double dfValue = CPLStrtod(papszValues[i], &pszTemp);
   10809         100 :             if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
   10810             :             {
   10811             :                 // Test for float instead of double.
   10812             :                 // strtof() is C89, which is not available in MSVC.
   10813             :                 // See if we loose precision if we cast to float and write to
   10814             :                 // char*.
   10815          14 :                 float fValue = float(dfValue);
   10816             :                 char szTemp[256];
   10817          14 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
   10818          14 :                 if (EQUAL(szTemp, papszValues[i]))
   10819           8 :                     nTmpAttrType = NC_FLOAT;
   10820             :                 else
   10821           6 :                     nTmpAttrType = NC_DOUBLE;
   10822             :             }
   10823             :         }
   10824         119 :         if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
   10825          99 :              nTmpAttrType > nAttrType) ||
   10826          99 :             (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
   10827           5 :             (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
   10828          20 :             nAttrType = nTmpAttrType;
   10829             :     }
   10830             : 
   10831             : #ifdef DEBUG
   10832         106 :     if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
   10833             :     {
   10834           0 :         nAttrType = NC_DOUBLE;
   10835           0 :         nAttrLen = 0;
   10836             :     }
   10837             : #endif
   10838             : 
   10839             :     /* now write the data */
   10840         106 :     if (nAttrType == NC_CHAR)
   10841             :     {
   10842          86 :         int nTmpFormat = 0;
   10843          86 :         if (nAttrLen > 1)
   10844             :         {
   10845           0 :             status = nc_inq_format(nCdfId, &nTmpFormat);
   10846           0 :             NCDF_ERR(status);
   10847             :         }
   10848          86 :         if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
   10849           0 :             status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
   10850             :                                        const_cast<const char **>(papszValues));
   10851             :         else
   10852          86 :             status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
   10853             :                                      strlen(pszValue), pszValue);
   10854          86 :         NCDF_ERR(status);
   10855             :     }
   10856             :     else
   10857             :     {
   10858          20 :         switch (nAttrType)
   10859             :         {
   10860          11 :             case NC_INT:
   10861             :             {
   10862             :                 int *pnTemp =
   10863          11 :                     static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10864          30 :                 for (size_t i = 0; i < nAttrLen; i++)
   10865             :                 {
   10866          19 :                     pnTemp[i] =
   10867          19 :                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   10868             :                 }
   10869          11 :                 status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
   10870             :                                         nAttrLen, pnTemp);
   10871          11 :                 NCDF_ERR(status);
   10872          11 :                 CPLFree(pnTemp);
   10873          11 :                 break;
   10874             :             }
   10875           0 :             case NC_UINT:
   10876             :             {
   10877             :                 unsigned int *punTemp = static_cast<unsigned int *>(
   10878           0 :                     CPLCalloc(nAttrLen, sizeof(unsigned int)));
   10879           0 :                 for (size_t i = 0; i < nAttrLen; i++)
   10880             :                 {
   10881           0 :                     punTemp[i] = static_cast<unsigned int>(
   10882           0 :                         strtol(papszValues[i], &pszTemp, 10));
   10883             :                 }
   10884           0 :                 status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
   10885             :                                          nAttrLen, punTemp);
   10886           0 :                 NCDF_ERR(status);
   10887           0 :                 CPLFree(punTemp);
   10888           0 :                 break;
   10889             :             }
   10890           6 :             case NC_FLOAT:
   10891             :             {
   10892             :                 float *pfTemp =
   10893           6 :                     static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10894          14 :                 for (size_t i = 0; i < nAttrLen; i++)
   10895             :                 {
   10896           8 :                     pfTemp[i] =
   10897           8 :                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
   10898             :                 }
   10899           6 :                 status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
   10900             :                                           nAttrLen, pfTemp);
   10901           6 :                 NCDF_ERR(status);
   10902           6 :                 CPLFree(pfTemp);
   10903           6 :                 break;
   10904             :             }
   10905           3 :             case NC_DOUBLE:
   10906             :             {
   10907             :                 double *pdfTemp =
   10908           3 :                     static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10909           9 :                 for (size_t i = 0; i < nAttrLen; i++)
   10910             :                 {
   10911           6 :                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
   10912             :                 }
   10913           3 :                 status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
   10914             :                                            NC_DOUBLE, nAttrLen, pdfTemp);
   10915           3 :                 NCDF_ERR(status);
   10916           3 :                 CPLFree(pdfTemp);
   10917           3 :                 break;
   10918             :             }
   10919           0 :             default:
   10920           0 :                 if (papszValues)
   10921           0 :                     CSLDestroy(papszValues);
   10922           0 :                 return CE_Failure;
   10923             :                 break;
   10924             :         }
   10925             :     }
   10926             : 
   10927         106 :     if (papszValues)
   10928         106 :         CSLDestroy(papszValues);
   10929             : 
   10930         106 :     return CE_None;
   10931             : }
   10932             : 
   10933          76 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
   10934             : {
   10935             :     /* get var information */
   10936          76 :     int nVarDimId = -1;
   10937          76 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   10938          76 :     if (status != NC_NOERR || nVarDimId != 1)
   10939           0 :         return CE_Failure;
   10940             : 
   10941          76 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   10942          76 :     if (status != NC_NOERR)
   10943           0 :         return CE_Failure;
   10944             : 
   10945          76 :     nc_type nVarType = NC_NAT;
   10946          76 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   10947          76 :     if (status != NC_NOERR)
   10948           0 :         return CE_Failure;
   10949             : 
   10950          76 :     size_t nVarLen = 0;
   10951          76 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   10952          76 :     if (status != NC_NOERR)
   10953           0 :         return CE_Failure;
   10954             : 
   10955          76 :     size_t start[1] = {0};
   10956          76 :     size_t count[1] = {nVarLen};
   10957             : 
   10958             :     /* Allocate guaranteed minimum size */
   10959          76 :     size_t nVarValueSize = NCDF_MAX_STR_LEN;
   10960             :     char *pszVarValue =
   10961          76 :         static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
   10962          76 :     *pszVarValue = '\0';
   10963             : 
   10964          76 :     if (nVarLen == 0)
   10965             :     {
   10966             :         /* set return values */
   10967           1 :         *pszValue = pszVarValue;
   10968             : 
   10969           1 :         return CE_None;
   10970             :     }
   10971             : 
   10972          75 :     if (nVarLen > 1 && nVarType != NC_CHAR)
   10973          40 :         NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
   10974             : 
   10975          75 :     switch (nVarType)
   10976             :     {
   10977           0 :         case NC_CHAR:
   10978           0 :             nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
   10979           0 :             pszVarValue[nVarLen] = '\0';
   10980           0 :             break;
   10981           0 :         case NC_BYTE:
   10982             :         {
   10983             :             signed char *pscTemp = static_cast<signed char *>(
   10984           0 :                 CPLCalloc(nVarLen, sizeof(signed char)));
   10985           0 :             nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   10986             :             char szTemp[256];
   10987           0 :             size_t m = 0;
   10988           0 :             for (; m < nVarLen - 1; m++)
   10989             :             {
   10990           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10991           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10992             :             }
   10993           0 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10994           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10995           0 :             CPLFree(pscTemp);
   10996           0 :             break;
   10997             :         }
   10998           0 :         case NC_SHORT:
   10999             :         {
   11000             :             short *psTemp =
   11001           0 :                 static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11002           0 :             nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
   11003             :             char szTemp[256];
   11004           0 :             size_t m = 0;
   11005           0 :             for (; m < nVarLen - 1; m++)
   11006             :             {
   11007           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   11008           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11009             :             }
   11010           0 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   11011           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11012           0 :             CPLFree(psTemp);
   11013           0 :             break;
   11014             :         }
   11015          19 :         case NC_INT:
   11016             :         {
   11017          19 :             int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11018          19 :             nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11019             :             char szTemp[256];
   11020          19 :             size_t m = 0;
   11021          36 :             for (; m < nVarLen - 1; m++)
   11022             :             {
   11023          17 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   11024          17 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11025             :             }
   11026          19 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   11027          19 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11028          19 :             CPLFree(pnTemp);
   11029          19 :             break;
   11030             :         }
   11031           8 :         case NC_FLOAT:
   11032             :         {
   11033             :             float *pfTemp =
   11034           8 :                 static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11035           8 :             nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11036             :             char szTemp[256];
   11037           8 :             size_t m = 0;
   11038         325 :             for (; m < nVarLen - 1; m++)
   11039             :             {
   11040         317 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   11041         317 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11042             :             }
   11043           8 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   11044           8 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11045           8 :             CPLFree(pfTemp);
   11046           8 :             break;
   11047             :         }
   11048          47 :         case NC_DOUBLE:
   11049             :         {
   11050             :             double *pdfTemp =
   11051          47 :                 static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11052          47 :             nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11053             :             char szTemp[256];
   11054          47 :             size_t m = 0;
   11055         225 :             for (; m < nVarLen - 1; m++)
   11056             :             {
   11057         178 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   11058         178 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11059             :             }
   11060          47 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   11061          47 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11062          47 :             CPLFree(pdfTemp);
   11063          47 :             break;
   11064             :         }
   11065           0 :         case NC_STRING:
   11066             :         {
   11067             :             char **ppszTemp =
   11068           0 :                 static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
   11069           0 :             nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
   11070           0 :             size_t m = 0;
   11071           0 :             for (; m < nVarLen - 1; m++)
   11072             :             {
   11073           0 :                 NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11074           0 :                 NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
   11075             :             }
   11076           0 :             NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11077           0 :             nc_free_string(nVarLen, ppszTemp);
   11078           0 :             CPLFree(ppszTemp);
   11079           0 :             break;
   11080             :         }
   11081           0 :         case NC_UBYTE:
   11082             :         {
   11083             :             unsigned char *pucTemp;
   11084             :             pucTemp = static_cast<unsigned char *>(
   11085           0 :                 CPLCalloc(nVarLen, sizeof(unsigned char)));
   11086           0 :             nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
   11087             :             char szTemp[256];
   11088           0 :             size_t m = 0;
   11089           0 :             for (; m < nVarLen - 1; m++)
   11090             :             {
   11091           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   11092           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11093             :             }
   11094           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   11095           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11096           0 :             CPLFree(pucTemp);
   11097           0 :             break;
   11098             :         }
   11099           0 :         case NC_USHORT:
   11100             :         {
   11101             :             unsigned short *pusTemp;
   11102             :             pusTemp = static_cast<unsigned short *>(
   11103           0 :                 CPLCalloc(nVarLen, sizeof(unsigned short)));
   11104           0 :             nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
   11105             :             char szTemp[256];
   11106           0 :             size_t m = 0;
   11107           0 :             for (; m < nVarLen - 1; m++)
   11108             :             {
   11109           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   11110           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11111             :             }
   11112           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   11113           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11114           0 :             CPLFree(pusTemp);
   11115           0 :             break;
   11116             :         }
   11117           0 :         case NC_UINT:
   11118             :         {
   11119             :             unsigned int *punTemp;
   11120             :             punTemp = static_cast<unsigned int *>(
   11121           0 :                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11122           0 :             nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
   11123             :             char szTemp[256];
   11124           0 :             size_t m = 0;
   11125           0 :             for (; m < nVarLen - 1; m++)
   11126             :             {
   11127           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   11128           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11129             :             }
   11130           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   11131           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11132           0 :             CPLFree(punTemp);
   11133           0 :             break;
   11134             :         }
   11135           1 :         case NC_INT64:
   11136             :         {
   11137             :             long long *pnTemp =
   11138           1 :                 static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
   11139           1 :             nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
   11140             :             char szTemp[256];
   11141           1 :             size_t m = 0;
   11142           2 :             for (; m < nVarLen - 1; m++)
   11143             :             {
   11144           1 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
   11145           1 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11146             :             }
   11147           1 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
   11148           1 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11149           1 :             CPLFree(pnTemp);
   11150           1 :             break;
   11151             :         }
   11152           0 :         case NC_UINT64:
   11153             :         {
   11154             :             unsigned long long *pnTemp = static_cast<unsigned long long *>(
   11155           0 :                 CPLCalloc(nVarLen, sizeof(unsigned long long)));
   11156           0 :             nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
   11157             :             char szTemp[256];
   11158           0 :             size_t m = 0;
   11159           0 :             for (; m < nVarLen - 1; m++)
   11160             :             {
   11161           0 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
   11162           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11163             :             }
   11164           0 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
   11165           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11166           0 :             CPLFree(pnTemp);
   11167           0 :             break;
   11168             :         }
   11169           0 :         default:
   11170           0 :             CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
   11171             :                      nVarType);
   11172           0 :             CPLFree(pszVarValue);
   11173           0 :             pszVarValue = nullptr;
   11174           0 :             break;
   11175             :     }
   11176             : 
   11177          75 :     if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
   11178          40 :         NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
   11179             : 
   11180             :     /* set return values */
   11181          75 :     *pszValue = pszVarValue;
   11182             : 
   11183          75 :     return CE_None;
   11184             : }
   11185             : 
   11186           8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
   11187             : {
   11188           8 :     if (EQUAL(pszValue, ""))
   11189           0 :         return CE_Failure;
   11190             : 
   11191             :     /* get var information */
   11192           8 :     int nVarDimId = -1;
   11193           8 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   11194           8 :     if (status != NC_NOERR || nVarDimId != 1)
   11195           0 :         return CE_Failure;
   11196             : 
   11197           8 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11198           8 :     if (status != NC_NOERR)
   11199           0 :         return CE_Failure;
   11200             : 
   11201           8 :     nc_type nVarType = NC_CHAR;
   11202           8 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11203           8 :     if (status != NC_NOERR)
   11204           0 :         return CE_Failure;
   11205             : 
   11206           8 :     size_t nVarLen = 0;
   11207           8 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11208           8 :     if (status != NC_NOERR)
   11209           0 :         return CE_Failure;
   11210             : 
   11211           8 :     size_t start[1] = {0};
   11212           8 :     size_t count[1] = {nVarLen};
   11213             : 
   11214             :     /* get the values as tokens */
   11215           8 :     char **papszValues = NCDFTokenizeArray(pszValue);
   11216           8 :     if (papszValues == nullptr)
   11217           0 :         return CE_Failure;
   11218             : 
   11219           8 :     nVarLen = CSLCount(papszValues);
   11220             : 
   11221             :     /* now write the data */
   11222           8 :     if (nVarType == NC_CHAR)
   11223             :     {
   11224           0 :         status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
   11225           0 :         NCDF_ERR(status);
   11226             :     }
   11227             :     else
   11228             :     {
   11229           8 :         switch (nVarType)
   11230             :         {
   11231           0 :             case NC_BYTE:
   11232             :             {
   11233             :                 signed char *pscTemp = static_cast<signed char *>(
   11234           0 :                     CPLCalloc(nVarLen, sizeof(signed char)));
   11235           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11236             :                 {
   11237           0 :                     char *pszTemp = nullptr;
   11238           0 :                     pscTemp[i] = static_cast<signed char>(
   11239           0 :                         strtol(papszValues[i], &pszTemp, 10));
   11240             :                 }
   11241             :                 status =
   11242           0 :                     nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11243           0 :                 NCDF_ERR(status);
   11244           0 :                 CPLFree(pscTemp);
   11245           0 :                 break;
   11246             :             }
   11247           0 :             case NC_SHORT:
   11248             :             {
   11249             :                 short *psTemp =
   11250           0 :                     static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11251           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11252             :                 {
   11253           0 :                     char *pszTemp = nullptr;
   11254           0 :                     psTemp[i] = static_cast<short>(
   11255           0 :                         strtol(papszValues[i], &pszTemp, 10));
   11256             :                 }
   11257             :                 status =
   11258           0 :                     nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
   11259           0 :                 NCDF_ERR(status);
   11260           0 :                 CPLFree(psTemp);
   11261           0 :                 break;
   11262             :             }
   11263           3 :             case NC_INT:
   11264             :             {
   11265             :                 int *pnTemp =
   11266           3 :                     static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11267          11 :                 for (size_t i = 0; i < nVarLen; i++)
   11268             :                 {
   11269           8 :                     char *pszTemp = nullptr;
   11270           8 :                     pnTemp[i] =
   11271           8 :                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   11272             :                 }
   11273           3 :                 status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11274           3 :                 NCDF_ERR(status);
   11275           3 :                 CPLFree(pnTemp);
   11276           3 :                 break;
   11277             :             }
   11278           0 :             case NC_FLOAT:
   11279             :             {
   11280             :                 float *pfTemp =
   11281           0 :                     static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11282           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11283             :                 {
   11284           0 :                     char *pszTemp = nullptr;
   11285           0 :                     pfTemp[i] =
   11286           0 :                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
   11287             :                 }
   11288             :                 status =
   11289           0 :                     nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11290           0 :                 NCDF_ERR(status);
   11291           0 :                 CPLFree(pfTemp);
   11292           0 :                 break;
   11293             :             }
   11294           5 :             case NC_DOUBLE:
   11295             :             {
   11296             :                 double *pdfTemp =
   11297           5 :                     static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11298          19 :                 for (size_t i = 0; i < nVarLen; i++)
   11299             :                 {
   11300          14 :                     char *pszTemp = nullptr;
   11301          14 :                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
   11302             :                 }
   11303             :                 status =
   11304           5 :                     nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11305           5 :                 NCDF_ERR(status);
   11306           5 :                 CPLFree(pdfTemp);
   11307           5 :                 break;
   11308             :             }
   11309           0 :             default:
   11310             :             {
   11311           0 :                 int nTmpFormat = 0;
   11312           0 :                 status = nc_inq_format(nCdfId, &nTmpFormat);
   11313           0 :                 NCDF_ERR(status);
   11314           0 :                 if (nTmpFormat == NCDF_FORMAT_NC4)
   11315             :                 {
   11316           0 :                     switch (nVarType)
   11317             :                     {
   11318           0 :                         case NC_STRING:
   11319             :                         {
   11320             :                             status =
   11321           0 :                                 nc_put_vara_string(nCdfId, nVarId, start, count,
   11322             :                                                    (const char **)papszValues);
   11323           0 :                             NCDF_ERR(status);
   11324           0 :                             break;
   11325             :                         }
   11326           0 :                         case NC_UBYTE:
   11327             :                         {
   11328             :                             unsigned char *pucTemp =
   11329             :                                 static_cast<unsigned char *>(
   11330           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned char)));
   11331           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11332             :                             {
   11333           0 :                                 char *pszTemp = nullptr;
   11334           0 :                                 pucTemp[i] = static_cast<unsigned char>(
   11335           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11336             :                             }
   11337           0 :                             status = nc_put_vara_uchar(nCdfId, nVarId, start,
   11338             :                                                        count, pucTemp);
   11339           0 :                             NCDF_ERR(status);
   11340           0 :                             CPLFree(pucTemp);
   11341           0 :                             break;
   11342             :                         }
   11343           0 :                         case NC_USHORT:
   11344             :                         {
   11345             :                             unsigned short *pusTemp =
   11346             :                                 static_cast<unsigned short *>(
   11347           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned short)));
   11348           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11349             :                             {
   11350           0 :                                 char *pszTemp = nullptr;
   11351           0 :                                 pusTemp[i] = static_cast<unsigned short>(
   11352           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11353             :                             }
   11354           0 :                             status = nc_put_vara_ushort(nCdfId, nVarId, start,
   11355             :                                                         count, pusTemp);
   11356           0 :                             NCDF_ERR(status);
   11357           0 :                             CPLFree(pusTemp);
   11358           0 :                             break;
   11359             :                         }
   11360           0 :                         case NC_UINT:
   11361             :                         {
   11362             :                             unsigned int *punTemp = static_cast<unsigned int *>(
   11363           0 :                                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11364           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11365             :                             {
   11366           0 :                                 char *pszTemp = nullptr;
   11367           0 :                                 punTemp[i] = static_cast<unsigned int>(
   11368           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11369             :                             }
   11370           0 :                             status = nc_put_vara_uint(nCdfId, nVarId, start,
   11371             :                                                       count, punTemp);
   11372           0 :                             NCDF_ERR(status);
   11373           0 :                             CPLFree(punTemp);
   11374           0 :                             break;
   11375             :                         }
   11376           0 :                         default:
   11377           0 :                             if (papszValues)
   11378           0 :                                 CSLDestroy(papszValues);
   11379           0 :                             return CE_Failure;
   11380             :                             break;
   11381             :                     }
   11382             :                 }
   11383           0 :                 break;
   11384             :             }
   11385             :         }
   11386             :     }
   11387             : 
   11388           8 :     if (papszValues)
   11389           8 :         CSLDestroy(papszValues);
   11390             : 
   11391           8 :     return CE_None;
   11392             : }
   11393             : 
   11394             : /************************************************************************/
   11395             : /*                           GetDefaultNoDataValue()                    */
   11396             : /************************************************************************/
   11397             : 
   11398         188 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
   11399             :                                  bool &bGotNoData)
   11400             : 
   11401             : {
   11402         188 :     int nNoFill = 0;
   11403         188 :     double dfNoData = 0.0;
   11404             : 
   11405         188 :     switch (nVarType)
   11406             :     {
   11407           0 :         case NC_CHAR:
   11408             :         case NC_BYTE:
   11409             :         case NC_UBYTE:
   11410             :             // Don't do default fill-values for bytes, too risky.
   11411             :             // This function should not be called in those cases.
   11412           0 :             CPLAssert(false);
   11413             :             break;
   11414          24 :         case NC_SHORT:
   11415             :         {
   11416          24 :             short nFillVal = 0;
   11417          24 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11418             :                 NC_NOERR)
   11419             :             {
   11420          24 :                 if (!nNoFill)
   11421             :                 {
   11422          23 :                     bGotNoData = true;
   11423          23 :                     dfNoData = nFillVal;
   11424             :                 }
   11425             :             }
   11426             :             else
   11427           0 :                 dfNoData = NC_FILL_SHORT;
   11428          24 :             break;
   11429             :         }
   11430          26 :         case NC_INT:
   11431             :         {
   11432          26 :             int nFillVal = 0;
   11433          26 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11434             :                 NC_NOERR)
   11435             :             {
   11436          26 :                 if (!nNoFill)
   11437             :                 {
   11438          25 :                     bGotNoData = true;
   11439          25 :                     dfNoData = nFillVal;
   11440             :                 }
   11441             :             }
   11442             :             else
   11443           0 :                 dfNoData = NC_FILL_INT;
   11444          26 :             break;
   11445             :         }
   11446          71 :         case NC_FLOAT:
   11447             :         {
   11448          71 :             float fFillVal = 0;
   11449          71 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
   11450             :                 NC_NOERR)
   11451             :             {
   11452          71 :                 if (!nNoFill)
   11453             :                 {
   11454          67 :                     bGotNoData = true;
   11455          67 :                     dfNoData = fFillVal;
   11456             :                 }
   11457             :             }
   11458             :             else
   11459           0 :                 dfNoData = NC_FILL_FLOAT;
   11460          71 :             break;
   11461             :         }
   11462          34 :         case NC_DOUBLE:
   11463             :         {
   11464          34 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
   11465             :                 NC_NOERR)
   11466             :             {
   11467          34 :                 if (!nNoFill)
   11468             :                 {
   11469          34 :                     bGotNoData = true;
   11470             :                 }
   11471             :             }
   11472             :             else
   11473           0 :                 dfNoData = NC_FILL_DOUBLE;
   11474          34 :             break;
   11475             :         }
   11476           7 :         case NC_USHORT:
   11477             :         {
   11478           7 :             unsigned short nFillVal = 0;
   11479           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11480             :                 NC_NOERR)
   11481             :             {
   11482           7 :                 if (!nNoFill)
   11483             :                 {
   11484           7 :                     bGotNoData = true;
   11485           7 :                     dfNoData = nFillVal;
   11486             :                 }
   11487             :             }
   11488             :             else
   11489           0 :                 dfNoData = NC_FILL_USHORT;
   11490           7 :             break;
   11491             :         }
   11492           7 :         case NC_UINT:
   11493             :         {
   11494           7 :             unsigned int nFillVal = 0;
   11495           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11496             :                 NC_NOERR)
   11497             :             {
   11498           7 :                 if (!nNoFill)
   11499             :                 {
   11500           7 :                     bGotNoData = true;
   11501           7 :                     dfNoData = nFillVal;
   11502             :                 }
   11503             :             }
   11504             :             else
   11505           0 :                 dfNoData = NC_FILL_UINT;
   11506           7 :             break;
   11507             :         }
   11508          19 :         default:
   11509          19 :             dfNoData = 0.0;
   11510          19 :             break;
   11511             :     }
   11512             : 
   11513         188 :     return dfNoData;
   11514             : }
   11515             : 
   11516             : /************************************************************************/
   11517             : /*                      NCDFGetDefaultNoDataValueAsInt64()              */
   11518             : /************************************************************************/
   11519             : 
   11520           2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
   11521             :                                          bool &bGotNoData)
   11522             : 
   11523             : {
   11524           2 :     int nNoFill = 0;
   11525           2 :     long long nFillVal = 0;
   11526           2 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11527             :     {
   11528           2 :         if (!nNoFill)
   11529             :         {
   11530           2 :             bGotNoData = true;
   11531           2 :             return static_cast<int64_t>(nFillVal);
   11532             :         }
   11533             :     }
   11534             :     else
   11535           0 :         return static_cast<int64_t>(NC_FILL_INT64);
   11536           0 :     return 0;
   11537             : }
   11538             : 
   11539             : /************************************************************************/
   11540             : /*                     NCDFGetDefaultNoDataValueAsUInt64()              */
   11541             : /************************************************************************/
   11542             : 
   11543           1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
   11544             :                                            bool &bGotNoData)
   11545             : 
   11546             : {
   11547           1 :     int nNoFill = 0;
   11548           1 :     unsigned long long nFillVal = 0;
   11549           1 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11550             :     {
   11551           1 :         if (!nNoFill)
   11552             :         {
   11553           1 :             bGotNoData = true;
   11554           1 :             return static_cast<uint64_t>(nFillVal);
   11555             :         }
   11556             :     }
   11557             :     else
   11558           0 :         return static_cast<uint64_t>(NC_FILL_UINT64);
   11559           0 :     return 0;
   11560             : }
   11561             : 
   11562       11087 : static int NCDFDoesVarContainAttribVal(int nCdfId,
   11563             :                                        const char *const *papszAttribNames,
   11564             :                                        const char *const *papszAttribValues,
   11565             :                                        int nVarId, const char *pszVarName,
   11566             :                                        bool bStrict = true)
   11567             : {
   11568       11087 :     if (nVarId == -1 && pszVarName != nullptr)
   11569        8116 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11570             : 
   11571       11087 :     if (nVarId == -1)
   11572         878 :         return -1;
   11573             : 
   11574       10209 :     bool bFound = false;
   11575       47648 :     for (int i = 0; !bFound && papszAttribNames != nullptr &&
   11576       45390 :                     papszAttribNames[i] != nullptr;
   11577             :          i++)
   11578             :     {
   11579       37439 :         char *pszTemp = nullptr;
   11580       37439 :         if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
   11581       53756 :                 CE_None &&
   11582       16317 :             pszTemp != nullptr)
   11583             :         {
   11584       16317 :             if (bStrict)
   11585             :             {
   11586       16317 :                 if (EQUAL(pszTemp, papszAttribValues[i]))
   11587        2258 :                     bFound = true;
   11588             :             }
   11589             :             else
   11590             :             {
   11591           0 :                 if (EQUALN(pszTemp, papszAttribValues[i],
   11592             :                            strlen(papszAttribValues[i])))
   11593           0 :                     bFound = true;
   11594             :             }
   11595       16317 :             CPLFree(pszTemp);
   11596             :         }
   11597             :     }
   11598       10209 :     return bFound;
   11599             : }
   11600             : 
   11601        1958 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
   11602             :                                         const char *const *papszAttribValues,
   11603             :                                         int nVarId, const char *pszVarName,
   11604             :                                         int bStrict = true)
   11605             : {
   11606        1958 :     if (nVarId == -1 && pszVarName != nullptr)
   11607        1575 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11608             : 
   11609        1958 :     if (nVarId == -1)
   11610           0 :         return -1;
   11611             : 
   11612        1958 :     bool bFound = false;
   11613        1958 :     char *pszTemp = nullptr;
   11614        2337 :     if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
   11615         379 :         pszTemp == nullptr)
   11616        1579 :         return FALSE;
   11617             : 
   11618        7703 :     for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
   11619             :     {
   11620        7324 :         if (bStrict)
   11621             :         {
   11622        7296 :             if (EQUAL(pszTemp, papszAttribValues[i]))
   11623          31 :                 bFound = true;
   11624             :         }
   11625             :         else
   11626             :         {
   11627          28 :             if (EQUALN(pszTemp, papszAttribValues[i],
   11628             :                        strlen(papszAttribValues[i])))
   11629           0 :                 bFound = true;
   11630             :         }
   11631             :     }
   11632             : 
   11633         379 :     CPLFree(pszTemp);
   11634             : 
   11635         379 :     return bFound;
   11636             : }
   11637             : 
   11638         876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
   11639             : {
   11640         876 :     if (papszName == nullptr || EQUAL(papszName, ""))
   11641           0 :         return false;
   11642             : 
   11643        2392 :     for (int i = 0; papszValues && papszValues[i]; ++i)
   11644             :     {
   11645        1636 :         if (EQUAL(papszName, papszValues[i]))
   11646         120 :             return true;
   11647             :     }
   11648             : 
   11649         756 :     return false;
   11650             : }
   11651             : 
   11652             : // Test that a variable is longitude/latitude coordinate,
   11653             : // following CF 4.1 and 4.2.
   11654        3782 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
   11655             : {
   11656             :     // Check for matching attributes.
   11657        3782 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
   11658             :                                            papszCFLongitudeAttribValues, nVarId,
   11659             :                                            pszVarName);
   11660             :     // If not found using attributes then check using var name
   11661             :     // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
   11662        3782 :     if (bVal == -1)
   11663             :     {
   11664         280 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11665             :                    "STRICT"))
   11666         280 :             bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
   11667             :         else
   11668           0 :             bVal = FALSE;
   11669             :     }
   11670        3502 :     else if (bVal)
   11671             :     {
   11672             :         // Check that the units is not 'm' or '1'. See #6759
   11673         789 :         char *pszTemp = nullptr;
   11674        1169 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11675         380 :             pszTemp != nullptr)
   11676             :         {
   11677         380 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11678          97 :                 bVal = false;
   11679         380 :             CPLFree(pszTemp);
   11680             :         }
   11681             :     }
   11682             : 
   11683        3782 :     return CPL_TO_BOOL(bVal);
   11684             : }
   11685             : 
   11686        2167 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
   11687             : {
   11688        2167 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
   11689             :                                            papszCFLatitudeAttribValues, nVarId,
   11690             :                                            pszVarName);
   11691        2167 :     if (bVal == -1)
   11692             :     {
   11693         163 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11694             :                    "STRICT"))
   11695         163 :             bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
   11696             :         else
   11697           0 :             bVal = FALSE;
   11698             :     }
   11699        2004 :     else if (bVal)
   11700             :     {
   11701             :         // Check that the units is not 'm' or '1'. See #6759
   11702         543 :         char *pszTemp = nullptr;
   11703         685 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11704         142 :             pszTemp != nullptr)
   11705             :         {
   11706         142 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11707          36 :                 bVal = false;
   11708         142 :             CPLFree(pszTemp);
   11709             :         }
   11710             :     }
   11711             : 
   11712        2167 :     return CPL_TO_BOOL(bVal);
   11713             : }
   11714             : 
   11715        2293 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
   11716             : {
   11717        2293 :     int bVal = NCDFDoesVarContainAttribVal(
   11718             :         nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
   11719             :         nVarId, pszVarName);
   11720        2293 :     if (bVal == -1)
   11721             :     {
   11722         274 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11723             :                    "STRICT"))
   11724         274 :             bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
   11725             :         else
   11726           0 :             bVal = FALSE;
   11727             :     }
   11728        2019 :     else if (bVal)
   11729             :     {
   11730             :         // Check that the units is not '1'
   11731         374 :         char *pszTemp = nullptr;
   11732         533 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11733         159 :             pszTemp != nullptr)
   11734             :         {
   11735         159 :             if (EQUAL(pszTemp, "1"))
   11736           5 :                 bVal = false;
   11737         159 :             CPLFree(pszTemp);
   11738             :         }
   11739             :     }
   11740             : 
   11741        2293 :     return CPL_TO_BOOL(bVal);
   11742             : }
   11743             : 
   11744        1623 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
   11745             : {
   11746        1623 :     int bVal = NCDFDoesVarContainAttribVal(
   11747             :         nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
   11748             :         nVarId, pszVarName);
   11749        1623 :     if (bVal == -1)
   11750             :     {
   11751         159 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11752             :                    "STRICT"))
   11753         159 :             bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
   11754             :         else
   11755           0 :             bVal = FALSE;
   11756             :     }
   11757        1464 :     else if (bVal)
   11758             :     {
   11759             :         // Check that the units is not '1'
   11760         373 :         char *pszTemp = nullptr;
   11761         531 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11762         158 :             pszTemp != nullptr)
   11763             :         {
   11764         158 :             if (EQUAL(pszTemp, "1"))
   11765           5 :                 bVal = false;
   11766         158 :             CPLFree(pszTemp);
   11767             :         }
   11768             :     }
   11769             : 
   11770        1623 :     return CPL_TO_BOOL(bVal);
   11771             : }
   11772             : 
   11773             : /* test that a variable is a vertical coordinate, following CF 4.3 */
   11774        1020 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
   11775             : {
   11776             :     /* check for matching attributes */
   11777        1020 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
   11778             :                                     papszCFVerticalAttribValues, nVarId,
   11779        1020 :                                     pszVarName))
   11780          72 :         return true;
   11781             :     /* check for matching units */
   11782         948 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11783             :                                           papszCFVerticalUnitsValues, nVarId,
   11784         948 :                                           pszVarName))
   11785          31 :         return true;
   11786             :     /* check for matching standard name */
   11787         917 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
   11788             :                                           papszCFVerticalStandardNameValues,
   11789         917 :                                           nVarId, pszVarName))
   11790           0 :         return true;
   11791             :     else
   11792         917 :         return false;
   11793             : }
   11794             : 
   11795             : /* test that a variable is a time coordinate, following CF 4.4 */
   11796         202 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
   11797             : {
   11798             :     /* check for matching attributes */
   11799         202 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
   11800             :                                     papszCFTimeAttribValues, nVarId,
   11801         202 :                                     pszVarName))
   11802         109 :         return true;
   11803             :     /* check for matching units */
   11804          93 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11805             :                                           papszCFTimeUnitsValues, nVarId,
   11806          93 :                                           pszVarName, false))
   11807           0 :         return true;
   11808             :     else
   11809          93 :         return false;
   11810             : }
   11811             : 
   11812             : // Parse a string, and return as a string list.
   11813             : // If it an array of the form {a,b}, then tokenize it.
   11814             : // Otherwise, return a copy.
   11815         188 : static char **NCDFTokenizeArray(const char *pszValue)
   11816             : {
   11817         188 :     if (pszValue == nullptr || EQUAL(pszValue, ""))
   11818          53 :         return nullptr;
   11819             : 
   11820         135 :     char **papszValues = nullptr;
   11821         135 :     const int nLen = static_cast<int>(strlen(pszValue));
   11822             : 
   11823         135 :     if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
   11824             :     {
   11825          41 :         char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
   11826          41 :         strncpy(pszTemp, pszValue + 1, nLen - 2);
   11827          41 :         pszTemp[nLen - 2] = '\0';
   11828          41 :         papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
   11829          41 :         CPLFree(pszTemp);
   11830             :     }
   11831             :     else
   11832             :     {
   11833          94 :         papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
   11834          94 :         papszValues[0] = CPLStrdup(pszValue);
   11835          94 :         papszValues[1] = nullptr;
   11836             :     }
   11837             : 
   11838         135 :     return papszValues;
   11839             : }
   11840             : 
   11841             : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
   11842             : // Leading slash is optional.
   11843         412 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
   11844             :                                  int *pnGroupId, int *pnVarId)
   11845             : {
   11846         412 :     *pnGroupId = -1;
   11847         412 :     *pnVarId = -1;
   11848             : 
   11849             :     // Open group.
   11850             :     char *pszGroupFullName =
   11851         412 :         CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
   11852             :     // Add a leading slash if needed.
   11853         412 :     if (pszGroupFullName[0] != '/')
   11854             :     {
   11855         396 :         char *old = pszGroupFullName;
   11856         396 :         pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
   11857         396 :         CPLFree(old);
   11858             :     }
   11859             :     // Detect root group.
   11860         412 :     if (EQUAL(pszGroupFullName, "/"))
   11861             :     {
   11862         396 :         *pnGroupId = nCdfId;
   11863         396 :         CPLFree(pszGroupFullName);
   11864             :     }
   11865             :     else
   11866             :     {
   11867          16 :         int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
   11868          16 :         CPLFree(pszGroupFullName);
   11869          16 :         NCDF_ERR_RET(status);
   11870             :     }
   11871             : 
   11872             :     // Open var.
   11873         412 :     const char *pszVarName = CPLGetFilename(pszSubdatasetName);
   11874         412 :     NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
   11875             : 
   11876         412 :     return CE_None;
   11877             : }
   11878             : 
   11879             : // Get all dimensions visible from a given NetCDF (or group) ID and any of
   11880             : // its parents.
   11881         352 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
   11882             : {
   11883         352 :     int nDims = 0;
   11884         352 :     int *panDimIds = nullptr;
   11885         352 :     NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
   11886             : 
   11887         352 :     panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
   11888             : 
   11889         352 :     int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
   11890         352 :     if (status != NC_NOERR)
   11891           0 :         CPLFree(panDimIds);
   11892         352 :     NCDF_ERR_RET(status);
   11893             : 
   11894         352 :     *pnDims = nDims;
   11895         352 :     *ppanDimIds = panDimIds;
   11896             : 
   11897         352 :     return CE_None;
   11898             : }
   11899             : 
   11900             : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
   11901             : // Consider only direct children, does not get children of children.
   11902        3051 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
   11903             :                                int **ppanSubGroupIds)
   11904             : {
   11905        3051 :     *pnSubGroups = 0;
   11906        3051 :     *ppanSubGroupIds = nullptr;
   11907             : 
   11908             :     int nSubGroups;
   11909        3051 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
   11910             :     int *panSubGroupIds =
   11911        3051 :         static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
   11912        3051 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
   11913        3051 :     *pnSubGroups = nSubGroups;
   11914        3051 :     *ppanSubGroupIds = panSubGroupIds;
   11915             : 
   11916        3051 :     return CE_None;
   11917             : }
   11918             : 
   11919             : // Get the full name of a given NetCDF (or group) ID
   11920             : // (e.g. /group1/group2/.../groupn).
   11921             : // bNC3Compat remove the leading slash for top-level variables for
   11922             : // backward compatibility (top-level variables are the ones in the root group).
   11923       15474 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
   11924             :                                    bool bNC3Compat)
   11925             : {
   11926       15474 :     *ppszFullName = nullptr;
   11927             : 
   11928             :     size_t nFullNameLen;
   11929       15474 :     NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
   11930       15474 :     *ppszFullName =
   11931       15474 :         static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
   11932       15474 :     int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
   11933       15474 :     if (status != NC_NOERR)
   11934             :     {
   11935           0 :         CPLFree(*ppszFullName);
   11936           0 :         *ppszFullName = nullptr;
   11937           0 :         NCDF_ERR_RET(status);
   11938             :     }
   11939             : 
   11940       15474 :     if (bNC3Compat && EQUAL(*ppszFullName, "/"))
   11941        7959 :         (*ppszFullName)[0] = '\0';
   11942             : 
   11943       15474 :     return CE_None;
   11944             : }
   11945             : 
   11946        7325 : CPLString NCDFGetGroupFullName(int nGroupId)
   11947             : {
   11948        7325 :     char *pszFullname = nullptr;
   11949        7325 :     NCDFGetGroupFullName(nGroupId, &pszFullname, false);
   11950        7325 :     CPLString osRet(pszFullname ? pszFullname : "");
   11951        7325 :     CPLFree(pszFullname);
   11952       14650 :     return osRet;
   11953             : }
   11954             : 
   11955             : // Get the full name of a given NetCDF variable ID
   11956             : // (e.g. /group1/group2/.../groupn/var).
   11957             : // Handle also NC_GLOBAL as nVarId.
   11958             : // bNC3Compat remove the leading slash for top-level variables for
   11959             : // backward compatibility (top-level variables are the ones in the root group).
   11960        8100 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
   11961             :                                  bool bNC3Compat)
   11962             : {
   11963        8100 :     *ppszFullName = nullptr;
   11964        8100 :     char *pszGroupFullName = nullptr;
   11965        8100 :     ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
   11966             :     char szVarName[NC_MAX_NAME + 1];
   11967        8100 :     if (nVarId == NC_GLOBAL)
   11968             :     {
   11969        1093 :         strcpy(szVarName, "NC_GLOBAL");
   11970             :     }
   11971             :     else
   11972             :     {
   11973        7007 :         int status = nc_inq_varname(nGroupId, nVarId, szVarName);
   11974        7007 :         if (status != NC_NOERR)
   11975             :         {
   11976           0 :             CPLFree(pszGroupFullName);
   11977           0 :             NCDF_ERR_RET(status);
   11978             :         }
   11979             :     }
   11980        8100 :     const char *pszSep = "/";
   11981        8100 :     if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
   11982        7912 :         pszSep = "";
   11983        8100 :     *ppszFullName =
   11984        8100 :         CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
   11985        8100 :     CPLFree(pszGroupFullName);
   11986        8100 :     return CE_None;
   11987             : }
   11988             : 
   11989             : // Get the NetCDF root group ID of a given group ID.
   11990           0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
   11991             : {
   11992           0 :     *pnRootGroupId = -1;
   11993             :     // Recurse on parent group.
   11994             :     int nParentGroupId;
   11995           0 :     int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
   11996           0 :     if (status == NC_NOERR)
   11997           0 :         return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
   11998           0 :     else if (status != NC_ENOGRP)
   11999           0 :         NCDF_ERR_RET(status);
   12000             :     else  // No more parent group.
   12001             :     {
   12002           0 :         *pnRootGroupId = nStartGroupId;
   12003             :     }
   12004             : 
   12005           0 :     return CE_None;
   12006             : }
   12007             : 
   12008             : // Implementation of NCDFResolveVar/Att.
   12009       13182 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
   12010             :                               const char *pszAtt, int *pnGroupId, int *pnId,
   12011             :                               bool bMandatory)
   12012             : {
   12013       13182 :     if (!pszVar && !pszAtt)
   12014             :     {
   12015           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   12016             :                  "pszVar and pszAtt NCDFResolveElem() args are both null.");
   12017           0 :         return CE_Failure;
   12018             :     }
   12019             : 
   12020             :     enum
   12021             :     {
   12022             :         NCRM_PARENT,
   12023             :         NCRM_WIDTH_WISE
   12024       13182 :     } eNCResolveMode = NCRM_PARENT;
   12025             : 
   12026       26364 :     std::queue<int> aoQueueGroupIdsToVisit;
   12027       13182 :     aoQueueGroupIdsToVisit.push(nStartGroupId);
   12028             : 
   12029       14851 :     while (!aoQueueGroupIdsToVisit.empty())
   12030             :     {
   12031             :         // Get the first group of the FIFO queue.
   12032       13329 :         *pnGroupId = aoQueueGroupIdsToVisit.front();
   12033       13329 :         aoQueueGroupIdsToVisit.pop();
   12034             : 
   12035             :         // Look if this group contains the searched element.
   12036             :         int status;
   12037       13329 :         if (pszVar)
   12038       13110 :             status = nc_inq_varid(*pnGroupId, pszVar, pnId);
   12039             :         else  // pszAtt != nullptr.
   12040         219 :             status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
   12041             : 
   12042       13329 :         if (status == NC_NOERR)
   12043             :         {
   12044       11660 :             return CE_None;
   12045             :         }
   12046        1669 :         else if ((pszVar && status != NC_ENOTVAR) ||
   12047         216 :                  (pszAtt && status != NC_ENOTATT))
   12048             :         {
   12049           0 :             NCDF_ERR(status);
   12050             :         }
   12051             :         // Element not found, in NC4 case we must search in other groups
   12052             :         // following the CF logic.
   12053             : 
   12054             :         // The first resolve mode consists to search on parent groups.
   12055        1669 :         if (eNCResolveMode == NCRM_PARENT)
   12056             :         {
   12057        1569 :             int nParentGroupId = -1;
   12058        1569 :             int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
   12059        1569 :             if (status2 == NC_NOERR)
   12060          45 :                 aoQueueGroupIdsToVisit.push(nParentGroupId);
   12061        1524 :             else if (status2 != NC_ENOGRP)
   12062           0 :                 NCDF_ERR(status2);
   12063        1524 :             else if (pszVar)
   12064             :                 // When resolving a variable, if there is no more
   12065             :                 // parent group then we switch to width-wise search mode
   12066             :                 // starting from the latest found parent group.
   12067        1311 :                 eNCResolveMode = NCRM_WIDTH_WISE;
   12068             :         }
   12069             : 
   12070             :         // The second resolve mode is a width-wise search.
   12071        1669 :         if (eNCResolveMode == NCRM_WIDTH_WISE)
   12072             :         {
   12073             :             // Enqueue all direct sub-groups.
   12074        1411 :             int nSubGroups = 0;
   12075        1411 :             int *panSubGroupIds = nullptr;
   12076        1411 :             NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
   12077        1513 :             for (int i = 0; i < nSubGroups; i++)
   12078         102 :                 aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
   12079        1411 :             CPLFree(panSubGroupIds);
   12080             :         }
   12081             :     }
   12082             : 
   12083        1522 :     if (bMandatory)
   12084             :     {
   12085           0 :         char *pszStartGroupFullName = nullptr;
   12086           0 :         NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
   12087           0 :         CPLError(CE_Failure, CPLE_AppDefined,
   12088             :                  "Cannot resolve mandatory %s %s from group %s",
   12089             :                  (pszVar ? pszVar : pszAtt),
   12090             :                  (pszVar ? "variable" : "attribute"),
   12091           0 :                  (pszStartGroupFullName ? pszStartGroupFullName : ""));
   12092           0 :         CPLFree(pszStartGroupFullName);
   12093             :     }
   12094             : 
   12095        1522 :     *pnGroupId = -1;
   12096        1522 :     *pnId = -1;
   12097        1522 :     return CE_Failure;
   12098             : }
   12099             : 
   12100             : // Resolve a variable name from a given starting group following the CF logic:
   12101             : // - if var name is an absolute path then directly open it
   12102             : // - first search in the starting group and its parent groups
   12103             : // - then if there is no more parent group we switch to a width-wise search
   12104             : //   mode starting from the latest found parent group.
   12105             : // The full CF logic is described here:
   12106             : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
   12107             : // If bMandatory then print an error if resolving fails.
   12108             : // TODO: implement support of relative paths.
   12109             : // TODO: to follow strictly the CF logic, when searching for a coordinate
   12110             : //       variable, we must stop the parent search mode once the corresponding
   12111             : //       dimension is found and start the width-wise search from this group.
   12112             : // TODO: to follow strictly the CF logic, when searching in width-wise mode
   12113             : //       we should skip every groups already visited during the parent
   12114             : //       search mode (but revisiting them should have no impact so we could
   12115             : //       let as it is if it is simpler...)
   12116             : // TODO: CF specifies that the width-wise search order is "left-to-right" so
   12117             : //       maybe we must sort sibling groups alphabetically? but maybe not
   12118             : //       necessary if nc_inq_grps() already sort them?
   12119       12966 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
   12120             :                       int *pnVarId, bool bMandatory)
   12121             : {
   12122       12966 :     *pnGroupId = -1;
   12123       12966 :     *pnVarId = -1;
   12124       12966 :     int nGroupId = nStartGroupId, nVarId;
   12125       12966 :     if (pszVar[0] == '/')
   12126             :     {
   12127             :         // This is an absolute path: we can open the var directly.
   12128             :         int nRootGroupId;
   12129           0 :         ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
   12130           0 :         ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
   12131             :     }
   12132             :     else
   12133             :     {
   12134             :         // We have to search the variable following the CF logic.
   12135       12966 :         ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
   12136             :                                 &nVarId, bMandatory));
   12137             :     }
   12138       11657 :     *pnGroupId = nGroupId;
   12139       11657 :     *pnVarId = nVarId;
   12140       11657 :     return CE_None;
   12141             : }
   12142             : 
   12143             : // Like NCDFResolveVar but returns directly the var full name.
   12144        1380 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
   12145             :                                      char **ppszFullName, bool bMandatory)
   12146             : {
   12147        1380 :     *ppszFullName = nullptr;
   12148             :     int nGroupId, nVarId;
   12149        1380 :     ERR_RET(
   12150             :         NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
   12151        1354 :     return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
   12152             : }
   12153             : 
   12154             : // Like NCDFResolveVar but resolves an attribute instead a variable and
   12155             : // returns its integer value.
   12156             : // Only GLOBAL attributes are supported for the moment.
   12157         216 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
   12158             :                                 const char *pszAtt, int *pnAtt, bool bMandatory)
   12159             : {
   12160         216 :     int nGroupId = nStartGroupId, nAttId = nStartVarId;
   12161         216 :     ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
   12162             :                             bMandatory));
   12163           3 :     NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
   12164           3 :     return CE_None;
   12165             : }
   12166             : 
   12167             : // Filter variables to keep only valid 2+D raster bands and vector fields in
   12168             : // a given a NetCDF (or group) ID and its sub-groups.
   12169             : // Coordinate or boundary variables are ignored.
   12170             : // It also creates corresponding vector layers.
   12171         525 : CPLErr netCDFDataset::FilterVars(
   12172             :     int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
   12173             :     int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
   12174             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
   12175             :         &oMap2DDimsToGroupAndVar)
   12176             : {
   12177         525 :     int nVars = 0;
   12178         525 :     int nRasterVars = 0;
   12179         525 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12180             : 
   12181        1050 :     std::vector<int> anPotentialVectorVarID;
   12182             :     // oMapDimIdToCount[x] = number of times dim x is the first dimension of
   12183             :     // potential vector variables
   12184        1050 :     std::map<int, int> oMapDimIdToCount;
   12185         525 :     int nVarXId = -1;
   12186         525 :     int nVarYId = -1;
   12187         525 :     int nVarZId = -1;
   12188         525 :     int nVarTimeId = -1;
   12189         525 :     int nVarTimeDimId = -1;
   12190         525 :     bool bIsVectorOnly = true;
   12191         525 :     int nProfileDimId = -1;
   12192         525 :     int nParentIndexVarID = -1;
   12193             : 
   12194        3217 :     for (int v = 0; v < nVars; v++)
   12195             :     {
   12196             :         int nVarDims;
   12197        2692 :         NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
   12198             :         // Should we ignore this variable?
   12199             :         char szTemp[NC_MAX_NAME + 1];
   12200        2692 :         szTemp[0] = '\0';
   12201        2692 :         NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
   12202             : 
   12203        2692 :         if (strstr(szTemp, "_node_coordinates") ||
   12204        2692 :             strstr(szTemp, "_node_count"))
   12205             :         {
   12206             :             // Ignore CF-1.8 Simple Geometries helper variables
   12207          69 :             continue;
   12208             :         }
   12209             : 
   12210        3917 :         if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
   12211        1294 :                               NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
   12212             :         {
   12213         358 :             nVarXId = v;
   12214             :         }
   12215        3201 :         else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
   12216         936 :                                    NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
   12217             :         {
   12218         358 :             nVarYId = v;
   12219             :         }
   12220        1907 :         else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
   12221             :         {
   12222          78 :             nVarZId = v;
   12223             :         }
   12224             :         else
   12225             :         {
   12226        1829 :             char *pszVarFullName = nullptr;
   12227        1829 :             CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
   12228        1829 :             if (eErr != CE_None)
   12229             :             {
   12230           0 :                 CPLFree(pszVarFullName);
   12231           0 :                 continue;
   12232             :             }
   12233             :             bool bIgnoreVar =
   12234        1829 :                 (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
   12235        1829 :             CPLFree(pszVarFullName);
   12236        1829 :             if (bIgnoreVar)
   12237             :             {
   12238         102 :                 if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
   12239             :                 {
   12240          11 :                     nVarTimeId = v;
   12241          11 :                     nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
   12242             :                 }
   12243          91 :                 else if (nVarDims > 1)
   12244             :                 {
   12245          87 :                     (*pnIgnoredVars)++;
   12246          87 :                     CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
   12247             :                              szTemp);
   12248             :                 }
   12249             :             }
   12250             :             // Only accept 2+D vars.
   12251        1727 :             else if (nVarDims >= 2)
   12252             :             {
   12253         704 :                 bool bRasterCandidate = true;
   12254             :                 // Identify variables that might be vector variables
   12255         704 :                 if (nVarDims == 2)
   12256             :                 {
   12257         630 :                     int anDimIds[2] = {-1, -1};
   12258         630 :                     nc_inq_vardimid(nCdfId, v, anDimIds);
   12259             : 
   12260         630 :                     nc_type vartype = NC_NAT;
   12261         630 :                     nc_inq_vartype(nCdfId, v, &vartype);
   12262             : 
   12263             :                     char szDimNameFirst[NC_MAX_NAME + 1];
   12264             :                     char szDimNameSecond[NC_MAX_NAME + 1];
   12265         630 :                     szDimNameFirst[0] = '\0';
   12266         630 :                     szDimNameSecond[0] = '\0';
   12267        1417 :                     if (vartype == NC_CHAR &&
   12268         157 :                         nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
   12269         157 :                             NC_NOERR &&
   12270         157 :                         nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
   12271         157 :                             NC_NOERR &&
   12272         157 :                         !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
   12273         157 :                         !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
   12274         944 :                         !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
   12275         157 :                         !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
   12276             :                     {
   12277         157 :                         anPotentialVectorVarID.push_back(v);
   12278         157 :                         oMapDimIdToCount[anDimIds[0]]++;
   12279         157 :                         if (strstr(szDimNameSecond, "_max_width"))
   12280             :                         {
   12281         127 :                             bRasterCandidate = false;
   12282             :                         }
   12283             :                         else
   12284             :                         {
   12285          30 :                             std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12286          30 :                                                     vartype};
   12287          30 :                             oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12288          30 :                                 std::pair(nCdfId, v));
   12289             :                         }
   12290             :                     }
   12291             :                     else
   12292             :                     {
   12293         473 :                         std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12294         473 :                                                 vartype};
   12295         473 :                         oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12296         473 :                             std::pair(nCdfId, v));
   12297         473 :                         bIsVectorOnly = false;
   12298             :                     }
   12299             :                 }
   12300             :                 else
   12301             :                 {
   12302          74 :                     bIsVectorOnly = false;
   12303             :                 }
   12304         704 :                 if (bKeepRasters && bRasterCandidate)
   12305             :                 {
   12306         548 :                     *pnGroupId = nCdfId;
   12307         548 :                     *pnVarId = v;
   12308         548 :                     nRasterVars++;
   12309             :                 }
   12310             :             }
   12311        1023 :             else if (nVarDims == 1)
   12312             :             {
   12313         728 :                 nc_type atttype = NC_NAT;
   12314         728 :                 size_t attlen = 0;
   12315         728 :                 if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
   12316          14 :                                &attlen) == NC_NOERR &&
   12317         728 :                     atttype == NC_CHAR && attlen < NC_MAX_NAME)
   12318             :                 {
   12319             :                     char szInstanceDimension[NC_MAX_NAME + 1];
   12320          14 :                     if (nc_get_att_text(nCdfId, v, "instance_dimension",
   12321          14 :                                         szInstanceDimension) == NC_NOERR)
   12322             :                     {
   12323          14 :                         szInstanceDimension[attlen] = 0;
   12324          14 :                         int status = nc_inq_dimid(nCdfId, szInstanceDimension,
   12325             :                                                   &nProfileDimId);
   12326          14 :                         if (status == NC_NOERR)
   12327          14 :                             nParentIndexVarID = v;
   12328             :                         else
   12329           0 :                             nProfileDimId = -1;
   12330          14 :                         if (status == NC_EBADDIM)
   12331           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
   12332             :                                      "Attribute instance_dimension='%s' refers "
   12333             :                                      "to a non existing dimension",
   12334             :                                      szInstanceDimension);
   12335             :                         else
   12336          14 :                             NCDF_ERR(status);
   12337             :                     }
   12338             :                 }
   12339         728 :                 if (v != nParentIndexVarID)
   12340             :                 {
   12341         714 :                     anPotentialVectorVarID.push_back(v);
   12342         714 :                     int nDimId = -1;
   12343         714 :                     nc_inq_vardimid(nCdfId, v, &nDimId);
   12344         714 :                     oMapDimIdToCount[nDimId]++;
   12345             :                 }
   12346             :             }
   12347             :         }
   12348             :     }
   12349             : 
   12350             :     // If we are opened in raster-only mode and that there are only 1D or 2D
   12351             :     // variables and that the 2D variables have no X/Y dim, and all
   12352             :     // variables refer to the same main dimension (or 2 dimensions for
   12353             :     // featureType=profile), then it is a pure vector dataset
   12354             :     CPLString osFeatureType(
   12355         525 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
   12356         414 :     if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
   12357         939 :         !anPotentialVectorVarID.empty() &&
   12358           0 :         (oMapDimIdToCount.size() == 1 ||
   12359           0 :          (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
   12360           0 :           nProfileDimId >= 0)))
   12361             :     {
   12362           0 :         anPotentialVectorVarID.resize(0);
   12363             :     }
   12364             :     else
   12365             :     {
   12366         525 :         *pnRasterVars += nRasterVars;
   12367             :     }
   12368             : 
   12369         525 :     if (!anPotentialVectorVarID.empty() && bKeepVectors)
   12370             :     {
   12371             :         // Take the dimension that is referenced the most times.
   12372          64 :         if (!(oMapDimIdToCount.size() == 1 ||
   12373          27 :               (EQUAL(osFeatureType, "profile") &&
   12374          26 :                oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
   12375             :         {
   12376           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12377             :                      "The dataset has several variables that could be "
   12378             :                      "identified as vector fields, but not all share the same "
   12379             :                      "primary dimension. Consequently they will be ignored.");
   12380             :         }
   12381             :         else
   12382             :         {
   12383          50 :             if (nVarTimeId >= 0 &&
   12384          50 :                 oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
   12385             :             {
   12386           1 :                 anPotentialVectorVarID.push_back(nVarTimeId);
   12387             :             }
   12388          49 :             CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
   12389             :                                   oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
   12390             :                                   nProfileDimId, nParentIndexVarID,
   12391             :                                   bKeepRasters);
   12392             :         }
   12393             :     }
   12394             : 
   12395             :     // Recurse on sub-groups.
   12396         525 :     int nSubGroups = 0;
   12397         525 :     int *panSubGroupIds = nullptr;
   12398         525 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12399         558 :     for (int i = 0; i < nSubGroups; i++)
   12400             :     {
   12401          33 :         FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
   12402             :                    papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
   12403             :                    pnIgnoredVars, oMap2DDimsToGroupAndVar);
   12404             :     }
   12405         525 :     CPLFree(panSubGroupIds);
   12406             : 
   12407         525 :     return CE_None;
   12408             : }
   12409             : 
   12410             : // Create vector layers from given potentially identified vector variables
   12411             : // resulting from the scanning of a NetCDF (or group) ID.
   12412          49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
   12413             :     int nCdfId, const CPLString &osFeatureType,
   12414             :     const std::vector<int> &anPotentialVectorVarID,
   12415             :     const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
   12416             :     int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
   12417             : {
   12418          49 :     char *pszGroupName = nullptr;
   12419          49 :     NCDFGetGroupFullName(nCdfId, &pszGroupName);
   12420          49 :     if (pszGroupName == nullptr || pszGroupName[0] == '\0')
   12421             :     {
   12422          47 :         CPLFree(pszGroupName);
   12423          47 :         pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
   12424             :     }
   12425          49 :     OGRwkbGeometryType eGType = wkbUnknown;
   12426             :     CPLString osLayerName = CSLFetchNameValueDef(
   12427          98 :         papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
   12428          49 :     CPLFree(pszGroupName);
   12429          49 :     papszMetadata =
   12430          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
   12431             : 
   12432          49 :     if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
   12433             :     {
   12434          33 :         papszMetadata =
   12435          33 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
   12436          33 :         eGType = wkbPoint;
   12437             :     }
   12438             : 
   12439             :     const char *pszLayerType =
   12440          49 :         CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
   12441          49 :     if (pszLayerType != nullptr)
   12442             :     {
   12443           9 :         eGType = OGRFromOGCGeomType(pszLayerType);
   12444           9 :         papszMetadata =
   12445           9 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
   12446             :     }
   12447             : 
   12448             :     CPLString osGeometryField =
   12449          98 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
   12450          49 :     papszMetadata =
   12451          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
   12452             : 
   12453          49 :     int nFirstVarId = -1;
   12454          49 :     int nVectorDim = oMapDimIdToCount.rbegin()->first;
   12455          49 :     if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
   12456             :     {
   12457          13 :         if (nVectorDim == nProfileDimId)
   12458           0 :             nVectorDim = oMapDimIdToCount.begin()->first;
   12459             :     }
   12460             :     else
   12461             :     {
   12462          36 :         nProfileDimId = -1;
   12463             :     }
   12464          62 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12465             :     {
   12466          62 :         int anDimIds[2] = {-1, -1};
   12467          62 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12468          62 :         if (nVectorDim == anDimIds[0])
   12469             :         {
   12470          49 :             nFirstVarId = anPotentialVectorVarID[j];
   12471          49 :             break;
   12472             :         }
   12473             :     }
   12474             : 
   12475             :     // In case where coordinates are explicitly specified for one of the
   12476             :     // field/variable, use them in priority over the ones that might have been
   12477             :     // identified above.
   12478          49 :     char *pszCoordinates = nullptr;
   12479          49 :     if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
   12480             :         CE_None)
   12481             :     {
   12482          34 :         char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
   12483          34 :         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
   12484             :              i++)
   12485             :         {
   12486           0 :             if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
   12487           0 :                 NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
   12488             :             {
   12489           0 :                 nVarXId = -1;
   12490           0 :                 CPL_IGNORE_RET_VAL(
   12491           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
   12492             :             }
   12493           0 :             else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
   12494           0 :                      NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
   12495             :             {
   12496           0 :                 nVarYId = -1;
   12497           0 :                 CPL_IGNORE_RET_VAL(
   12498           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
   12499             :             }
   12500           0 :             else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
   12501             :             {
   12502           0 :                 nVarZId = -1;
   12503           0 :                 CPL_IGNORE_RET_VAL(
   12504           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
   12505             :             }
   12506             :         }
   12507          34 :         CSLDestroy(papszTokens);
   12508             :     }
   12509          49 :     CPLFree(pszCoordinates);
   12510             : 
   12511             :     // Check that the X,Y,Z vars share 1D and share the same dimension as
   12512             :     // attribute variables.
   12513          49 :     if (nVarXId >= 0 && nVarYId >= 0)
   12514             :     {
   12515          38 :         int nVarDimCount = -1;
   12516          38 :         int nVarDimId = -1;
   12517          38 :         if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
   12518          38 :             nVarDimCount != 1 ||
   12519          38 :             nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
   12520          38 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
   12521          35 :             nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
   12522          35 :             nVarDimCount != 1 ||
   12523         111 :             nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
   12524          35 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
   12525             :         {
   12526           3 :             nVarXId = nVarYId = -1;
   12527             :         }
   12528          69 :         else if (nVarZId >= 0 &&
   12529          34 :                  (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
   12530          34 :                   nVarDimCount != 1 ||
   12531          34 :                   nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
   12532          34 :                   nVarDimId != nVectorDim))
   12533             :         {
   12534           0 :             nVarZId = -1;
   12535             :         }
   12536             :     }
   12537             : 
   12538          49 :     if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
   12539             :     {
   12540           2 :         eGType = wkbPoint;
   12541             :     }
   12542          49 :     if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
   12543             :     {
   12544          34 :         eGType = wkbPoint25D;
   12545             :     }
   12546          49 :     if (eGType == wkbUnknown && osGeometryField.empty())
   12547             :     {
   12548           5 :         eGType = wkbNone;
   12549             :     }
   12550             : 
   12551             :     // Read projection info
   12552          49 :     char **papszMetadataBackup = CSLDuplicate(papszMetadata);
   12553          49 :     ReadAttributes(nCdfId, nFirstVarId);
   12554          49 :     if (!this->bSGSupport)
   12555          49 :         SetProjectionFromVar(nCdfId, nFirstVarId, true);
   12556          49 :     const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
   12557          49 :     char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
   12558          49 :     CSLDestroy(papszMetadata);
   12559          49 :     papszMetadata = papszMetadataBackup;
   12560             : 
   12561          49 :     OGRSpatialReference *poSRS = nullptr;
   12562          49 :     if (!m_oSRS.IsEmpty())
   12563             :     {
   12564          21 :         poSRS = m_oSRS.Clone();
   12565             :     }
   12566             :     // Reset if there's a 2D raster
   12567          49 :     m_bHasProjection = false;
   12568          49 :     m_bHasGeoTransform = false;
   12569             : 
   12570          49 :     if (!bKeepRasters)
   12571             :     {
   12572             :         // Strip out uninteresting metadata.
   12573          45 :         papszMetadata =
   12574          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
   12575          45 :         papszMetadata =
   12576          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
   12577          45 :         papszMetadata =
   12578          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
   12579             :     }
   12580             : 
   12581             :     std::shared_ptr<netCDFLayer> poLayer(
   12582          49 :         new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
   12583          49 :     if (poSRS != nullptr)
   12584          21 :         poSRS->Release();
   12585          49 :     poLayer->SetRecordDimID(nVectorDim);
   12586          49 :     if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
   12587             :     {
   12588          35 :         poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
   12589             :     }
   12590          14 :     else if (!osGeometryField.empty())
   12591             :     {
   12592           9 :         poLayer->SetWKTGeometryField(osGeometryField);
   12593             :     }
   12594          49 :     if (pszGridMapping != nullptr)
   12595             :     {
   12596          21 :         poLayer->SetGridMapping(pszGridMapping);
   12597          21 :         CPLFree(pszGridMapping);
   12598             :     }
   12599          49 :     poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
   12600             : 
   12601         574 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12602             :     {
   12603         525 :         int anDimIds[2] = {-1, -1};
   12604         525 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12605         525 :         if (anDimIds[0] == nVectorDim ||
   12606          24 :             (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
   12607             :         {
   12608             : #ifdef NCDF_DEBUG
   12609             :             char szTemp2[NC_MAX_NAME + 1] = {};
   12610             :             CPL_IGNORE_RET_VAL(
   12611             :                 nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
   12612             :             CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
   12613             : #endif
   12614         525 :             poLayer->AddField(anPotentialVectorVarID[j]);
   12615             :         }
   12616             :     }
   12617             : 
   12618          49 :     if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
   12619           0 :         poLayer->GetGeomType() != wkbNone)
   12620             :     {
   12621          49 :         papoLayers.push_back(poLayer);
   12622             :     }
   12623             : 
   12624          98 :     return CE_None;
   12625             : }
   12626             : 
   12627             : // Get all coordinate and boundary variables full names referenced in
   12628             : // a given a NetCDF (or group) ID and its sub-groups.
   12629             : // These variables are identified in other variable's
   12630             : // "coordinates" and "bounds" attribute.
   12631             : // Searching coordinate and boundary variables may need to explore
   12632             : // parents groups (or other groups in case of reference given in form of an
   12633             : // absolute path).
   12634             : // See CF sections 5.2, 5.6 and 7.1
   12635         526 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
   12636             : {
   12637         526 :     int nVars = 0;
   12638         526 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12639             : 
   12640        3232 :     for (int v = 0; v < nVars; v++)
   12641             :     {
   12642        2706 :         char *pszTemp = nullptr;
   12643        2706 :         char **papszTokens = nullptr;
   12644        2706 :         if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
   12645         445 :             papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
   12646        2706 :         CPLFree(pszTemp);
   12647        2706 :         pszTemp = nullptr;
   12648        2706 :         if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
   12649        2706 :             pszTemp != nullptr && !EQUAL(pszTemp, ""))
   12650          17 :             papszTokens = CSLAddString(papszTokens, pszTemp);
   12651        2706 :         CPLFree(pszTemp);
   12652        3992 :         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
   12653             :              i++)
   12654             :         {
   12655        1286 :             char *pszVarFullName = nullptr;
   12656        1286 :             if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
   12657        1286 :                                        &pszVarFullName) == CE_None)
   12658        1260 :                 *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
   12659        1286 :             CPLFree(pszVarFullName);
   12660             :         }
   12661        2706 :         CSLDestroy(papszTokens);
   12662             :     }
   12663             : 
   12664             :     // Recurse on sub-groups.
   12665             :     int nSubGroups;
   12666         526 :     int *panSubGroupIds = nullptr;
   12667         526 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12668         559 :     for (int i = 0; i < nSubGroups; i++)
   12669             :     {
   12670          33 :         NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
   12671             :     }
   12672         526 :     CPLFree(panSubGroupIds);
   12673             : 
   12674         526 :     return CE_None;
   12675             : }
   12676             : 
   12677             : // Check if give type is user defined
   12678        1520 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
   12679             : {
   12680        1520 :     return type >= NC_FIRSTUSERTYPEID;
   12681             : }
   12682             : 
   12683         558 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
   12684             : {
   12685             :     // CF conventions use space as the separator for variable names in the
   12686             :     // coordinates attribute, but some products such as
   12687             :     // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
   12688             :     // use comma.
   12689         558 :     return CSLTokenizeString2(pszCoordinates, ", ", 0);
   12690             : }

Generated by: LCOV version 1.14