LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4972 5831 85.3 %
Date: 2026-09-15 00:00:15 Functions: 159 165 96.4 %

          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 CPLStringList 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, std::string &osFullName,
     111             :                                    bool bNC3Compat = true);
     112             : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId,
     113             :                                  std::string &osFullName,
     114             :                                  bool bNC3Compat = true);
     115             : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
     116             : 
     117             : static std::pair<int, int> ReadExtraDimDef(GDALDataset *poSrcDS,
     118             :                                            const char *pszDimName);
     119             : 
     120             : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
     121             :                                      std::string &osFullName,
     122             :                                      bool bMandatory = false);
     123             : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
     124             :                                 const char *pszAtt, int *pnAtt,
     125             :                                 bool bMandatory = false);
     126             : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
     127             :                                                CPLStringList &aosVars);
     128             : 
     129             : // Uncomment this for more debug output.
     130             : // #define NCDF_DEBUG 1
     131             : 
     132             : CPLMutex *hNCMutex = nullptr;
     133             : 
     134             : // Workaround https://github.com/OSGeo/gdal/issues/6253
     135             : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
     136             : // way. Apparently having the same handle works better (this is OK since
     137             : // we have a global mutex on the netCDF library)
     138             : static std::map<std::string, int> goMapNameToNetCDFId;
     139             : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
     140             : 
     141         864 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
     142             : {
     143        1728 :     std::string osKey(pszFilename);
     144         864 :     osKey += "#####";
     145         864 :     osKey += std::to_string(nMode);
     146         864 :     auto oIter = goMapNameToNetCDFId.find(osKey);
     147         864 :     if (oIter == goMapNameToNetCDFId.end())
     148             :     {
     149         798 :         int ret = nc_open(pszFilename, nMode, pID);
     150         798 :         if (ret != NC_NOERR)
     151           3 :             return ret;
     152         795 :         goMapNameToNetCDFId[osKey] = *pID;
     153         795 :         goMapNetCDFIdToKeyAndCount[*pID] =
     154        1590 :             std::pair<std::string, int>(osKey, 1);
     155         795 :         return ret;
     156             :     }
     157             :     else
     158             :     {
     159          66 :         *pID = oIter->second;
     160          66 :         goMapNetCDFIdToKeyAndCount[oIter->second].second++;
     161          66 :         return NC_NOERR;
     162             :     }
     163             : }
     164             : 
     165        1188 : int GDAL_nc_close(int cdfid)
     166             : {
     167        1188 :     int ret = NC_NOERR;
     168        1188 :     auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
     169        1188 :     if (oIter != goMapNetCDFIdToKeyAndCount.end())
     170             :     {
     171         861 :         if (--oIter->second.second == 0)
     172             :         {
     173         795 :             ret = nc_close(cdfid);
     174         795 :             goMapNameToNetCDFId.erase(oIter->second.first);
     175         795 :             goMapNetCDFIdToKeyAndCount.erase(oIter);
     176             :         }
     177             :     }
     178             :     else
     179             :     {
     180             :         // we can go here if file opened with nc_open_mem() or nc_create()
     181         327 :         ret = nc_close(cdfid);
     182             :     }
     183        1188 :     return ret;
     184             : }
     185             : 
     186             : /************************************************************************/
     187             : /* ==================================================================== */
     188             : /*                         netCDFRasterBand                             */
     189             : /* ==================================================================== */
     190             : /************************************************************************/
     191             : 
     192             : class netCDFRasterBand final : public GDALPamRasterBand
     193             : {
     194             :     friend class netCDFDataset;
     195             : 
     196             :     nc_type nc_datatype;
     197             :     int cdfid;
     198             :     int nZId;
     199             :     int nZDim;
     200             :     int nLevel;
     201             :     int nBandXPos;
     202             :     int nBandYPos;
     203             :     int *panBandZPos;
     204             :     int *panBandZLev;
     205             :     bool m_bNoDataSet = false;
     206             :     double m_dfNoDataValue = 0;
     207             :     bool m_bNoDataSetAsInt64 = false;
     208             :     int64_t m_nNodataValueInt64 = 0;
     209             :     bool m_bNoDataSetAsUInt64 = false;
     210             :     uint64_t m_nNodataValueUInt64 = 0;
     211             :     bool bValidRangeValid = false;
     212             :     double adfValidRange[2]{0, 0};
     213             :     bool m_bHaveScale = false;
     214             :     bool m_bHaveOffset = false;
     215             :     double m_dfScale = 1;
     216             :     double m_dfOffset = 0;
     217             :     CPLString m_osUnitType{};
     218             :     bool bSignedData;
     219             :     bool bCheckLongitude;
     220             :     bool m_bCreateMetadataFromOtherVarsDone = false;
     221             : 
     222             :     void CreateMetadataFromAttributes();
     223             :     void CreateMetadataFromOtherVars();
     224             : 
     225             :     template <class T>
     226             :     void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     227             :                    size_t nTmpBlockYSize, bool bCheckIsNan = false);
     228             :     template <class T>
     229             :     void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     230             :                       size_t nTmpBlockYSize, bool bCheckIsNan = false);
     231             :     void SetBlockSize();
     232             : 
     233             :     bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
     234             : 
     235             :     void SetNoDataValueNoUpdate(double dfNoData);
     236             :     void SetNoDataValueNoUpdate(int64_t nNoData);
     237             :     void SetNoDataValueNoUpdate(uint64_t nNoData);
     238             : 
     239             :     void SetOffsetNoUpdate(double dfVal);
     240             :     void SetScaleNoUpdate(double dfVal);
     241             :     void SetUnitTypeNoUpdate(const char *pszNewValue);
     242             : 
     243             :   protected:
     244             :     CPLXMLNode *SerializeToXML(const char *pszUnused) override;
     245             : 
     246             :   public:
     247             :     struct CONSTRUCTOR_OPEN
     248             :     {
     249             :     };
     250             : 
     251             :     struct CONSTRUCTOR_CREATE
     252             :     {
     253             :     };
     254             : 
     255             :     netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
     256             :                      int nGroupId, int nZId, int nZDim, int nLevel,
     257             :                      const int *panBandZLen, const int *panBandPos, int nBand);
     258             :     netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
     259             :                      GDALDataType eType, int nBand, bool bSigned = true,
     260             :                      const char *pszBandName = nullptr,
     261             :                      const char *pszLongName = nullptr, int nZId = -1,
     262             :                      int nZDim = 2, int nLevel = 0,
     263             :                      const int *panBandZLev = nullptr,
     264             :                      const int *panBandZPos = nullptr,
     265             :                      const int *paDimIds = nullptr);
     266             :     ~netCDFRasterBand() override;
     267             : 
     268             :     double GetNoDataValue(int *) override;
     269             :     int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
     270             :     uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
     271             :     CPLErr SetNoDataValue(double) override;
     272             :     CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
     273             :     CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
     274             :     // virtual CPLErr DeleteNoDataValue();
     275             :     double GetOffset(int *) override;
     276             :     CPLErr SetOffset(double) override;
     277             :     double GetScale(int *) override;
     278             :     CPLErr SetScale(double) override;
     279             :     const char *GetUnitType() override;
     280             :     CPLErr SetUnitType(const char *) override;
     281             :     CPLErr IReadBlock(int, int, void *) override;
     282             :     CPLErr IWriteBlock(int, int, void *) override;
     283             : 
     284             :     CSLConstList GetMetadata(const char *pszDomain = "") override;
     285             :     const char *GetMetadataItem(const char *pszName,
     286             :                                 const char *pszDomain = "") override;
     287             : 
     288             :     CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     289             :                            const char *pszDomain = "") override;
     290             :     CPLErr SetMetadata(CSLConstList papszMD,
     291             :                        const char *pszDomain = "") override;
     292             : };
     293             : 
     294             : /************************************************************************/
     295             : /*                          netCDFRasterBand()                          */
     296             : /************************************************************************/
     297             : 
     298         519 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
     299             :                                    netCDFDataset *poNCDFDS, int nGroupId,
     300             :                                    int nZIdIn, int nZDimIn, int nLevelIn,
     301             :                                    const int *panBandZLevIn,
     302         519 :                                    const int *panBandZPosIn, int nBandIn)
     303             :     : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
     304         519 :       nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
     305         519 :       nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
     306             :       panBandZLev(nullptr),
     307             :       bSignedData(true),  // Default signed, except for Byte.
     308        1038 :       bCheckLongitude(false)
     309             : {
     310         519 :     poDS = poNCDFDS;
     311         519 :     nBand = nBandIn;
     312             : 
     313             :     // Take care of all other dimensions.
     314         519 :     if (nZDim > 2)
     315             :     {
     316         182 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     317         182 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     318             : 
     319         490 :         for (int i = 0; i < nZDim - 2; i++)
     320             :         {
     321         308 :             panBandZPos[i] = panBandZPosIn[i + 2];
     322         308 :             panBandZLev[i] = panBandZLevIn[i];
     323             :         }
     324             :     }
     325             : 
     326         519 :     nRasterXSize = poDS->GetRasterXSize();
     327         519 :     nRasterYSize = poDS->GetRasterYSize();
     328         519 :     nBlockXSize = poDS->GetRasterXSize();
     329         519 :     nBlockYSize = 1;
     330             : 
     331             :     // Get the type of the "z" variable, our target raster array.
     332         519 :     if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
     333         519 :                    nullptr) != NC_NOERR)
     334             :     {
     335           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
     336           0 :         return;
     337             :     }
     338             : 
     339         519 :     if (NCDFIsUserDefinedType(cdfid, nc_datatype))
     340             :     {
     341             :         // First enquire and check that the number of fields is 2
     342             :         size_t nfields, compoundsize;
     343           5 :         if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
     344           5 :                             &nfields) != NC_NOERR)
     345             :         {
     346           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     347             :                      "Error in nc_inq_compound() on 'z'.");
     348           0 :             return;
     349             :         }
     350             : 
     351           5 :         if (nfields != 2)
     352             :         {
     353           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     354             :                      "Unsupported data type encountered in nc_inq_compound() "
     355             :                      "on 'z'.");
     356           0 :             return;
     357             :         }
     358             : 
     359             :         // Now check that that two types are the same in the struct.
     360             :         nc_type field_type1, field_type2;
     361             :         int field_dims1, field_dims2;
     362           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     363             :                                   &field_type1, &field_dims1,
     364           5 :                                   nullptr) != NC_NOERR)
     365             :         {
     366           0 :             CPLError(
     367             :                 CE_Failure, CPLE_AppDefined,
     368             :                 "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
     369           0 :             return;
     370             :         }
     371             : 
     372           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     373             :                                   &field_type2, &field_dims2,
     374           5 :                                   nullptr) != NC_NOERR)
     375             :         {
     376           0 :             CPLError(
     377             :                 CE_Failure, CPLE_AppDefined,
     378             :                 "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
     379           0 :             return;
     380             :         }
     381             : 
     382           5 :         if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
     383           5 :             (field_dims1 != 0))
     384             :         {
     385           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     386             :                      "Error in interpreting compound data type on 'z'.");
     387           0 :             return;
     388             :         }
     389             : 
     390           5 :         if (field_type1 == NC_SHORT)
     391           0 :             eDataType = GDT_CInt16;
     392           5 :         else if (field_type1 == NC_INT)
     393           0 :             eDataType = GDT_CInt32;
     394           5 :         else if (field_type1 == NC_FLOAT)
     395           4 :             eDataType = GDT_CFloat32;
     396           1 :         else if (field_type1 == NC_DOUBLE)
     397           1 :             eDataType = GDT_CFloat64;
     398             :         else
     399             :         {
     400           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     401             :                      "Unsupported netCDF compound data type encountered.");
     402           0 :             return;
     403             :         }
     404             :     }
     405             :     else
     406             :     {
     407         514 :         if (nc_datatype == NC_BYTE)
     408         166 :             eDataType = GDT_UInt8;
     409         348 :         else if (nc_datatype == NC_CHAR)
     410           0 :             eDataType = GDT_UInt8;
     411         348 :         else if (nc_datatype == NC_SHORT)
     412          44 :             eDataType = GDT_Int16;
     413         304 :         else if (nc_datatype == NC_INT)
     414          89 :             eDataType = GDT_Int32;
     415         215 :         else if (nc_datatype == NC_FLOAT)
     416         131 :             eDataType = GDT_Float32;
     417          84 :         else if (nc_datatype == NC_DOUBLE)
     418          45 :             eDataType = GDT_Float64;
     419          39 :         else if (nc_datatype == NC_UBYTE)
     420          16 :             eDataType = GDT_UInt8;
     421          23 :         else if (nc_datatype == NC_USHORT)
     422           4 :             eDataType = GDT_UInt16;
     423          19 :         else if (nc_datatype == NC_UINT)
     424           4 :             eDataType = GDT_UInt32;
     425          15 :         else if (nc_datatype == NC_INT64)
     426           8 :             eDataType = GDT_Int64;
     427           7 :         else if (nc_datatype == NC_UINT64)
     428           7 :             eDataType = GDT_UInt64;
     429             :         else
     430             :         {
     431           0 :             if (nBand == 1)
     432           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     433             :                          "Unsupported netCDF datatype (%d), treat as Float32.",
     434           0 :                          static_cast<int>(nc_datatype));
     435           0 :             eDataType = GDT_Float32;
     436           0 :             nc_datatype = NC_FLOAT;
     437             :         }
     438             :     }
     439             : 
     440             :     // Find and set No Data for this variable.
     441         519 :     nc_type atttype = NC_NAT;
     442         519 :     size_t attlen = 0;
     443         519 :     const char *pszNoValueName = nullptr;
     444             : 
     445             :     // Find attribute name, either _FillValue or missing_value.
     446         519 :     int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
     447         519 :     if (status == NC_NOERR)
     448             :     {
     449         260 :         pszNoValueName = NCDF_FillValue;
     450             :     }
     451             :     else
     452             :     {
     453         259 :         status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
     454         259 :         if (status == NC_NOERR)
     455             :         {
     456          12 :             pszNoValueName = "missing_value";
     457             :         }
     458             :     }
     459             : 
     460             :     // Fetch missing value.
     461         519 :     double dfNoData = 0.0;
     462         519 :     bool bGotNoData = false;
     463         519 :     int64_t nNoDataAsInt64 = 0;
     464         519 :     bool bGotNoDataAsInt64 = false;
     465         519 :     uint64_t nNoDataAsUInt64 = 0;
     466         519 :     bool bGotNoDataAsUInt64 = false;
     467         519 :     if (status == NC_NOERR)
     468             :     {
     469         272 :         nc_type nAttrType = NC_NAT;
     470         272 :         size_t nAttrLen = 0;
     471         272 :         status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
     472         272 :         if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
     473             :         {
     474             :             long long v;
     475           7 :             nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
     476           7 :             bGotNoData = true;
     477           7 :             bGotNoDataAsInt64 = true;
     478           7 :             nNoDataAsInt64 = static_cast<int64_t>(v);
     479             :         }
     480         265 :         else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
     481             :         {
     482             :             unsigned long long v;
     483           7 :             nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
     484           7 :             bGotNoData = true;
     485           7 :             bGotNoDataAsUInt64 = true;
     486           7 :             nNoDataAsUInt64 = static_cast<uint64_t>(v);
     487             :         }
     488         258 :         else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
     489             :         {
     490         257 :             bGotNoData = true;
     491             :         }
     492             :     }
     493             : 
     494             :     // If NoData was not found, use the default value, but for non-Byte types
     495             :     // as it is not recommended:
     496             :     // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
     497         519 :     nc_type vartype = NC_NAT;
     498         519 :     if (!bGotNoData)
     499             :     {
     500         248 :         nc_inq_vartype(cdfid, nZId, &vartype);
     501         248 :         if (vartype == NC_INT64)
     502             :         {
     503             :             nNoDataAsInt64 =
     504           1 :                 NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
     505           1 :             bGotNoDataAsInt64 = bGotNoData;
     506             :         }
     507         247 :         else if (vartype == NC_UINT64)
     508             :         {
     509             :             nNoDataAsUInt64 =
     510           0 :                 NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
     511           0 :             bGotNoDataAsUInt64 = bGotNoData;
     512             :         }
     513         247 :         else if (vartype != NC_CHAR && vartype != NC_BYTE &&
     514         104 :                  vartype != NC_UBYTE)
     515             :         {
     516          94 :             dfNoData =
     517          94 :                 NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
     518          94 :             if (bGotNoData)
     519             :             {
     520          83 :                 CPLDebug("GDAL_netCDF",
     521             :                          "did not get nodata value for variable #%d, using "
     522             :                          "default %f",
     523             :                          nZId, dfNoData);
     524             :             }
     525             :         }
     526             :     }
     527             : 
     528         519 :     bool bHasUnderscoreUnsignedAttr = false;
     529         519 :     bool bUnderscoreUnsignedAttrVal = false;
     530             :     {
     531         519 :         char *pszTemp = nullptr;
     532         519 :         if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
     533             :         {
     534         158 :             if (EQUAL(pszTemp, "true"))
     535             :             {
     536         150 :                 bHasUnderscoreUnsignedAttr = true;
     537         150 :                 bUnderscoreUnsignedAttrVal = true;
     538             :             }
     539           8 :             else if (EQUAL(pszTemp, "false"))
     540             :             {
     541           8 :                 bHasUnderscoreUnsignedAttr = true;
     542           8 :                 bUnderscoreUnsignedAttrVal = false;
     543             :             }
     544         158 :             CPLFree(pszTemp);
     545             :         }
     546             :     }
     547             : 
     548             :     // Look for valid_range or valid_min/valid_max.
     549             : 
     550             :     // First look for valid_range.
     551         519 :     if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
     552             :     {
     553         517 :         char *pszValidRange = nullptr;
     554         517 :         if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
     555         151 :                 CE_None &&
     556         668 :             pszValidRange[0] == '{' &&
     557         151 :             pszValidRange[strlen(pszValidRange) - 1] == '}')
     558             :         {
     559             :             const std::string osValidRange =
     560         453 :                 std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
     561             :             const CPLStringList aosValidRange(
     562         302 :                 CSLTokenizeString2(osValidRange.c_str(), ",", 0));
     563         151 :             if (aosValidRange.size() == 2 &&
     564         302 :                 CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
     565         151 :                 CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
     566             :             {
     567         151 :                 bValidRangeValid = true;
     568         151 :                 adfValidRange[0] = CPLAtof(aosValidRange[0]);
     569         151 :                 adfValidRange[1] = CPLAtof(aosValidRange[1]);
     570             :             }
     571             :         }
     572         517 :         CPLFree(pszValidRange);
     573             : 
     574             :         // If not found look for valid_min and valid_max.
     575         517 :         if (!bValidRangeValid)
     576             :         {
     577         366 :             double dfMin = 0;
     578         366 :             double dfMax = 0;
     579         381 :             if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
     580          15 :                 NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
     581             :             {
     582           8 :                 adfValidRange[0] = dfMin;
     583           8 :                 adfValidRange[1] = dfMax;
     584           8 :                 bValidRangeValid = true;
     585             :             }
     586             :         }
     587             : 
     588         517 :         if (bValidRangeValid &&
     589         159 :             (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
     590          17 :             nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
     591             :             bUnderscoreUnsignedAttrVal)
     592             :         {
     593           2 :             if (adfValidRange[0] < 0)
     594           0 :                 adfValidRange[0] += 65536;
     595           2 :             if (adfValidRange[1] < 0)
     596           2 :                 adfValidRange[1] += 65536;
     597           2 :             if (adfValidRange[0] <= adfValidRange[1])
     598             :             {
     599             :                 // Updating metadata item
     600           2 :                 GDALPamRasterBand::SetMetadataItem(
     601             :                     "valid_range",
     602           2 :                     CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
     603           2 :                                static_cast<int>(adfValidRange[1])));
     604             :             }
     605             :         }
     606             : 
     607         517 :         if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
     608             :         {
     609           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     610             :                      "netCDFDataset::valid_range: min > max:\n"
     611             :                      "  min: %lf\n  max: %lf\n",
     612             :                      adfValidRange[0], adfValidRange[1]);
     613           0 :             bValidRangeValid = false;
     614           0 :             adfValidRange[0] = 0.0;
     615           0 :             adfValidRange[1] = 0.0;
     616             :         }
     617             :     }
     618             : 
     619             :     // Special For Byte Bands: check for signed/unsigned byte.
     620         519 :     if (nc_datatype == NC_BYTE)
     621             :     {
     622             :         // netcdf uses signed byte by default, but GDAL uses unsigned by default
     623             :         // This may cause unexpected results, but is needed for back-compat.
     624         166 :         if (poNCDFDS->bIsGdalFile)
     625         144 :             bSignedData = false;
     626             :         else
     627          22 :             bSignedData = true;
     628             : 
     629             :         // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
     630             :         // But in case a NC3 file was converted automatically and has hints
     631             :         // that it is unsigned, take them into account
     632         166 :         if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     633             :         {
     634           3 :             bSignedData = true;
     635             :         }
     636             : 
     637             :         // If we got valid_range, test for signed/unsigned range.
     638             :         // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
     639         166 :         if (bValidRangeValid)
     640             :         {
     641             :             // If we got valid_range={0,255}, treat as unsigned.
     642         147 :             if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
     643             :             {
     644         139 :                 bSignedData = false;
     645             :                 // Reset valid_range.
     646         139 :                 bValidRangeValid = false;
     647             :             }
     648             :             // If we got valid_range={-128,127}, treat as signed.
     649           8 :             else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
     650             :             {
     651           8 :                 bSignedData = true;
     652             :                 // Reset valid_range.
     653           8 :                 bValidRangeValid = false;
     654             :             }
     655             :         }
     656             :         // Else test for _Unsigned.
     657             :         // https://docs.unidata.ucar.edu/nug/current/best_practices.html
     658             :         else
     659             :         {
     660          19 :             if (bHasUnderscoreUnsignedAttr)
     661           7 :                 bSignedData = !bUnderscoreUnsignedAttrVal;
     662             :         }
     663             : 
     664         166 :         if (bSignedData)
     665             :         {
     666          20 :             eDataType = GDT_Int8;
     667             :         }
     668         146 :         else if (dfNoData < 0)
     669             :         {
     670             :             // Fix nodata value as it was stored signed.
     671           6 :             dfNoData += 256;
     672           6 :             if (pszNoValueName)
     673             :             {
     674             :                 // Updating metadata item
     675           6 :                 GDALPamRasterBand::SetMetadataItem(
     676             :                     pszNoValueName,
     677             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     678             :             }
     679             :         }
     680             :     }
     681         353 :     else if (nc_datatype == NC_SHORT)
     682             :     {
     683          44 :         if (bHasUnderscoreUnsignedAttr)
     684             :         {
     685           4 :             bSignedData = !bUnderscoreUnsignedAttrVal;
     686           4 :             if (!bSignedData)
     687           4 :                 eDataType = GDT_UInt16;
     688             :         }
     689             : 
     690             :         // Fix nodata value as it was stored signed.
     691          44 :         if (!bSignedData && dfNoData < 0)
     692             :         {
     693           4 :             dfNoData += 65536;
     694           4 :             if (pszNoValueName)
     695             :             {
     696             :                 // Updating metadata item
     697           4 :                 GDALPamRasterBand::SetMetadataItem(
     698             :                     pszNoValueName,
     699             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     700             :             }
     701             :         }
     702             :     }
     703             : 
     704         309 :     else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
     705         289 :              nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
     706             :     {
     707          31 :         bSignedData = false;
     708             :     }
     709             : 
     710         519 :     CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
     711         519 :              nc_datatype, eDataType, static_cast<int>(bSignedData));
     712             : 
     713         519 :     if (bGotNoData)
     714             :     {
     715             :         // Set nodata value.
     716         355 :         if (bGotNoDataAsInt64)
     717             :         {
     718           8 :             if (eDataType == GDT_Int64)
     719             :             {
     720           8 :                 SetNoDataValueNoUpdate(nNoDataAsInt64);
     721             :             }
     722           0 :             else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
     723             :             {
     724           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
     725             :             }
     726             :             else
     727             :             {
     728           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
     729             :             }
     730             :         }
     731         347 :         else if (bGotNoDataAsUInt64)
     732             :         {
     733           7 :             if (eDataType == GDT_UInt64)
     734             :             {
     735           7 :                 SetNoDataValueNoUpdate(nNoDataAsUInt64);
     736             :             }
     737           0 :             else if (eDataType == GDT_Int64 &&
     738             :                      nNoDataAsUInt64 <=
     739           0 :                          static_cast<uint64_t>(
     740           0 :                              std::numeric_limits<int64_t>::max()))
     741             :             {
     742           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
     743             :             }
     744             :             else
     745             :             {
     746           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
     747             :             }
     748             :         }
     749             :         else
     750             :         {
     751             : #ifdef NCDF_DEBUG
     752             :             CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
     753             : #endif
     754         340 :             if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
     755             :             {
     756           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
     757             :             }
     758         340 :             else if (eDataType == GDT_UInt64 &&
     759           0 :                      GDALIsValueExactAs<uint64_t>(dfNoData))
     760             :             {
     761           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
     762             :             }
     763             :             else
     764             :             {
     765         340 :                 SetNoDataValueNoUpdate(dfNoData);
     766             :             }
     767             :         }
     768             :     }
     769             : 
     770         519 :     CreateMetadataFromAttributes();
     771             : 
     772             :     // Attempt to fetch the scale_factor and add_offset attributes for the
     773             :     // variable and set them.  If these values are not available, set
     774             :     // offset to 0 and scale to 1.
     775         519 :     if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
     776             :     {
     777          21 :         double dfOffset = 0;
     778          21 :         status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
     779          21 :         CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
     780             :                  status);
     781          21 :         SetOffsetNoUpdate(dfOffset);
     782             :     }
     783             : 
     784         519 :     bool bHasScale = false;
     785         519 :     if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
     786             :     {
     787          23 :         bHasScale = true;
     788          23 :         double dfScale = 1;
     789          23 :         status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
     790          23 :         CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
     791             :                  status);
     792          23 :         SetScaleNoUpdate(dfScale);
     793             :     }
     794             : 
     795          12 :     if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
     796           4 :         eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
     797           4 :         (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
     798         531 :          std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
     799           1 :         CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
     800             :             nullptr)
     801             :     {
     802           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     803             :                  "validity range = %f, %f contains floating-point values, "
     804             :                  "whereas data type is integer. valid_range is thus likely "
     805             :                  "wrong%s. Ignoring it.",
     806             :                  adfValidRange[0], adfValidRange[1],
     807             :                  bHasScale ? " (likely scaled using scale_factor/add_factor "
     808             :                              "whereas it should be using the packed data type)"
     809             :                            : "");
     810           1 :         bValidRangeValid = false;
     811           1 :         adfValidRange[0] = 0.0;
     812           1 :         adfValidRange[1] = 0.0;
     813             :     }
     814             : 
     815             :     // Should we check for longitude values > 360?
     816         519 :     bCheckLongitude =
     817        1038 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
     818         519 :         NCDFIsVarLongitude(cdfid, nZId, nullptr);
     819             : 
     820             :     // Attempt to fetch the units attribute for the variable and set it.
     821         519 :     SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
     822             : 
     823         519 :     SetBlockSize();
     824             : }
     825             : 
     826         713 : void netCDFRasterBand::SetBlockSize()
     827             : {
     828             :     // Check for variable chunking (netcdf-4 only).
     829             :     // GDAL block size should be set to hdf5 chunk size.
     830         713 :     int nTmpFormat = 0;
     831         713 :     int status = nc_inq_format(cdfid, &nTmpFormat);
     832         713 :     NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
     833         713 :     if ((status == NC_NOERR) &&
     834         603 :         (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
     835             :     {
     836         126 :         size_t chunksize[MAX_NC_DIMS] = {};
     837             :         // Check for chunksize and set it as the blocksize (optimizes read).
     838         126 :         status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
     839         126 :         if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
     840             :         {
     841          14 :             nBlockXSize = (int)chunksize[nZDim - 1];
     842          14 :             if (nZDim >= 2)
     843          14 :                 nBlockYSize = (int)chunksize[nZDim - 2];
     844             :             else
     845           0 :                 nBlockYSize = 1;
     846             :         }
     847             :     }
     848             : 
     849             :     // Deal with bottom-up datasets and nBlockYSize != 1.
     850         713 :     auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
     851         713 :     if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
     852             :     {
     853           6 :         if (poGDS->eAccess == GA_ReadOnly)
     854             :         {
     855             :             // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
     856             :             // width of the raster
     857           6 :             size_t nChunks =
     858           6 :                 static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
     859           6 :             if ((nRasterYSize % nBlockYSize) != 0)
     860           2 :                 nChunks *= 2;
     861             :             const size_t nChunkSize =
     862           6 :                 static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
     863           6 :                 nBlockXSize * nBlockYSize;
     864           6 :             constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
     865           6 :             nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
     866           6 :             if (nChunks)
     867             :             {
     868           6 :                 poGDS->poChunkCache.reset(
     869           6 :                     new netCDFDataset::ChunkCacheType(nChunks));
     870             :             }
     871             :         }
     872             :         else
     873             :         {
     874           0 :             nBlockYSize = 1;
     875             :         }
     876             :     }
     877         713 : }
     878             : 
     879             : // Constructor in create mode.
     880             : // If nZId and following variables are not passed, the band will have 2
     881             : // dimensions.
     882             : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
     883         194 : netCDFRasterBand::netCDFRasterBand(
     884             :     const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
     885             :     const GDALDataType eTypeIn, int nBandIn, bool bSigned,
     886             :     const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
     887             :     int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
     888         194 :     const int *paDimIds)
     889         194 :     : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
     890             :       nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
     891             :       panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
     892         194 :       bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
     893             : {
     894         194 :     poDS = poNCDFDS;
     895         194 :     nBand = nBandIn;
     896             : 
     897         194 :     nRasterXSize = poDS->GetRasterXSize();
     898         194 :     nRasterYSize = poDS->GetRasterYSize();
     899         194 :     nBlockXSize = poDS->GetRasterXSize();
     900         194 :     nBlockYSize = 1;
     901             : 
     902         194 :     if (poDS->GetAccess() != GA_Update)
     903             :     {
     904           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     905             :                  "Dataset is not in update mode, "
     906             :                  "wrong netCDFRasterBand constructor");
     907           0 :         return;
     908             :     }
     909             : 
     910             :     // Take care of all other dimensions.
     911         194 :     if (nZDim > 2 && paDimIds != nullptr)
     912             :     {
     913          28 :         nBandXPos = panBandZPosIn[0];
     914          28 :         nBandYPos = panBandZPosIn[1];
     915             : 
     916          28 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     917          28 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     918             : 
     919          78 :         for (int i = 0; i < nZDim - 2; i++)
     920             :         {
     921          50 :             panBandZPos[i] = panBandZPosIn[i + 2];
     922          50 :             panBandZLev[i] = panBandZLevIn[i];
     923             :         }
     924             :     }
     925             : 
     926             :     // Get the type of the "z" variable, our target raster array.
     927         194 :     eDataType = eTypeIn;
     928             : 
     929         194 :     switch (eDataType)
     930             :     {
     931          87 :         case GDT_UInt8:
     932          87 :             nc_datatype = NC_BYTE;
     933             :             // NC_UBYTE (unsigned byte) is only available for NC4.
     934          87 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     935           3 :                 nc_datatype = NC_UBYTE;
     936          87 :             break;
     937           7 :         case GDT_Int8:
     938           7 :             nc_datatype = NC_BYTE;
     939           7 :             break;
     940          11 :         case GDT_Int16:
     941          11 :             nc_datatype = NC_SHORT;
     942          11 :             break;
     943          24 :         case GDT_Int32:
     944          24 :             nc_datatype = NC_INT;
     945          24 :             break;
     946          15 :         case GDT_Float32:
     947          15 :             nc_datatype = NC_FLOAT;
     948          15 :             break;
     949           8 :         case GDT_Float64:
     950           8 :             nc_datatype = NC_DOUBLE;
     951           8 :             break;
     952           7 :         case GDT_Int64:
     953           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     954             :             {
     955           7 :                 nc_datatype = NC_INT64;
     956             :             }
     957             :             else
     958             :             {
     959           0 :                 if (nBand == 1)
     960           0 :                     CPLError(
     961             :                         CE_Warning, CPLE_AppDefined,
     962             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     963             :                         "Int64");
     964           0 :                 nc_datatype = NC_DOUBLE;
     965           0 :                 eDataType = GDT_Float64;
     966             :             }
     967           7 :             break;
     968           7 :         case GDT_UInt64:
     969           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     970             :             {
     971           7 :                 nc_datatype = NC_UINT64;
     972             :             }
     973             :             else
     974             :             {
     975           0 :                 if (nBand == 1)
     976           0 :                     CPLError(
     977             :                         CE_Warning, CPLE_AppDefined,
     978             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     979             :                         "UInt64");
     980           0 :                 nc_datatype = NC_DOUBLE;
     981           0 :                 eDataType = GDT_Float64;
     982             :             }
     983           7 :             break;
     984           6 :         case GDT_UInt16:
     985           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     986             :             {
     987           6 :                 nc_datatype = NC_USHORT;
     988           6 :                 break;
     989             :             }
     990             :             [[fallthrough]];
     991             :         case GDT_UInt32:
     992           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     993             :             {
     994           6 :                 nc_datatype = NC_UINT;
     995           6 :                 break;
     996             :             }
     997             :             [[fallthrough]];
     998             :         default:
     999          16 :             if (nBand == 1)
    1000           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1001             :                          "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
    1002           8 :                          static_cast<int>(eDataType));
    1003          16 :             nc_datatype = NC_FLOAT;
    1004          16 :             eDataType = GDT_Float32;
    1005          16 :             break;
    1006             :     }
    1007             : 
    1008             :     // Define the variable if necessary (if nZId == -1).
    1009         194 :     bool bDefineVar = false;
    1010             : 
    1011         194 :     if (nZId == -1)
    1012             :     {
    1013         172 :         bDefineVar = true;
    1014             : 
    1015             :         // Make sure we are in define mode.
    1016         172 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1017             : 
    1018             :         char szTempPrivate[256 + 1];
    1019         172 :         const char *pszTemp = nullptr;
    1020         172 :         if (!pszBandName || EQUAL(pszBandName, ""))
    1021             :         {
    1022         144 :             snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
    1023         144 :             pszTemp = szTempPrivate;
    1024             :         }
    1025             :         else
    1026             :         {
    1027          28 :             pszTemp = pszBandName;
    1028             :         }
    1029             : 
    1030             :         int status;
    1031         172 :         if (nZDim > 2 && paDimIds != nullptr)
    1032             :         {
    1033           6 :             status =
    1034           6 :                 nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
    1035             :         }
    1036             :         else
    1037             :         {
    1038         166 :             int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
    1039             :             status =
    1040         166 :                 nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
    1041             :         }
    1042         172 :         NCDF_ERR(status);
    1043         172 :         CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
    1044             :                  nc_datatype, nZId);
    1045             : 
    1046         172 :         if (!pszLongName || EQUAL(pszLongName, ""))
    1047             :         {
    1048         159 :             snprintf(szTempPrivate, sizeof(szTempPrivate),
    1049             :                      "GDAL Band Number %d", nBand);
    1050         159 :             pszTemp = szTempPrivate;
    1051             :         }
    1052             :         else
    1053             :         {
    1054          13 :             pszTemp = pszLongName;
    1055             :         }
    1056             :         status =
    1057         172 :             nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
    1058         172 :         NCDF_ERR(status);
    1059             : 
    1060         172 :         poNCDFDS->DefVarDeflate(nZId, true);
    1061             :     }
    1062             : 
    1063             :     // For Byte data add signed/unsigned info.
    1064         194 :     if (eDataType == GDT_UInt8 || eDataType == GDT_Int8)
    1065             :     {
    1066          94 :         if (bDefineVar)
    1067             :         {
    1068             :             // Only add attributes if creating variable.
    1069             :             // For unsigned NC_BYTE (except NC4 format),
    1070             :             // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
    1071          86 :             if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
    1072             :             {
    1073          83 :                 CPLDebug("GDAL_netCDF",
    1074             :                          "adding valid_range attributes for Byte Band");
    1075          83 :                 short l_adfValidRange[2] = {0, 0};
    1076             :                 int status;
    1077          83 :                 if (bSignedData || eDataType == GDT_Int8)
    1078             :                 {
    1079           7 :                     l_adfValidRange[0] = -128;
    1080           7 :                     l_adfValidRange[1] = 127;
    1081           7 :                     status =
    1082           7 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
    1083             :                 }
    1084             :                 else
    1085             :                 {
    1086          76 :                     l_adfValidRange[0] = 0;
    1087          76 :                     l_adfValidRange[1] = 255;
    1088             :                     status =
    1089          76 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
    1090             :                 }
    1091          83 :                 NCDF_ERR(status);
    1092          83 :                 status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
    1093             :                                           2, l_adfValidRange);
    1094          83 :                 NCDF_ERR(status);
    1095             :             }
    1096             :         }
    1097             :     }
    1098             : 
    1099         194 :     if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
    1100         103 :         nc_datatype != NC_UBYTE)
    1101             :     {
    1102             :         // Set default nodata.
    1103         100 :         bool bIgnored = false;
    1104             :         double dfNoData =
    1105         100 :             NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
    1106             : #ifdef NCDF_DEBUG
    1107             :         CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
    1108             : #endif
    1109         100 :         netCDFRasterBand::SetNoDataValue(dfNoData);
    1110             :     }
    1111             : 
    1112         194 :     SetBlockSize();
    1113             : }
    1114             : 
    1115             : /************************************************************************/
    1116             : /*                         ~netCDFRasterBand()                          */
    1117             : /************************************************************************/
    1118             : 
    1119        1426 : netCDFRasterBand::~netCDFRasterBand()
    1120             : {
    1121         713 :     netCDFRasterBand::FlushCache(true);
    1122         713 :     CPLFree(panBandZPos);
    1123         713 :     CPLFree(panBandZLev);
    1124        1426 : }
    1125             : 
    1126             : /************************************************************************/
    1127             : /*                            GetMetadata()                             */
    1128             : /************************************************************************/
    1129             : 
    1130          60 : CSLConstList netCDFRasterBand::GetMetadata(const char *pszDomain)
    1131             : {
    1132          60 :     if (!m_bCreateMetadataFromOtherVarsDone)
    1133          58 :         CreateMetadataFromOtherVars();
    1134          60 :     return GDALPamRasterBand::GetMetadata(pszDomain);
    1135             : }
    1136             : 
    1137             : /************************************************************************/
    1138             : /*                          GetMetadataItem()                           */
    1139             : /************************************************************************/
    1140             : 
    1141         606 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
    1142             :                                               const char *pszDomain)
    1143             : {
    1144         606 :     if (!m_bCreateMetadataFromOtherVarsDone &&
    1145         590 :         STARTS_WITH(pszName, "NETCDF_DIM_") &&
    1146           1 :         (!pszDomain || pszDomain[0] == 0))
    1147           1 :         CreateMetadataFromOtherVars();
    1148         606 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
    1149             : }
    1150             : 
    1151             : /************************************************************************/
    1152             : /*                          SetMetadataItem()                           */
    1153             : /************************************************************************/
    1154             : 
    1155           7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
    1156             :                                          const char *pszValue,
    1157             :                                          const char *pszDomain)
    1158             : {
    1159           9 :     if (GetAccess() == GA_Update &&
    1160           9 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    1161             :     {
    1162             :         // Same logic as in CopyMetadata()
    1163             : 
    1164           2 :         const char *const papszIgnoreBand[] = {
    1165             :             CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    1166             :             NCDF_FillValue, "coordinates",   nullptr};
    1167             :         // Do not copy varname, stats, NETCDF_DIM_*, nodata
    1168             :         // and items in papszIgnoreBand.
    1169           6 :         if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
    1170           2 :             STARTS_WITH(pszName, "STATISTICS_") ||
    1171           2 :             STARTS_WITH(pszName, "NETCDF_DIM_") ||
    1172           2 :             STARTS_WITH(pszName, "missing_value") ||
    1173           6 :             STARTS_WITH(pszName, "_FillValue") ||
    1174           2 :             CSLFindString(papszIgnoreBand, pszName) != -1)
    1175             :         {
    1176             :             // do nothing
    1177             :         }
    1178             :         else
    1179             :         {
    1180           2 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1181             : 
    1182           2 :             if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
    1183           2 :                 return CE_Failure;
    1184             :         }
    1185             :     }
    1186             : 
    1187           5 :     return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    1188             : }
    1189             : 
    1190             : /************************************************************************/
    1191             : /*                            SetMetadata()                             */
    1192             : /************************************************************************/
    1193             : 
    1194           2 : CPLErr netCDFRasterBand::SetMetadata(CSLConstList papszMD,
    1195             :                                      const char *pszDomain)
    1196             : {
    1197           4 :     if (GetAccess() == GA_Update &&
    1198           2 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    1199             :     {
    1200             :         // We don't handle metadata item removal for now
    1201           4 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    1202             :              ++papszIter)
    1203             :         {
    1204           2 :             char *pszName = nullptr;
    1205           2 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    1206           2 :             if (pszName && pszValue)
    1207           2 :                 SetMetadataItem(pszName, pszValue);
    1208           2 :             CPLFree(pszName);
    1209             :         }
    1210             :     }
    1211           2 :     return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
    1212             : }
    1213             : 
    1214             : /************************************************************************/
    1215             : /*                             GetOffset()                              */
    1216             : /************************************************************************/
    1217          56 : double netCDFRasterBand::GetOffset(int *pbSuccess)
    1218             : {
    1219          56 :     if (pbSuccess != nullptr)
    1220          50 :         *pbSuccess = static_cast<int>(m_bHaveOffset);
    1221             : 
    1222          56 :     return m_dfOffset;
    1223             : }
    1224             : 
    1225             : /************************************************************************/
    1226             : /*                             SetOffset()                              */
    1227             : /************************************************************************/
    1228           6 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
    1229             : {
    1230          12 :     CPLMutexHolderD(&hNCMutex);
    1231             : 
    1232             :     // Write value if in update mode.
    1233           6 :     if (poDS->GetAccess() == GA_Update)
    1234             :     {
    1235             :         // Make sure we are in define mode.
    1236           6 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1237             : 
    1238           6 :         const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
    1239             :                                              NC_DOUBLE, 1, &dfNewOffset);
    1240             : 
    1241           6 :         NCDF_ERR(status);
    1242           6 :         if (status == NC_NOERR)
    1243             :         {
    1244           6 :             SetOffsetNoUpdate(dfNewOffset);
    1245           6 :             return CE_None;
    1246             :         }
    1247             : 
    1248           0 :         return CE_Failure;
    1249             :     }
    1250             : 
    1251           0 :     SetOffsetNoUpdate(dfNewOffset);
    1252           0 :     return CE_None;
    1253             : }
    1254             : 
    1255             : /************************************************************************/
    1256             : /*                         SetOffsetNoUpdate()                          */
    1257             : /************************************************************************/
    1258          27 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
    1259             : {
    1260          27 :     m_dfOffset = dfVal;
    1261          27 :     m_bHaveOffset = true;
    1262          27 : }
    1263             : 
    1264             : /************************************************************************/
    1265             : /*                              GetScale()                              */
    1266             : /************************************************************************/
    1267          56 : double netCDFRasterBand::GetScale(int *pbSuccess)
    1268             : {
    1269          56 :     if (pbSuccess != nullptr)
    1270          50 :         *pbSuccess = static_cast<int>(m_bHaveScale);
    1271             : 
    1272          56 :     return m_dfScale;
    1273             : }
    1274             : 
    1275             : /************************************************************************/
    1276             : /*                              SetScale()                              */
    1277             : /************************************************************************/
    1278           1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
    1279             : {
    1280           2 :     CPLMutexHolderD(&hNCMutex);
    1281             : 
    1282             :     // Write value if in update mode.
    1283           1 :     if (poDS->GetAccess() == GA_Update)
    1284             :     {
    1285             :         // Make sure we are in define mode.
    1286           1 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1287             : 
    1288           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
    1289             :                                              NC_DOUBLE, 1, &dfNewScale);
    1290             : 
    1291           1 :         NCDF_ERR(status);
    1292           1 :         if (status == NC_NOERR)
    1293             :         {
    1294           1 :             SetScaleNoUpdate(dfNewScale);
    1295           1 :             return CE_None;
    1296             :         }
    1297             : 
    1298           0 :         return CE_Failure;
    1299             :     }
    1300             : 
    1301           0 :     SetScaleNoUpdate(dfNewScale);
    1302           0 :     return CE_None;
    1303             : }
    1304             : 
    1305             : /************************************************************************/
    1306             : /*                          SetScaleNoUpdate()                          */
    1307             : /************************************************************************/
    1308          24 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
    1309             : {
    1310          24 :     m_dfScale = dfVal;
    1311          24 :     m_bHaveScale = true;
    1312          24 : }
    1313             : 
    1314             : /************************************************************************/
    1315             : /*                            GetUnitType()                             */
    1316             : /************************************************************************/
    1317             : 
    1318          27 : const char *netCDFRasterBand::GetUnitType()
    1319             : 
    1320             : {
    1321          27 :     if (!m_osUnitType.empty())
    1322           6 :         return m_osUnitType;
    1323             : 
    1324          21 :     return GDALRasterBand::GetUnitType();
    1325             : }
    1326             : 
    1327             : /************************************************************************/
    1328             : /*                            SetUnitType()                             */
    1329             : /************************************************************************/
    1330             : 
    1331           1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
    1332             : 
    1333             : {
    1334           2 :     CPLMutexHolderD(&hNCMutex);
    1335             : 
    1336           2 :     const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1337             : 
    1338           1 :     if (!osUnitType.empty())
    1339             :     {
    1340             :         // Write value if in update mode.
    1341           1 :         if (poDS->GetAccess() == GA_Update)
    1342             :         {
    1343             :             // Make sure we are in define mode.
    1344           1 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
    1345             : 
    1346           1 :             const int status = nc_put_att_text(
    1347             :                 cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
    1348             : 
    1349           1 :             NCDF_ERR(status);
    1350           1 :             if (status == NC_NOERR)
    1351             :             {
    1352           1 :                 SetUnitTypeNoUpdate(pszNewValue);
    1353           1 :                 return CE_None;
    1354             :             }
    1355             : 
    1356           0 :             return CE_Failure;
    1357             :         }
    1358             :     }
    1359             : 
    1360           0 :     SetUnitTypeNoUpdate(pszNewValue);
    1361             : 
    1362           0 :     return CE_None;
    1363             : }
    1364             : 
    1365             : /************************************************************************/
    1366             : /*                        SetUnitTypeNoUpdate()                         */
    1367             : /************************************************************************/
    1368             : 
    1369         520 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
    1370             : {
    1371         520 :     m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1372         520 : }
    1373             : 
    1374             : /************************************************************************/
    1375             : /*                           GetNoDataValue()                           */
    1376             : /************************************************************************/
    1377             : 
    1378         208 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
    1379             : 
    1380             : {
    1381         208 :     if (m_bNoDataSetAsInt64)
    1382             :     {
    1383           0 :         if (pbSuccess)
    1384           0 :             *pbSuccess = TRUE;
    1385           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
    1386             :     }
    1387             : 
    1388         208 :     if (m_bNoDataSetAsUInt64)
    1389             :     {
    1390           0 :         if (pbSuccess)
    1391           0 :             *pbSuccess = TRUE;
    1392           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
    1393             :     }
    1394             : 
    1395         208 :     if (m_bNoDataSet)
    1396             :     {
    1397         145 :         if (pbSuccess)
    1398         128 :             *pbSuccess = TRUE;
    1399         145 :         return m_dfNoDataValue;
    1400             :     }
    1401             : 
    1402          63 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
    1403             : }
    1404             : 
    1405             : /************************************************************************/
    1406             : /*                       GetNoDataValueAsInt64()                        */
    1407             : /************************************************************************/
    1408             : 
    1409           4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
    1410             : 
    1411             : {
    1412           4 :     if (m_bNoDataSetAsInt64)
    1413             :     {
    1414           4 :         if (pbSuccess)
    1415           4 :             *pbSuccess = TRUE;
    1416             : 
    1417           4 :         return m_nNodataValueInt64;
    1418             :     }
    1419             : 
    1420           0 :     return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
    1421             : }
    1422             : 
    1423             : /************************************************************************/
    1424             : /*                       GetNoDataValueAsUInt64()                       */
    1425             : /************************************************************************/
    1426             : 
    1427           4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
    1428             : 
    1429             : {
    1430           4 :     if (m_bNoDataSetAsUInt64)
    1431             :     {
    1432           4 :         if (pbSuccess)
    1433           4 :             *pbSuccess = TRUE;
    1434             : 
    1435           4 :         return m_nNodataValueUInt64;
    1436             :     }
    1437             : 
    1438           0 :     return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
    1439             : }
    1440             : 
    1441             : /************************************************************************/
    1442             : /*                           SetNoDataValue()                           */
    1443             : /************************************************************************/
    1444             : 
    1445         137 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
    1446             : 
    1447             : {
    1448         274 :     CPLMutexHolderD(&hNCMutex);
    1449             : 
    1450             :     // If already set to new value, don't do anything.
    1451         137 :     if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
    1452          19 :         return CE_None;
    1453             : 
    1454             :     // Write value if in update mode.
    1455         118 :     if (poDS->GetAccess() == GA_Update)
    1456             :     {
    1457             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1458             :         // but it is ok if variable has not been written to, so only print
    1459             :         // debug. See bug #4484.
    1460         129 :         if (m_bNoDataSet &&
    1461          11 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1462             :         {
    1463           0 :             CPLDebug("GDAL_netCDF",
    1464             :                      "Setting NoDataValue to %.17g (previously set to %.17g) "
    1465             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1466             :                      dfNoData, m_dfNoDataValue, cdfid, nBand);
    1467             :         }
    1468             : #ifdef NCDF_DEBUG
    1469             :         else
    1470             :         {
    1471             :             CPLDebug("GDAL_netCDF",
    1472             :                      "Setting NoDataValue to %.17g (id #%d, band #%d)",
    1473             :                      dfNoData, cdfid, nBand);
    1474             :         }
    1475             : #endif
    1476             :         // Make sure we are in define mode.
    1477         118 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1478             : 
    1479             :         int status;
    1480         118 :         if (eDataType == GDT_UInt8)
    1481             :         {
    1482           6 :             if (bSignedData)
    1483             :             {
    1484           0 :                 signed char cNoDataValue = static_cast<signed char>(dfNoData);
    1485           0 :                 status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
    1486             :                                           nc_datatype, 1, &cNoDataValue);
    1487             :             }
    1488             :             else
    1489             :             {
    1490           6 :                 const unsigned char ucNoDataValue =
    1491           6 :                     static_cast<unsigned char>(dfNoData);
    1492           6 :                 status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
    1493             :                                           nc_datatype, 1, &ucNoDataValue);
    1494             :             }
    1495             :         }
    1496         112 :         else if (eDataType == GDT_Int16)
    1497             :         {
    1498          14 :             short nsNoDataValue = static_cast<short>(dfNoData);
    1499          14 :             status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1500             :                                       1, &nsNoDataValue);
    1501             :         }
    1502          98 :         else if (eDataType == GDT_Int32)
    1503             :         {
    1504          27 :             int nNoDataValue = static_cast<int>(dfNoData);
    1505          27 :             status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
    1506             :                                     &nNoDataValue);
    1507             :         }
    1508          71 :         else if (eDataType == GDT_Float32)
    1509             :         {
    1510          34 :             float fNoDataValue = static_cast<float>(dfNoData);
    1511          34 :             status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1512             :                                       1, &fNoDataValue);
    1513             :         }
    1514          43 :         else if (eDataType == GDT_UInt16 &&
    1515           6 :                  cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
    1516             :                      NCDF_FORMAT_NC4)
    1517             :         {
    1518           6 :             unsigned short usNoDataValue =
    1519           6 :                 static_cast<unsigned short>(dfNoData);
    1520           6 :             status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1521             :                                        1, &usNoDataValue);
    1522             :         }
    1523          38 :         else if (eDataType == GDT_UInt32 &&
    1524           7 :                  cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
    1525             :                      NCDF_FORMAT_NC4)
    1526             :         {
    1527           7 :             unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
    1528           7 :             status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1529             :                                      1, &unNoDataValue);
    1530             :         }
    1531             :         else
    1532             :         {
    1533          24 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1534             :                                        1, &dfNoData);
    1535             :         }
    1536             : 
    1537         118 :         NCDF_ERR(status);
    1538             : 
    1539             :         // Update status if write worked.
    1540         118 :         if (status == NC_NOERR)
    1541             :         {
    1542         118 :             SetNoDataValueNoUpdate(dfNoData);
    1543         118 :             return CE_None;
    1544             :         }
    1545             : 
    1546           0 :         return CE_Failure;
    1547             :     }
    1548             : 
    1549           0 :     SetNoDataValueNoUpdate(dfNoData);
    1550           0 :     return CE_None;
    1551             : }
    1552             : 
    1553             : /************************************************************************/
    1554             : /*                       SetNoDataValueNoUpdate()                       */
    1555             : /************************************************************************/
    1556             : 
    1557         458 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
    1558             : {
    1559         458 :     m_dfNoDataValue = dfNoData;
    1560         458 :     m_bNoDataSet = true;
    1561         458 :     m_bNoDataSetAsInt64 = false;
    1562         458 :     m_bNoDataSetAsUInt64 = false;
    1563         458 : }
    1564             : 
    1565             : /************************************************************************/
    1566             : /*                       SetNoDataValueAsInt64()                        */
    1567             : /************************************************************************/
    1568             : 
    1569           3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    1570             : 
    1571             : {
    1572           6 :     CPLMutexHolderD(&hNCMutex);
    1573             : 
    1574             :     // If already set to new value, don't do anything.
    1575           3 :     if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
    1576           0 :         return CE_None;
    1577             : 
    1578             :     // Write value if in update mode.
    1579           3 :     if (poDS->GetAccess() == GA_Update)
    1580             :     {
    1581             :         // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
    1582             :         // but it is ok if variable has not been written to, so only print
    1583             :         // debug. See bug #4484.
    1584           3 :         if (m_bNoDataSetAsInt64 &&
    1585           0 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1586             :         {
    1587           0 :             CPLDebug("GDAL_netCDF",
    1588             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1589             :                      " (previously set to " CPL_FRMT_GIB ") "
    1590             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1591             :                      static_cast<GIntBig>(nNoData),
    1592           0 :                      static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
    1593             :         }
    1594             : #ifdef NCDF_DEBUG
    1595             :         else
    1596             :         {
    1597             :             CPLDebug("GDAL_netCDF",
    1598             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1599             :                      " (id #%d, band #%d)",
    1600             :                      static_cast<GIntBig>(nNoData), cdfid, nBand);
    1601             :         }
    1602             : #endif
    1603             :         // Make sure we are in define mode.
    1604           3 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1605             : 
    1606             :         int status;
    1607           6 :         if (eDataType == GDT_Int64 &&
    1608           3 :             cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1609             :         {
    1610           3 :             long long tmp = static_cast<long long>(nNoData);
    1611           3 :             status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
    1612             :                                          nc_datatype, 1, &tmp);
    1613             :         }
    1614             :         else
    1615             :         {
    1616           0 :             double dfNoData = static_cast<double>(nNoData);
    1617           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1618             :                                        1, &dfNoData);
    1619             :         }
    1620             : 
    1621           3 :         NCDF_ERR(status);
    1622             : 
    1623             :         // Update status if write worked.
    1624           3 :         if (status == NC_NOERR)
    1625             :         {
    1626           3 :             SetNoDataValueNoUpdate(nNoData);
    1627           3 :             return CE_None;
    1628             :         }
    1629             : 
    1630           0 :         return CE_Failure;
    1631             :     }
    1632             : 
    1633           0 :     SetNoDataValueNoUpdate(nNoData);
    1634           0 :     return CE_None;
    1635             : }
    1636             : 
    1637             : /************************************************************************/
    1638             : /*                       SetNoDataValueNoUpdate()                       */
    1639             : /************************************************************************/
    1640             : 
    1641          11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
    1642             : {
    1643          11 :     m_nNodataValueInt64 = nNoData;
    1644          11 :     m_bNoDataSet = false;
    1645          11 :     m_bNoDataSetAsInt64 = true;
    1646          11 :     m_bNoDataSetAsUInt64 = false;
    1647          11 : }
    1648             : 
    1649             : /************************************************************************/
    1650             : /*                       SetNoDataValueAsUInt64()                       */
    1651             : /************************************************************************/
    1652             : 
    1653           3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    1654             : 
    1655             : {
    1656           6 :     CPLMutexHolderD(&hNCMutex);
    1657             : 
    1658             :     // If already set to new value, don't do anything.
    1659           3 :     if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
    1660           0 :         return CE_None;
    1661             : 
    1662             :     // Write value if in update mode.
    1663           3 :     if (poDS->GetAccess() == GA_Update)
    1664             :     {
    1665             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1666             :         // but it is ok if variable has not been written to, so only print
    1667             :         // debug. See bug #4484.
    1668           3 :         if (m_bNoDataSetAsUInt64 &&
    1669           0 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1670             :         {
    1671           0 :             CPLDebug("GDAL_netCDF",
    1672             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1673             :                      " (previously set to " CPL_FRMT_GUIB ") "
    1674             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1675             :                      static_cast<GUIntBig>(nNoData),
    1676           0 :                      static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
    1677             :         }
    1678             : #ifdef NCDF_DEBUG
    1679             :         else
    1680             :         {
    1681             :             CPLDebug("GDAL_netCDF",
    1682             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1683             :                      " (id #%d, band #%d)",
    1684             :                      static_cast<GUIntBig>(nNoData), cdfid, nBand);
    1685             :         }
    1686             : #endif
    1687             :         // Make sure we are in define mode.
    1688           3 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1689             : 
    1690             :         int status;
    1691           6 :         if (eDataType == GDT_UInt64 &&
    1692           3 :             cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1693             :         {
    1694           3 :             unsigned long long tmp = static_cast<long long>(nNoData);
    1695           3 :             status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
    1696             :                                           nc_datatype, 1, &tmp);
    1697             :         }
    1698             :         else
    1699             :         {
    1700           0 :             double dfNoData = static_cast<double>(nNoData);
    1701           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1702             :                                        1, &dfNoData);
    1703             :         }
    1704             : 
    1705           3 :         NCDF_ERR(status);
    1706             : 
    1707             :         // Update status if write worked.
    1708           3 :         if (status == NC_NOERR)
    1709             :         {
    1710           3 :             SetNoDataValueNoUpdate(nNoData);
    1711           3 :             return CE_None;
    1712             :         }
    1713             : 
    1714           0 :         return CE_Failure;
    1715             :     }
    1716             : 
    1717           0 :     SetNoDataValueNoUpdate(nNoData);
    1718           0 :     return CE_None;
    1719             : }
    1720             : 
    1721             : /************************************************************************/
    1722             : /*                       SetNoDataValueNoUpdate()                       */
    1723             : /************************************************************************/
    1724             : 
    1725          10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
    1726             : {
    1727          10 :     m_nNodataValueUInt64 = nNoData;
    1728          10 :     m_bNoDataSet = false;
    1729          10 :     m_bNoDataSetAsInt64 = false;
    1730          10 :     m_bNoDataSetAsUInt64 = true;
    1731          10 : }
    1732             : 
    1733             : /************************************************************************/
    1734             : /*                         DeleteNoDataValue()                          */
    1735             : /************************************************************************/
    1736             : 
    1737             : #ifdef notdef
    1738             : CPLErr netCDFRasterBand::DeleteNoDataValue()
    1739             : 
    1740             : {
    1741             :     CPLMutexHolderD(&hNCMutex);
    1742             : 
    1743             :     if (!bNoDataSet)
    1744             :         return CE_None;
    1745             : 
    1746             :     // Write value if in update mode.
    1747             :     if (poDS->GetAccess() == GA_Update)
    1748             :     {
    1749             :         // Make sure we are in define mode.
    1750             :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1751             : 
    1752             :         status = nc_del_att(cdfid, nZId, NCDF_FillValue);
    1753             : 
    1754             :         NCDF_ERR(status);
    1755             : 
    1756             :         // Update status if write worked.
    1757             :         if (status == NC_NOERR)
    1758             :         {
    1759             :             dfNoDataValue = 0.0;
    1760             :             bNoDataSet = false;
    1761             :             return CE_None;
    1762             :         }
    1763             : 
    1764             :         return CE_Failure;
    1765             :     }
    1766             : 
    1767             :     dfNoDataValue = 0.0;
    1768             :     bNoDataSet = false;
    1769             :     return CE_None;
    1770             : }
    1771             : #endif
    1772             : 
    1773             : /************************************************************************/
    1774             : /*                           SerializeToXML()                           */
    1775             : /************************************************************************/
    1776             : 
    1777           5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
    1778             : {
    1779             :     // Overridden from GDALPamDataset to add only band histogram
    1780             :     // and statistics. See bug #4244.
    1781           5 :     if (psPam == nullptr)
    1782           0 :         return nullptr;
    1783             : 
    1784             :     // Setup root node and attributes.
    1785             :     CPLXMLNode *psTree =
    1786           5 :         CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
    1787             : 
    1788           5 :     if (GetBand() > 0)
    1789             :     {
    1790          10 :         CPLString oFmt;
    1791           5 :         CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
    1792             :     }
    1793             : 
    1794             :     // Histograms.
    1795           5 :     if (psPam->psSavedHistograms != nullptr)
    1796           1 :         CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
    1797             : 
    1798             :     // Metadata (statistics only).
    1799           5 :     GDALMultiDomainMetadata oMDMDStats;
    1800           5 :     const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
    1801             :                                   "STATISTICS_MEAN", "STATISTICS_STDDEV",
    1802             :                                   nullptr};
    1803          25 :     for (int i = 0; i < CSLCount(papszMDStats); i++)
    1804             :     {
    1805          20 :         const char *pszMDI = GetMetadataItem(papszMDStats[i]);
    1806          20 :         if (pszMDI)
    1807           4 :             oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
    1808             :     }
    1809           5 :     CPLXMLNode *psMD = oMDMDStats.Serialize();
    1810             : 
    1811           5 :     if (psMD != nullptr)
    1812             :     {
    1813           1 :         if (psMD->psChild == nullptr)
    1814           0 :             CPLDestroyXMLNode(psMD);
    1815             :         else
    1816           1 :             CPLAddXMLChild(psTree, psMD);
    1817             :     }
    1818             : 
    1819             :     // We don't want to return anything if we had no metadata to attach.
    1820           5 :     if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
    1821             :     {
    1822           3 :         CPLDestroyXMLNode(psTree);
    1823           3 :         psTree = nullptr;
    1824             :     }
    1825             : 
    1826           5 :     return psTree;
    1827             : }
    1828             : 
    1829             : /************************************************************************/
    1830             : /*                  Get1DVariableIndexedByDimension()                   */
    1831             : /************************************************************************/
    1832             : 
    1833          85 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
    1834             :                                            const char *pszDimName,
    1835             :                                            bool bVerboseError, int *pnGroupID)
    1836             : {
    1837          85 :     *pnGroupID = -1;
    1838          85 :     int nVarID = -1;
    1839             :     // First try to find a variable whose name is identical to the dimension
    1840             :     // name, and check that it is indeed indexed by this dimension
    1841          85 :     if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
    1842             :     {
    1843          71 :         int nDimCountOfVariable = 0;
    1844          71 :         nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
    1845          71 :         if (nDimCountOfVariable == 1)
    1846             :         {
    1847          71 :             int nDimIdOfVariable = -1;
    1848          71 :             nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
    1849          71 :             if (nDimIdOfVariable == nDimId)
    1850             :             {
    1851          71 :                 return nVarID;
    1852             :             }
    1853             :         }
    1854             :     }
    1855             : 
    1856             :     // Otherwise iterate over the variables to find potential candidates
    1857             :     // TODO: should be modified to search also in other groups using the same
    1858             :     //       logic than in NCDFResolveVar(), but maybe not needed if it's a
    1859             :     //       very rare case? and I think this is not CF compliant.
    1860          14 :     int nvars = 0;
    1861          14 :     CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
    1862             : 
    1863          14 :     int nCountCandidateVars = 0;
    1864          14 :     int nCandidateVarID = -1;
    1865          65 :     for (int k = 0; k < nvars; k++)
    1866             :     {
    1867          51 :         int nDimCountOfVariable = 0;
    1868          51 :         nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
    1869          51 :         if (nDimCountOfVariable == 1)
    1870             :         {
    1871          27 :             int nDimIdOfVariable = -1;
    1872          27 :             nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
    1873          27 :             if (nDimIdOfVariable == nDimId)
    1874             :             {
    1875           7 :                 nCountCandidateVars++;
    1876           7 :                 nCandidateVarID = k;
    1877             :             }
    1878             :         }
    1879             :     }
    1880          14 :     if (nCountCandidateVars > 1)
    1881             :     {
    1882           1 :         if (bVerboseError)
    1883             :         {
    1884           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1885             :                      "Several 1D variables are indexed by dimension %s",
    1886             :                      pszDimName);
    1887             :         }
    1888           1 :         *pnGroupID = -1;
    1889           1 :         return -1;
    1890             :     }
    1891          13 :     else if (nCandidateVarID < 0)
    1892             :     {
    1893           8 :         if (bVerboseError)
    1894             :         {
    1895           8 :             CPLError(CE_Warning, CPLE_AppDefined,
    1896             :                      "No 1D variable is indexed by dimension %s", pszDimName);
    1897             :         }
    1898             :     }
    1899          13 :     *pnGroupID = cdfid;
    1900          13 :     return nCandidateVarID;
    1901             : }
    1902             : 
    1903             : /************************************************************************/
    1904             : /*                    CreateMetadataFromAttributes()                    */
    1905             : /************************************************************************/
    1906             : 
    1907         519 : void netCDFRasterBand::CreateMetadataFromAttributes()
    1908             : {
    1909         519 :     char szVarName[NC_MAX_NAME + 1] = {};
    1910         519 :     int status = nc_inq_varname(cdfid, nZId, szVarName);
    1911         519 :     NCDF_ERR(status);
    1912             : 
    1913         519 :     GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
    1914             : 
    1915             :     // Get attribute metadata.
    1916         519 :     int nAtt = 0;
    1917         519 :     NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
    1918             : 
    1919        2291 :     for (int i = 0; i < nAtt; i++)
    1920             :     {
    1921        1772 :         char szMetaName[NC_MAX_NAME + 1] = {};
    1922        1772 :         status = nc_inq_attname(cdfid, nZId, i, szMetaName);
    1923        1772 :         if (status != NC_NOERR)
    1924          12 :             continue;
    1925             : 
    1926        1772 :         if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
    1927             :         {
    1928          12 :             continue;
    1929             :         }
    1930             : 
    1931        1760 :         char *pszMetaValue = nullptr;
    1932        1760 :         if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
    1933             :         {
    1934        1760 :             GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
    1935             :         }
    1936             :         else
    1937             :         {
    1938           0 :             CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
    1939             :         }
    1940             : 
    1941        1760 :         if (pszMetaValue)
    1942             :         {
    1943        1760 :             CPLFree(pszMetaValue);
    1944        1760 :             pszMetaValue = nullptr;
    1945             :         }
    1946             :     }
    1947         519 : }
    1948             : 
    1949             : /************************************************************************/
    1950             : /*                    CreateMetadataFromOtherVars()                     */
    1951             : /************************************************************************/
    1952             : 
    1953          59 : void netCDFRasterBand::CreateMetadataFromOtherVars()
    1954             : 
    1955             : {
    1956          59 :     CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
    1957          59 :     m_bCreateMetadataFromOtherVarsDone = true;
    1958             : 
    1959          59 :     netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
    1960          59 :     const int nPamFlagsBackup = l_poDS->nPamFlags;
    1961             : 
    1962             :     // Compute all dimensions from Band number and save in Metadata.
    1963          59 :     int nd = 0;
    1964          59 :     nc_inq_varndims(cdfid, nZId, &nd);
    1965             :     // Compute multidimention band position.
    1966             :     //
    1967             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    1968             :     // if Data[2,3,4,x,y]
    1969             :     //
    1970             :     //  BandPos0 = (nBand) / (3*4)
    1971             :     //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)
    1972             :     //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)
    1973             : 
    1974          59 :     int Sum = 1;
    1975          59 :     if (nd == 3)
    1976             :     {
    1977           6 :         Sum *= panBandZLev[0];
    1978             :     }
    1979             : 
    1980             :     // Loop over non-spatial dimensions.
    1981          59 :     int Taken = 0;
    1982             : 
    1983         100 :     for (int i = 0; i < nd - 2; i++)
    1984             :     {
    1985             :         int result;
    1986          41 :         if (i != nd - 2 - 1)
    1987             :         {
    1988          18 :             Sum = 1;
    1989          37 :             for (int j = i + 1; j < nd - 2; j++)
    1990             :             {
    1991          19 :                 Sum *= panBandZLev[j];
    1992             :             }
    1993          18 :             result = static_cast<int>((nLevel - Taken) / Sum);
    1994             :         }
    1995             :         else
    1996             :         {
    1997          23 :             result = static_cast<int>((nLevel - Taken) % Sum);
    1998             :         }
    1999             : 
    2000          41 :         char szName[NC_MAX_NAME + 1] = {};
    2001          41 :         snprintf(szName, sizeof(szName), "%s",
    2002          41 :                  l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
    2003             : 
    2004             :         char szMetaName[NC_MAX_NAME + 1 + 32];
    2005          41 :         snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
    2006             : 
    2007          41 :         const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
    2008          41 :         const int nVarID = l_poDS->m_anExtraDimVarIds[i];
    2009          41 :         if (nVarID < 0)
    2010             :         {
    2011           2 :             GDALPamRasterBand::SetMetadataItem(szMetaName,
    2012             :                                                CPLSPrintf("%d", result + 1));
    2013             :         }
    2014             :         else
    2015             :         {
    2016             :             // TODO: Make sure all the status checks make sense.
    2017             : 
    2018          39 :             nc_type nVarType = NC_NAT;
    2019          39 :             /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
    2020             : 
    2021          39 :             int nDims = 0;
    2022          39 :             /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
    2023             : 
    2024          39 :             char szMetaTemp[256] = {};
    2025          39 :             if (nDims == 1)
    2026             :             {
    2027          39 :                 size_t count[1] = {1};
    2028          39 :                 size_t start[1] = {static_cast<size_t>(result)};
    2029             : 
    2030          39 :                 switch (nVarType)
    2031             :                 {
    2032           0 :                     case NC_BYTE:
    2033             :                         // TODO: Check for signed/unsigned byte.
    2034             :                         signed char cData;
    2035           0 :                         /* status = */ nc_get_vara_schar(nGroupID, nVarID,
    2036             :                                                          start, count, &cData);
    2037           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
    2038           0 :                         break;
    2039           0 :                     case NC_SHORT:
    2040             :                         short sData;
    2041           0 :                         /* status = */ nc_get_vara_short(nGroupID, nVarID,
    2042             :                                                          start, count, &sData);
    2043           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
    2044           0 :                         break;
    2045          19 :                     case NC_INT:
    2046             :                     {
    2047             :                         int nData;
    2048          19 :                         /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
    2049             :                                                        count, &nData);
    2050          19 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
    2051          19 :                         break;
    2052             :                     }
    2053           0 :                     case NC_FLOAT:
    2054             :                         float fData;
    2055           0 :                         /* status = */ nc_get_vara_float(nGroupID, nVarID,
    2056             :                                                          start, count, &fData);
    2057           0 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
    2058             :                                     fData);
    2059           0 :                         break;
    2060          18 :                     case NC_DOUBLE:
    2061             :                         double dfData;
    2062          18 :                         /* status = */ nc_get_vara_double(
    2063             :                             nGroupID, nVarID, start, count, &dfData);
    2064          18 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
    2065             :                                     dfData);
    2066          18 :                         break;
    2067           0 :                     case NC_UBYTE:
    2068             :                         unsigned char ucData;
    2069           0 :                         /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
    2070             :                                                          start, count, &ucData);
    2071           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
    2072           0 :                         break;
    2073           0 :                     case NC_USHORT:
    2074             :                         unsigned short usData;
    2075           0 :                         /* status = */ nc_get_vara_ushort(
    2076             :                             nGroupID, nVarID, start, count, &usData);
    2077           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
    2078           0 :                         break;
    2079           0 :                     case NC_UINT:
    2080             :                     {
    2081             :                         unsigned int unData;
    2082           0 :                         /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
    2083             :                                                         count, &unData);
    2084           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
    2085           0 :                         break;
    2086             :                     }
    2087           2 :                     case NC_INT64:
    2088             :                     {
    2089             :                         long long nData;
    2090           2 :                         /* status = */ nc_get_vara_longlong(
    2091             :                             nGroupID, nVarID, start, count, &nData);
    2092           2 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
    2093             :                                  nData);
    2094           2 :                         break;
    2095             :                     }
    2096           0 :                     case NC_UINT64:
    2097             :                     {
    2098             :                         unsigned long long unData;
    2099           0 :                         /* status = */ nc_get_vara_ulonglong(
    2100             :                             nGroupID, nVarID, start, count, &unData);
    2101           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
    2102             :                                  unData);
    2103           0 :                         break;
    2104             :                     }
    2105           0 :                     default:
    2106           0 :                         CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
    2107             :                                  szMetaTemp, nVarType);
    2108           0 :                         break;
    2109             :                 }
    2110             :             }
    2111             :             else
    2112             :             {
    2113           0 :                 snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
    2114             :             }
    2115             : 
    2116             :             // Save dimension value.
    2117             :             // NOTE: removed #original_units as not part of CF-1.
    2118             : 
    2119          39 :             GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
    2120             :         }
    2121             : 
    2122             :         // Avoid int32 overflow. Perhaps something more sensible to do here ?
    2123          41 :         if (result > 0 && Sum > INT_MAX / result)
    2124           0 :             break;
    2125          41 :         if (Taken > INT_MAX - result * Sum)
    2126           0 :             break;
    2127             : 
    2128          41 :         Taken += result * Sum;
    2129             :     }  // End loop non-spatial dimensions.
    2130             : 
    2131          59 :     l_poDS->nPamFlags = nPamFlagsBackup;
    2132          59 : }
    2133             : 
    2134             : /************************************************************************/
    2135             : /*                             CheckData()                              */
    2136             : /************************************************************************/
    2137             : template <class T>
    2138        6054 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
    2139             :                                  size_t nTmpBlockXSize, size_t nTmpBlockYSize,
    2140             :                                  bool bCheckIsNan)
    2141             : {
    2142        6054 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2143             : 
    2144             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2145             :     // the data this is because partial blocks are not arranged the same way in
    2146             :     // netcdf and gdal.
    2147        6054 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2148             :     {
    2149           6 :         T *ptrWrite = static_cast<T *>(pImage);
    2150           6 :         T *ptrRead = static_cast<T *>(pImageNC);
    2151          29 :         for (size_t j = 0; j < nTmpBlockYSize;
    2152          23 :              j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
    2153             :         {
    2154          23 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
    2155             :         }
    2156             :     }
    2157             : 
    2158             :     // Is valid data checking needed or requested?
    2159        6054 :     if (bValidRangeValid || bCheckIsNan)
    2160             :     {
    2161        1365 :         T *ptrImage = static_cast<T *>(pImage);
    2162        2784 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2163             :         {
    2164             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2165        1419 :             size_t k = j * nBlockXSize;
    2166       99038 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2167             :             {
    2168             :                 // Check for nodata and nan.
    2169       97619 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2170        6301 :                     continue;
    2171       91318 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2172             :                 {
    2173        5737 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2174        5737 :                     continue;
    2175             :                 }
    2176             :                 // Check for valid_range.
    2177       85581 :                 if (bValidRangeValid)
    2178             :                 {
    2179       40986 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2180       40986 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2181       40983 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2182       40983 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2183             :                     {
    2184           4 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2185             :                     }
    2186             :                 }
    2187             :             }
    2188             :         }
    2189             :     }
    2190             : 
    2191             :     // If minimum longitude is > 180, subtract 360 from all.
    2192             :     // If not, disable checking for further calls (check just once).
    2193             :     // Only check first and last block elements since lon must be monotonic.
    2194        6054 :     const bool bIsSigned = std::numeric_limits<T>::is_signed;
    2195        5665 :     if (bCheckLongitude && bIsSigned &&
    2196          11 :         !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
    2197          10 :         !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
    2198        2838 :                     m_dfNoDataValue) &&
    2199          10 :         std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
    2200             :     {
    2201           0 :         T *ptrImage = static_cast<T *>(pImage);
    2202           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2203             :         {
    2204           0 :             size_t k = j * nBlockXSize;
    2205           0 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2206             :             {
    2207           0 :                 if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2208           0 :                     ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
    2209             :             }
    2210             :         }
    2211             :     }
    2212             :     else
    2213             :     {
    2214        6054 :         bCheckLongitude = false;
    2215             :     }
    2216        6054 : }
    2217             : 
    2218             : /************************************************************************/
    2219             : /*                            CheckDataCpx()                            */
    2220             : /************************************************************************/
    2221             : template <class T>
    2222          25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
    2223             :                                     size_t nTmpBlockXSize,
    2224             :                                     size_t nTmpBlockYSize, bool bCheckIsNan)
    2225             : {
    2226          25 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2227             : 
    2228             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2229             :     // the data this is because partial blocks are not arranged the same way in
    2230             :     // netcdf and gdal.
    2231          25 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2232             :     {
    2233           0 :         T *ptrWrite = static_cast<T *>(pImage);
    2234           0 :         T *ptrRead = static_cast<T *>(pImageNC);
    2235           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++,
    2236           0 :                     ptrWrite += (2 * nBlockXSize),
    2237           0 :                     ptrRead += (2 * nTmpBlockXSize))
    2238             :         {
    2239           0 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
    2240             :         }
    2241             :     }
    2242             : 
    2243             :     // Is valid data checking needed or requested?
    2244          25 :     if (bValidRangeValid || bCheckIsNan)
    2245             :     {
    2246           0 :         T *ptrImage = static_cast<T *>(pImage);
    2247           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2248             :         {
    2249             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2250           0 :             size_t k = 2 * j * nBlockXSize;
    2251           0 :             for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
    2252             :             {
    2253             :                 // Check for nodata and nan.
    2254           0 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2255           0 :                     continue;
    2256           0 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2257             :                 {
    2258           0 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2259           0 :                     continue;
    2260             :                 }
    2261             :                 // Check for valid_range.
    2262           0 :                 if (bValidRangeValid)
    2263             :                 {
    2264           0 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2265           0 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2266           0 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2267           0 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2268             :                     {
    2269           0 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2270             :                     }
    2271             :                 }
    2272             :             }
    2273             :         }
    2274             :     }
    2275          25 : }
    2276             : 
    2277             : /************************************************************************/
    2278             : /*                          FetchNetcdfChunk()                          */
    2279             : /************************************************************************/
    2280             : 
    2281        6079 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
    2282             :                                         void *pImage)
    2283             : {
    2284        6079 :     size_t start[MAX_NC_DIMS] = {};
    2285        6079 :     size_t edge[MAX_NC_DIMS] = {};
    2286             : 
    2287        6079 :     start[nBandXPos] = xstart;
    2288        6079 :     edge[nBandXPos] = nBlockXSize;
    2289        6079 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2290           6 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2291        6079 :     if (nBandYPos >= 0)
    2292             :     {
    2293        6075 :         start[nBandYPos] = ystart;
    2294        6075 :         edge[nBandYPos] = nBlockYSize;
    2295        6075 :         if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2296           4 :             edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2297             :     }
    2298        6079 :     const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
    2299             : 
    2300             : #ifdef NCDF_DEBUG
    2301             :     CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
    2302             :              start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
    2303             :              edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
    2304             : #endif
    2305             : 
    2306        6079 :     int nd = 0;
    2307        6079 :     nc_inq_varndims(cdfid, nZId, &nd);
    2308        6079 :     if (nd == 3)
    2309             :     {
    2310        1120 :         start[panBandZPos[0]] = nLevel;  // z
    2311        1120 :         edge[panBandZPos[0]] = 1;
    2312             :     }
    2313             : 
    2314             :     // Compute multidimention band position.
    2315             :     //
    2316             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2317             :     // if Data[2,3,4,x,y]
    2318             :     //
    2319             :     //  BandPos0 = (nBand) / (3*4)
    2320             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2321             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2322        6079 :     if (nd > 3)
    2323             :     {
    2324         160 :         int Sum = -1;
    2325         160 :         int Taken = 0;
    2326         480 :         for (int i = 0; i < nd - 2; i++)
    2327             :         {
    2328         320 :             if (i != nd - 2 - 1)
    2329             :             {
    2330         160 :                 Sum = 1;
    2331         320 :                 for (int j = i + 1; j < nd - 2; j++)
    2332             :                 {
    2333         160 :                     Sum *= panBandZLev[j];
    2334             :                 }
    2335         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2336         160 :                 edge[panBandZPos[i]] = 1;
    2337             :             }
    2338             :             else
    2339             :             {
    2340         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2341         160 :                 edge[panBandZPos[i]] = 1;
    2342             :             }
    2343         320 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2344             :         }
    2345             :     }
    2346             : 
    2347             :     // Make sure we are in data mode.
    2348        6079 :     cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2349             : 
    2350             :     // If this block is not a full block in the x axis, we need to
    2351             :     // re-arrange the data because partial blocks are not arranged the
    2352             :     // same way in netcdf and gdal, so we first we read the netcdf data at
    2353             :     // the end of the gdal block buffer then re-arrange rows in CheckData().
    2354        6079 :     void *pImageNC = pImage;
    2355        6079 :     if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
    2356             :     {
    2357           6 :         pImageNC = static_cast<GByte *>(pImage) +
    2358           6 :                    ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
    2359          12 :                      edge[nBandXPos] * nYChunkSize) *
    2360           6 :                     GDALGetDataTypeSizeBytes(eDataType));
    2361             :     }
    2362             : 
    2363             :     // Read data according to type.
    2364             :     int status;
    2365        6079 :     if (eDataType == GDT_UInt8)
    2366             :     {
    2367        3205 :         if (bSignedData)
    2368             :         {
    2369           0 :             status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2370             :                                        static_cast<signed char *>(pImageNC));
    2371           0 :             if (status == NC_NOERR)
    2372           0 :                 CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2373             :                                        nYChunkSize, false);
    2374             :         }
    2375             :         else
    2376             :         {
    2377        3205 :             status = nc_get_vara_uchar(cdfid, nZId, start, edge,
    2378             :                                        static_cast<unsigned char *>(pImageNC));
    2379        3205 :             if (status == NC_NOERR)
    2380        3205 :                 CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
    2381             :                                          nYChunkSize, false);
    2382             :         }
    2383             :     }
    2384        2874 :     else if (eDataType == GDT_Int8)
    2385             :     {
    2386          60 :         status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2387             :                                    static_cast<signed char *>(pImageNC));
    2388          60 :         if (status == NC_NOERR)
    2389          60 :             CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2390             :                                    nYChunkSize, false);
    2391             :     }
    2392        2814 :     else if (nc_datatype == NC_SHORT)
    2393             :     {
    2394         487 :         status = nc_get_vara_short(cdfid, nZId, start, edge,
    2395             :                                    static_cast<short *>(pImageNC));
    2396         487 :         if (status == NC_NOERR)
    2397             :         {
    2398         487 :             if (eDataType == GDT_Int16)
    2399             :             {
    2400         484 :                 CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
    2401             :                                   nYChunkSize, false);
    2402             :             }
    2403             :             else
    2404             :             {
    2405           3 :                 CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
    2406             :                                    nYChunkSize, false);
    2407             :             }
    2408             :         }
    2409             :     }
    2410        2327 :     else if (eDataType == GDT_Int32)
    2411             :     {
    2412             : #if SIZEOF_UNSIGNED_LONG == 4
    2413             :         status = nc_get_vara_long(cdfid, nZId, start, edge,
    2414             :                                   static_cast<long *>(pImageNC));
    2415             :         if (status == NC_NOERR)
    2416             :             CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2417             :                             false);
    2418             : #else
    2419         912 :         status = nc_get_vara_int(cdfid, nZId, start, edge,
    2420             :                                  static_cast<int *>(pImageNC));
    2421         912 :         if (status == NC_NOERR)
    2422         912 :             CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2423             :                            false);
    2424             : #endif
    2425             :     }
    2426        1415 :     else if (eDataType == GDT_Float32)
    2427             :     {
    2428        1278 :         status = nc_get_vara_float(cdfid, nZId, start, edge,
    2429             :                                    static_cast<float *>(pImageNC));
    2430        1278 :         if (status == NC_NOERR)
    2431        1278 :             CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2432             :                              true);
    2433             :     }
    2434         137 :     else if (eDataType == GDT_Float64)
    2435             :     {
    2436          86 :         status = nc_get_vara_double(cdfid, nZId, start, edge,
    2437             :                                     static_cast<double *>(pImageNC));
    2438          86 :         if (status == NC_NOERR)
    2439          86 :             CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2440             :                               true);
    2441             :     }
    2442          51 :     else if (eDataType == GDT_UInt16)
    2443             :     {
    2444           6 :         status = nc_get_vara_ushort(cdfid, nZId, start, edge,
    2445             :                                     static_cast<unsigned short *>(pImageNC));
    2446           6 :         if (status == NC_NOERR)
    2447           6 :             CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
    2448             :                                       nYChunkSize, false);
    2449             :     }
    2450          45 :     else if (eDataType == GDT_UInt32)
    2451             :     {
    2452           6 :         status = nc_get_vara_uint(cdfid, nZId, start, edge,
    2453             :                                   static_cast<unsigned int *>(pImageNC));
    2454           6 :         if (status == NC_NOERR)
    2455           6 :             CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
    2456             :                                     nYChunkSize, false);
    2457             :     }
    2458          39 :     else if (eDataType == GDT_Int64)
    2459             :     {
    2460           7 :         status = nc_get_vara_longlong(cdfid, nZId, start, edge,
    2461             :                                       static_cast<long long *>(pImageNC));
    2462           7 :         if (status == NC_NOERR)
    2463           7 :             CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
    2464             :                                     nYChunkSize, false);
    2465             :     }
    2466          32 :     else if (eDataType == GDT_UInt64)
    2467             :     {
    2468             :         status =
    2469           7 :             nc_get_vara_ulonglong(cdfid, nZId, start, edge,
    2470             :                                   static_cast<unsigned long long *>(pImageNC));
    2471           7 :         if (status == NC_NOERR)
    2472           7 :             CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
    2473             :                                      nYChunkSize, false);
    2474             :     }
    2475          25 :     else if (eDataType == GDT_CInt16)
    2476             :     {
    2477           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2478           0 :         if (status == NC_NOERR)
    2479           0 :             CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2480             :                                 false);
    2481             :     }
    2482          25 :     else if (eDataType == GDT_CInt32)
    2483             :     {
    2484           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2485           0 :         if (status == NC_NOERR)
    2486           0 :             CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2487             :                               false);
    2488             :     }
    2489          25 :     else if (eDataType == GDT_CFloat32)
    2490             :     {
    2491          20 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2492          20 :         if (status == NC_NOERR)
    2493          20 :             CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2494             :                                 false);
    2495             :     }
    2496           5 :     else if (eDataType == GDT_CFloat64)
    2497             :     {
    2498           5 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2499           5 :         if (status == NC_NOERR)
    2500           5 :             CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2501             :                                  false);
    2502             :     }
    2503             : 
    2504             :     else
    2505           0 :         status = NC_EBADTYPE;
    2506             : 
    2507        6079 :     if (status != NC_NOERR)
    2508             :     {
    2509           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2510             :                  "netCDF chunk fetch failed: #%d (%s)", status,
    2511             :                  nc_strerror(status));
    2512           0 :         return false;
    2513             :     }
    2514        6079 :     return true;
    2515             : }
    2516             : 
    2517             : /************************************************************************/
    2518             : /*                             IReadBlock()                             */
    2519             : /************************************************************************/
    2520             : 
    2521        6079 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2522             :                                     void *pImage)
    2523             : 
    2524             : {
    2525       12158 :     CPLMutexHolderD(&hNCMutex);
    2526             : 
    2527             :     // Locate X, Y and Z position in the array.
    2528             : 
    2529        6079 :     size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2530        6079 :     size_t ystart = 0;
    2531             : 
    2532             :     // Check y order.
    2533        6079 :     if (nBandYPos >= 0)
    2534             :     {
    2535        6075 :         auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
    2536        6075 :         if (poGDS->bBottomUp)
    2537             :         {
    2538        5118 :             if (nBlockYSize == 1)
    2539             :             {
    2540        5105 :                 ystart = nRasterYSize - 1 - nBlockYOff;
    2541             :             }
    2542             :             else
    2543             :             {
    2544             :                 // in GDAL space
    2545          13 :                 ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2546             :                 const size_t yend =
    2547          26 :                     std::min(ystart + nBlockYSize - 1,
    2548          13 :                              static_cast<size_t>(nRasterYSize - 1));
    2549             :                 // in netCDF space
    2550          13 :                 const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
    2551          13 :                 const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
    2552          13 :                 const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
    2553          13 :                 const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
    2554             : 
    2555             :                 const auto firstKey = netCDFDataset::ChunkKey(
    2556          13 :                     nBlockXOff, nFirstChunkBlock, nBand);
    2557             :                 const auto secondKey =
    2558          13 :                     netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
    2559             : 
    2560             :                 // Retrieve data from the one or 2 needed netCDF chunks
    2561          13 :                 std::shared_ptr<std::vector<GByte>> firstChunk;
    2562          13 :                 std::shared_ptr<std::vector<GByte>> secondChunk;
    2563          13 :                 if (poGDS->poChunkCache)
    2564             :                 {
    2565          13 :                     poGDS->poChunkCache->tryGet(firstKey, firstChunk);
    2566          13 :                     if (firstKey != secondKey)
    2567           6 :                         poGDS->poChunkCache->tryGet(secondKey, secondChunk);
    2568             :                 }
    2569             :                 const size_t nChunkLineSize =
    2570          13 :                     static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
    2571          13 :                     nBlockXSize;
    2572          13 :                 const size_t nChunkSize = nChunkLineSize * nBlockYSize;
    2573          13 :                 if (!firstChunk)
    2574             :                 {
    2575          11 :                     firstChunk.reset(new std::vector<GByte>(nChunkSize));
    2576          11 :                     if (!FetchNetcdfChunk(xstart,
    2577          11 :                                           nFirstChunkBlock * nBlockYSize,
    2578          11 :                                           firstChunk.get()->data()))
    2579           0 :                         return CE_Failure;
    2580          11 :                     if (poGDS->poChunkCache)
    2581          11 :                         poGDS->poChunkCache->insert(firstKey, firstChunk);
    2582             :                 }
    2583          13 :                 if (!secondChunk && firstKey != secondKey)
    2584             :                 {
    2585           2 :                     secondChunk.reset(new std::vector<GByte>(nChunkSize));
    2586           2 :                     if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
    2587           2 :                                           secondChunk.get()->data()))
    2588           0 :                         return CE_Failure;
    2589           2 :                     if (poGDS->poChunkCache)
    2590           2 :                         poGDS->poChunkCache->insert(secondKey, secondChunk);
    2591             :                 }
    2592             : 
    2593             :                 // Assemble netCDF chunks into GDAL block
    2594          13 :                 GByte *pabyImage = static_cast<GByte *>(pImage);
    2595          13 :                 const size_t nFirstChunkBlockLine =
    2596          13 :                     nFirstChunkBlock * nBlockYSize;
    2597          13 :                 const size_t nLastChunkBlockLine =
    2598          13 :                     nLastChunkBlock * nBlockYSize;
    2599         146 :                 for (size_t iLine = ystart; iLine <= yend; iLine++)
    2600             :                 {
    2601         133 :                     const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
    2602         133 :                     const size_t nChunkY = nLineFromBottom / nBlockYSize;
    2603         133 :                     if (nChunkY == nFirstChunkBlock)
    2604             :                     {
    2605         121 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2606         121 :                                firstChunk.get()->data() +
    2607         121 :                                    (nLineFromBottom - nFirstChunkBlockLine) *
    2608             :                                        nChunkLineSize,
    2609             :                                nChunkLineSize);
    2610             :                     }
    2611             :                     else
    2612             :                     {
    2613          12 :                         CPLAssert(nChunkY == nLastChunkBlock);
    2614          12 :                         assert(secondChunk);
    2615          12 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2616          12 :                                secondChunk.get()->data() +
    2617          12 :                                    (nLineFromBottom - nLastChunkBlockLine) *
    2618             :                                        nChunkLineSize,
    2619             :                                nChunkLineSize);
    2620             :                     }
    2621             :                 }
    2622          13 :                 return CE_None;
    2623             :             }
    2624             :         }
    2625             :         else
    2626             :         {
    2627         957 :             ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2628             :         }
    2629             :     }
    2630             : 
    2631        6066 :     return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
    2632             : }
    2633             : 
    2634             : /************************************************************************/
    2635             : /*                            IWriteBlock()                             */
    2636             : /************************************************************************/
    2637             : 
    2638        6621 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
    2639             :                                      void *pImage)
    2640             : {
    2641       13242 :     CPLMutexHolderD(&hNCMutex);
    2642             : 
    2643             : #ifdef NCDF_DEBUG
    2644             :     if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
    2645             :         CPLDebug("GDAL_netCDF",
    2646             :                  "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
    2647             :                  nBlockXOff, nBlockYOff, nBand);
    2648             : #endif
    2649             : 
    2650        6621 :     int nd = 0;
    2651        6621 :     nc_inq_varndims(cdfid, nZId, &nd);
    2652             : 
    2653             :     // Locate X, Y and Z position in the array.
    2654             : 
    2655             :     size_t start[MAX_NC_DIMS];
    2656        6621 :     memset(start, 0, sizeof(start));
    2657        6621 :     start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2658             : 
    2659             :     // check y order.
    2660        6621 :     if (cpl::down_cast<netCDFDataset *>(poDS)->bBottomUp)
    2661             :     {
    2662        6557 :         if (nBlockYSize == 1)
    2663             :         {
    2664        6557 :             start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
    2665             :         }
    2666             :         else
    2667             :         {
    2668           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2669             :                      "nBlockYSize = %d, only 1 supported when "
    2670             :                      "writing bottom-up dataset",
    2671             :                      nBlockYSize);
    2672           0 :             return CE_Failure;
    2673             :         }
    2674             :     }
    2675             :     else
    2676             :     {
    2677          64 :         start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y
    2678             :     }
    2679             : 
    2680        6621 :     size_t edge[MAX_NC_DIMS] = {};
    2681             : 
    2682        6621 :     edge[nBandXPos] = nBlockXSize;
    2683        6621 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2684           0 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2685        6621 :     edge[nBandYPos] = nBlockYSize;
    2686        6621 :     if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2687           0 :         edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2688             : 
    2689        6621 :     if (nd == 3)
    2690             :     {
    2691         630 :         start[panBandZPos[0]] = nLevel;  // z
    2692         630 :         edge[panBandZPos[0]] = 1;
    2693             :     }
    2694             : 
    2695             :     // Compute multidimention band position.
    2696             :     //
    2697             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2698             :     // if Data[2,3,4,x,y]
    2699             :     //
    2700             :     //  BandPos0 = (nBand) / (3*4)
    2701             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2702             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2703        6621 :     if (nd > 3)
    2704             :     {
    2705         178 :         int Sum = -1;
    2706         178 :         int Taken = 0;
    2707         534 :         for (int i = 0; i < nd - 2; i++)
    2708             :         {
    2709         356 :             if (i != nd - 2 - 1)
    2710             :             {
    2711         178 :                 Sum = 1;
    2712         356 :                 for (int j = i + 1; j < nd - 2; j++)
    2713             :                 {
    2714         178 :                     Sum *= panBandZLev[j];
    2715             :                 }
    2716         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2717         178 :                 edge[panBandZPos[i]] = 1;
    2718             :             }
    2719             :             else
    2720             :             {
    2721         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2722         178 :                 edge[panBandZPos[i]] = 1;
    2723             :             }
    2724         356 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2725             :         }
    2726             :     }
    2727             : 
    2728             :     // Make sure we are in data mode.
    2729        6621 :     cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2730             : 
    2731             :     // Copy data according to type.
    2732        6621 :     int status = 0;
    2733        6621 :     if (eDataType == GDT_UInt8)
    2734             :     {
    2735        6022 :         if (bSignedData)
    2736           0 :             status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2737             :                                        static_cast<signed char *>(pImage));
    2738             :         else
    2739        6022 :             status = nc_put_vara_uchar(cdfid, nZId, start, edge,
    2740             :                                        static_cast<unsigned char *>(pImage));
    2741             :     }
    2742         599 :     else if (eDataType == GDT_Int8)
    2743             :     {
    2744          40 :         status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2745             :                                    static_cast<signed char *>(pImage));
    2746             :     }
    2747         559 :     else if (nc_datatype == NC_SHORT)
    2748             :     {
    2749         101 :         status = nc_put_vara_short(cdfid, nZId, start, edge,
    2750             :                                    static_cast<short *>(pImage));
    2751             :     }
    2752         458 :     else if (eDataType == GDT_Int32)
    2753             :     {
    2754         210 :         status = nc_put_vara_int(cdfid, nZId, start, edge,
    2755             :                                  static_cast<int *>(pImage));
    2756             :     }
    2757         248 :     else if (eDataType == GDT_Float32)
    2758             :     {
    2759         168 :         status = nc_put_vara_float(cdfid, nZId, start, edge,
    2760             :                                    static_cast<float *>(pImage));
    2761             :     }
    2762          80 :     else if (eDataType == GDT_Float64)
    2763             :     {
    2764          50 :         status = nc_put_vara_double(cdfid, nZId, start, edge,
    2765             :                                     static_cast<double *>(pImage));
    2766             :     }
    2767          42 :     else if (eDataType == GDT_UInt16 &&
    2768          12 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2769             :     {
    2770          12 :         status = nc_put_vara_ushort(cdfid, nZId, start, edge,
    2771             :                                     static_cast<unsigned short *>(pImage));
    2772             :     }
    2773          30 :     else if (eDataType == GDT_UInt32 &&
    2774          12 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2775             :     {
    2776          12 :         status = nc_put_vara_uint(cdfid, nZId, start, edge,
    2777             :                                   static_cast<unsigned int *>(pImage));
    2778             :     }
    2779           9 :     else if (eDataType == GDT_UInt64 &&
    2780           3 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2781             :     {
    2782             :         status =
    2783           3 :             nc_put_vara_ulonglong(cdfid, nZId, start, edge,
    2784             :                                   static_cast<unsigned long long *>(pImage));
    2785             :     }
    2786           6 :     else if (eDataType == GDT_Int64 &&
    2787           3 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2788             :     {
    2789           3 :         status = nc_put_vara_longlong(cdfid, nZId, start, edge,
    2790             :                                       static_cast<long long *>(pImage));
    2791             :     }
    2792             :     else
    2793             :     {
    2794           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2795             :                  "The NetCDF driver does not support GDAL data type %d",
    2796           0 :                  eDataType);
    2797           0 :         status = NC_EBADTYPE;
    2798             :     }
    2799        6621 :     NCDF_ERR(status);
    2800             : 
    2801        6621 :     if (status != NC_NOERR)
    2802             :     {
    2803           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2804             :                  "netCDF scanline write failed: %s", nc_strerror(status));
    2805           0 :         return CE_Failure;
    2806             :     }
    2807             : 
    2808        6621 :     return CE_None;
    2809             : }
    2810             : 
    2811             : /************************************************************************/
    2812             : /* ==================================================================== */
    2813             : /*                              netCDFDataset                           */
    2814             : /* ==================================================================== */
    2815             : /************************************************************************/
    2816             : 
    2817             : /************************************************************************/
    2818             : /*                           netCDFDataset()                            */
    2819             : /************************************************************************/
    2820             : 
    2821        1331 : netCDFDataset::netCDFDataset()
    2822             :     :
    2823             : // Basic dataset vars.
    2824             : #ifdef ENABLE_NCDUMP
    2825             :       bFileToDestroyAtClosing(false),
    2826             : #endif
    2827             :       cdfid(-1), nSubDatasets(0), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
    2828             :       bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
    2829             :       pszCFCoordinates(nullptr), bSGSupport(false),
    2830        1331 :       eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
    2831        1331 :       GeometryScribe(vcdf, this->generateLogName()),
    2832        1331 :       FieldScribe(vcdf, this->generateLogName()),
    2833        2662 :       bufManager(CPLGetUsablePhysicalRAM() / 5),
    2834             : 
    2835             :       // projection/GT.
    2836             :       nXDimID(-1), nYDimID(-1), bIsProjected(false),
    2837             :       bIsGeographic(false),  // Can be not projected, and also not geographic
    2838             :       // State vars.
    2839             :       bDefineMode(true), bAddedGridMappingRef(false),
    2840             : 
    2841             :       // Create vars.
    2842             :       eCompress(NCDF_COMPRESS_NONE), nZLevel(NCDF_DEFLATE_LEVEL),
    2843        3993 :       bChunking(false), nCreateMode(NC_CLOBBER), bSignedData(true)
    2844             : {
    2845        1331 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2846             : 
    2847             :     // Set buffers
    2848        1331 :     bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
    2849        1331 :     bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
    2850        1331 : }
    2851             : 
    2852             : /************************************************************************/
    2853             : /*                           ~netCDFDataset()                           */
    2854             : /************************************************************************/
    2855             : 
    2856        2549 : netCDFDataset::~netCDFDataset()
    2857             : 
    2858             : {
    2859        1331 :     netCDFDataset::Close();
    2860        2549 : }
    2861             : 
    2862             : /************************************************************************/
    2863             : /*                               Close()                                */
    2864             : /************************************************************************/
    2865             : 
    2866        2234 : CPLErr netCDFDataset::Close(GDALProgressFunc, void *)
    2867             : {
    2868        2234 :     CPLErr eErr = CE_None;
    2869        2234 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    2870             :     {
    2871        2662 :         CPLMutexHolderD(&hNCMutex);
    2872             : 
    2873             : #ifdef NCDF_DEBUG
    2874             :         CPLDebug("GDAL_netCDF",
    2875             :                  "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
    2876             :                  osFilename.c_str());
    2877             : #endif
    2878             : 
    2879             :         // Write data related to geotransform
    2880        1641 :         if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
    2881         310 :             (m_bHasProjection || m_bHasGeoTransform))
    2882             :         {
    2883             :             // Ensure projection is written if GeoTransform OR Projection are
    2884             :             // missing.
    2885          37 :             if (!m_bAddedProjectionVarsDefs)
    2886             :             {
    2887           2 :                 AddProjectionVars(true, nullptr, nullptr);
    2888             :             }
    2889          37 :             AddProjectionVars(false, nullptr, nullptr);
    2890             :         }
    2891             : 
    2892        1331 :         if (netCDFDataset::FlushCache(true) != CE_None)
    2893           0 :             eErr = CE_Failure;
    2894             : 
    2895        1331 :         if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
    2896           0 :             eErr = CE_Failure;
    2897             : 
    2898        1333 :         for (size_t i = 0; i < apoVectorDatasets.size(); i++)
    2899           2 :             delete apoVectorDatasets[i];
    2900             : 
    2901             :         // Make sure projection variable is written to band variable.
    2902        1331 :         if (GetAccess() == GA_Update && !bAddedGridMappingRef)
    2903             :         {
    2904         340 :             if (!AddGridMappingRef())
    2905           0 :                 eErr = CE_Failure;
    2906             :         }
    2907             : 
    2908        1331 :         CPLFree(pszCFProjection);
    2909             : 
    2910        1331 :         if (cdfid > 0)
    2911             :         {
    2912             : #ifdef NCDF_DEBUG
    2913             :             CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
    2914             : #endif
    2915         729 :             int status = GDAL_nc_close(cdfid);
    2916             : #ifdef ENABLE_UFFD
    2917         729 :             NETCDF_UFFD_UNMAP(pCtx);
    2918             : #endif
    2919         729 :             NCDF_ERR(status);
    2920         729 :             if (status != NC_NOERR)
    2921           0 :                 eErr = CE_Failure;
    2922             :         }
    2923             : 
    2924        1331 :         if (fpVSIMEM)
    2925          15 :             VSIFCloseL(fpVSIMEM);
    2926             : 
    2927             : #ifdef ENABLE_NCDUMP
    2928        1331 :         if (bFileToDestroyAtClosing)
    2929           0 :             VSIUnlink(osFilename);
    2930             : #endif
    2931             : 
    2932        1331 :         if (GDALPamDataset::Close() != CE_None)
    2933           0 :             eErr = CE_Failure;
    2934             :     }
    2935        2234 :     return eErr;
    2936             : }
    2937             : 
    2938             : /************************************************************************/
    2939             : /*                           SetDefineMode()                            */
    2940             : /************************************************************************/
    2941       14837 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
    2942             : {
    2943             :     // Do nothing if already in new define mode
    2944             :     // or if dataset is in read-only mode or if dataset is true NC4 dataset.
    2945       15446 :     if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
    2946         609 :         eFormat == NCDF_FORMAT_NC4)
    2947       14398 :         return true;
    2948             : 
    2949         439 :     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
    2950         439 :              static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
    2951             : 
    2952         439 :     bDefineMode = bNewDefineMode;
    2953             : 
    2954             :     int status;
    2955         439 :     if (bDefineMode)
    2956         152 :         status = nc_redef(cdfid);
    2957             :     else
    2958         287 :         status = nc_enddef(cdfid);
    2959             : 
    2960         439 :     NCDF_ERR(status);
    2961         439 :     return status == NC_NOERR;
    2962             : }
    2963             : 
    2964             : /************************************************************************/
    2965             : /*                       GetMetadataDomainList()                        */
    2966             : /************************************************************************/
    2967             : 
    2968          26 : char **netCDFDataset::GetMetadataDomainList()
    2969             : {
    2970             :     char **papszDomains =
    2971          26 :         BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
    2972             :                                 GDAL_MDD_SUBDATASETS, nullptr);
    2973          27 :     for (const auto &kv : m_oMapDomainToJSon)
    2974           1 :         papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
    2975          26 :     return papszDomains;
    2976             : }
    2977             : 
    2978             : /************************************************************************/
    2979             : /*                            GetMetadata()                             */
    2980             : /************************************************************************/
    2981         450 : CSLConstList netCDFDataset::GetMetadata(const char *pszDomain)
    2982             : {
    2983         450 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, GDAL_MDD_SUBDATASETS))
    2984          47 :         return aosSubDatasets.List();
    2985             : 
    2986         403 :     if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
    2987             :     {
    2988           6 :         auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
    2989           6 :         if (iter != m_oMapDomainToJSon.end())
    2990           1 :             return iter->second.List();
    2991             :     }
    2992             : 
    2993         402 :     return GDALDataset::GetMetadata(pszDomain);
    2994             : }
    2995             : 
    2996             : /************************************************************************/
    2997             : /*                          SetMetadataItem()                           */
    2998             : /************************************************************************/
    2999             : 
    3000          43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    3001             :                                       const char *pszDomain)
    3002             : {
    3003          85 :     if (GetAccess() == GA_Update &&
    3004          85 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    3005             :     {
    3006          42 :         std::string osName(pszName);
    3007             : 
    3008             :         // Same logic as in CopyMetadata()
    3009          42 :         if (cpl::starts_with(osName, "NC_GLOBAL#"))
    3010           8 :             osName = osName.substr(strlen("NC_GLOBAL#"));
    3011          34 :         else if (strchr(osName.c_str(), '#') == nullptr)
    3012           5 :             osName = "GDAL_" + osName;
    3013             : 
    3014          84 :         if (cpl::starts_with(osName, "NETCDF_DIM_") ||
    3015          42 :             strchr(osName.c_str(), '#') != nullptr)
    3016             :         {
    3017             :             // do nothing
    3018          29 :             return CE_None;
    3019             :         }
    3020             :         else
    3021             :         {
    3022          13 :             SetDefineMode(true);
    3023             : 
    3024          13 :             if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
    3025          13 :                 return CE_Failure;
    3026             :         }
    3027             :     }
    3028             : 
    3029           1 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    3030             : }
    3031             : 
    3032             : /************************************************************************/
    3033             : /*                            SetMetadata()                             */
    3034             : /************************************************************************/
    3035             : 
    3036           8 : CPLErr netCDFDataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
    3037             : {
    3038          13 :     if (GetAccess() == GA_Update &&
    3039           5 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    3040             :     {
    3041             :         // We don't handle metadata item removal for now
    3042          50 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    3043             :              ++papszIter)
    3044             :         {
    3045          42 :             char *pszName = nullptr;
    3046          42 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    3047          42 :             if (pszName && pszValue)
    3048          42 :                 SetMetadataItem(pszName, pszValue);
    3049          42 :             CPLFree(pszName);
    3050             :         }
    3051           8 :         return CE_None;
    3052             :     }
    3053           0 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    3054             : }
    3055             : 
    3056             : /************************************************************************/
    3057             : /*                           GetSpatialRef()                            */
    3058             : /************************************************************************/
    3059             : 
    3060         252 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
    3061             : {
    3062         252 :     if (m_bHasProjection)
    3063         121 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3064             : 
    3065         131 :     return GDALPamDataset::GetSpatialRef();
    3066             : }
    3067             : 
    3068             : /************************************************************************/
    3069             : /*                           FetchCopyParam()                           */
    3070             : /************************************************************************/
    3071             : 
    3072         452 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
    3073             :                                      const char *pszParam, double dfDefault,
    3074             :                                      bool *pbFound) const
    3075             : 
    3076             : {
    3077         904 :     std::string osTemp = CPLOPrintf("%s#%s", pszGridMappingValue, pszParam);
    3078         452 :     const char *pszValue = aosMetadata.FetchNameValue(osTemp.c_str());
    3079             : 
    3080         452 :     if (pbFound)
    3081             :     {
    3082         452 :         *pbFound = (pszValue != nullptr);
    3083             :     }
    3084             : 
    3085         452 :     if (pszValue)
    3086             :     {
    3087           0 :         return CPLAtofM(pszValue);
    3088             :     }
    3089             : 
    3090         452 :     return dfDefault;
    3091             : }
    3092             : 
    3093             : /************************************************************************/
    3094             : /*                       FetchStandardParallels()                       */
    3095             : /************************************************************************/
    3096             : 
    3097             : std::vector<std::string>
    3098           0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) const
    3099             : {
    3100             :     // cf-1.0 tags
    3101           0 :     const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
    3102             : 
    3103           0 :     std::vector<std::string> ret;
    3104           0 :     if (pszValue != nullptr)
    3105             :     {
    3106           0 :         CPLStringList aosValues;
    3107           0 :         if (pszValue[0] != '{' &&
    3108           0 :             CPLString(pszValue).Trim().find(' ') != std::string::npos)
    3109             :         {
    3110             :             // Some files like
    3111             :             // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
    3112             :             // do not use standard formatting for arrays, but just space
    3113             :             // separated syntax
    3114           0 :             aosValues = CSLTokenizeString2(pszValue, " ", 0);
    3115             :         }
    3116             :         else
    3117             :         {
    3118           0 :             aosValues = NCDFTokenizeArray(pszValue);
    3119             :         }
    3120           0 :         for (int i = 0; i < aosValues.size(); i++)
    3121             :         {
    3122           0 :             ret.push_back(aosValues[i]);
    3123             :         }
    3124             :     }
    3125             :     // Try gdal tags.
    3126             :     else
    3127             :     {
    3128           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
    3129             : 
    3130           0 :         if (pszValue != nullptr)
    3131           0 :             ret.push_back(pszValue);
    3132             : 
    3133           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
    3134             : 
    3135           0 :         if (pszValue != nullptr)
    3136           0 :             ret.push_back(pszValue);
    3137             :     }
    3138             : 
    3139           0 :     return ret;
    3140             : }
    3141             : 
    3142             : /************************************************************************/
    3143             : /*                             FetchAttr()                              */
    3144             : /************************************************************************/
    3145             : 
    3146        4430 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
    3147             :                                      const char *pszAttr) const
    3148             : 
    3149             : {
    3150        4430 :     auto oKey = CPLOPrintf("%s#%s", pszVarFullName, pszAttr);
    3151        4430 :     const char *pszValue = aosMetadata.FetchNameValue(oKey.c_str());
    3152        8860 :     return pszValue;
    3153             : }
    3154             : 
    3155        2883 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
    3156             :                                      const char *pszAttr) const
    3157             : 
    3158             : {
    3159        2883 :     std::string osFullName;
    3160        2883 :     NCDFGetVarFullName(nGroupId, nVarId, osFullName);
    3161        2883 :     const char *pszValue = FetchAttr(osFullName.c_str(), pszAttr);
    3162        5766 :     return pszValue;
    3163             : }
    3164             : 
    3165             : /************************************************************************/
    3166             : /*                         IsDifferenceBelow()                          */
    3167             : /************************************************************************/
    3168             : 
    3169        1199 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
    3170             : {
    3171        1199 :     const double dfAbsDiff = fabs(dfA - dfB);
    3172        1199 :     return dfAbsDiff <= dfError;
    3173             : }
    3174             : 
    3175             : /************************************************************************/
    3176             : /*                        SetProjectionFromVar()                        */
    3177             : /************************************************************************/
    3178         639 : void netCDFDataset::SetProjectionFromVar(
    3179             :     int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
    3180             :     std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
    3181             :     std::vector<std::string> *paosRemovedMDItems)
    3182             : {
    3183         639 :     bool bGotGeogCS = false;
    3184         639 :     bool bGotCfSRS = false;
    3185         639 :     bool bGotCfWktSRS = false;
    3186         639 :     bool bGotGdalSRS = false;
    3187         639 :     bool bGotCfGT = false;
    3188         639 :     bool bGotGdalGT = false;
    3189             : 
    3190             :     // These values from CF metadata.
    3191         639 :     OGRSpatialReference oSRS;
    3192         639 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3193         639 :     size_t xdim = nRasterXSize;
    3194         639 :     size_t ydim = nRasterYSize;
    3195             : 
    3196             :     // These values from GDAL metadata.
    3197         639 :     const char *pszWKT = nullptr;
    3198         639 :     const char *pszGeoTransform = nullptr;
    3199             : 
    3200         639 :     netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.
    3201             : 
    3202         639 :     CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
    3203             :              nVarId);
    3204             : 
    3205             :     // Get x/y range information.
    3206             : 
    3207             :     // Temp variables to use in SetGeoTransform() and SetProjection().
    3208         639 :     GDALGeoTransform tmpGT;
    3209             : 
    3210             :     // Look for grid_mapping metadata.
    3211         639 :     const char *pszValue = pszGivenGM;
    3212         639 :     CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may
    3213             :     // point to it
    3214         639 :     if (pszValue == nullptr)
    3215             :     {
    3216         595 :         pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
    3217         595 :         if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
    3218             :         {
    3219             :             // Expanded form of grid_mapping
    3220             :             // e.g. "crsOSGB: x y crsWGS84: lat lon"
    3221             :             // Pickup the grid_mapping whose coordinates are dimensions of the
    3222             :             // variable
    3223           6 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
    3224           3 :             if ((aosTokens.size() % 3) == 0)
    3225             :             {
    3226           3 :                 for (int i = 0; i < aosTokens.size() / 3; i++)
    3227             :                 {
    3228           3 :                     if (CSLFindString(poDS->papszDimName,
    3229           9 :                                       aosTokens[3 * i + 1]) >= 0 &&
    3230           3 :                         CSLFindString(poDS->papszDimName,
    3231           3 :                                       aosTokens[3 * i + 2]) >= 0)
    3232             :                     {
    3233           3 :                         osTmpGridMapping = aosTokens[3 * i];
    3234           6 :                         if (!osTmpGridMapping.empty() &&
    3235           3 :                             osTmpGridMapping.back() == ':')
    3236             :                         {
    3237           3 :                             osTmpGridMapping.resize(osTmpGridMapping.size() -
    3238             :                                                     1);
    3239             :                         }
    3240           3 :                         pszValue = osTmpGridMapping.c_str();
    3241           3 :                         break;
    3242             :                     }
    3243             :                 }
    3244             :             }
    3245             :         }
    3246             :     }
    3247         639 :     std::string osGridMappingValue = pszValue ? pszValue : "";
    3248             : 
    3249         639 :     if (!osGridMappingValue.empty())
    3250             :     {
    3251             :         // Read grid_mapping metadata.
    3252         266 :         int nProjGroupID = -1;
    3253         266 :         int nProjVarID = -1;
    3254         266 :         if (NCDFResolveVar(nGroupId, osGridMappingValue.c_str(), &nProjGroupID,
    3255         266 :                            &nProjVarID) == CE_None)
    3256             :         {
    3257         264 :             poDS->ReadAttributes(nProjGroupID, nProjVarID);
    3258             : 
    3259             :             // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
    3260         264 :             if (NCDFGetVarFullName(nProjGroupID, nProjVarID,
    3261         264 :                                    osGridMappingValue) == CE_None)
    3262             :             {
    3263         264 :                 CPLDebug("GDAL_netCDF", "got grid_mapping %s",
    3264             :                          osGridMappingValue.c_str());
    3265             :                 pszWKT =
    3266         264 :                     FetchAttr(osGridMappingValue.c_str(), NCDF_SPATIAL_REF);
    3267         264 :                 if (!pszWKT)
    3268             :                 {
    3269             :                     pszWKT =
    3270          36 :                         FetchAttr(osGridMappingValue.c_str(), NCDF_CRS_WKT);
    3271             :                 }
    3272             :                 else
    3273             :                 {
    3274         228 :                     bGotGdalSRS = true;
    3275         228 :                     CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
    3276             :                 }
    3277         264 :                 if (pszWKT)
    3278             :                 {
    3279         234 :                     if (!bGotGdalSRS)
    3280             :                     {
    3281           6 :                         bGotCfWktSRS = true;
    3282           6 :                         CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3283             :                     }
    3284         234 :                     if (returnProjStr != nullptr)
    3285             :                     {
    3286          42 :                         (*returnProjStr) = std::string(pszWKT);
    3287             :                     }
    3288             :                     else
    3289             :                     {
    3290         192 :                         m_bAddedProjectionVarsDefs = true;
    3291         192 :                         m_bAddedProjectionVarsData = true;
    3292         384 :                         OGRSpatialReference oSRSTmp;
    3293         192 :                         oSRSTmp.SetAxisMappingStrategy(
    3294             :                             OAMS_TRADITIONAL_GIS_ORDER);
    3295         192 :                         oSRSTmp.importFromWkt(pszWKT);
    3296         192 :                         SetSpatialRefNoUpdate(&oSRSTmp);
    3297             :                     }
    3298         234 :                     pszGeoTransform = FetchAttr(osGridMappingValue.c_str(),
    3299             :                                                 NCDF_GEOTRANSFORM);
    3300             :                 }
    3301             :             }
    3302             :         }
    3303             :         else
    3304             :         {
    3305           4 :             std::string osVarName = "unknown";
    3306           2 :             NCDFGetVarFullName(nGroupId, nVarId, osVarName);
    3307             : 
    3308           2 :             CPLError(CE_Warning, CPLE_AppDefined,
    3309             :                      "'%s' attribute of variable '%s' references grid mapping "
    3310             :                      "variable '%s', but no such variable exists. The spatial "
    3311             :                      "referencing of this dataset may be incorrect.",
    3312             :                      CF_GRD_MAPPING, osVarName.c_str(),
    3313             :                      osGridMappingValue.c_str());
    3314             :         }
    3315             :     }
    3316             : 
    3317             :     // Get information about the file.
    3318             :     //
    3319             :     // Was this file created by the GDAL netcdf driver?
    3320             :     // Was this file created by the newer (CF-conformant) driver?
    3321             :     //
    3322             :     // 1) If GDAL netcdf metadata is set, and version >= 1.9,
    3323             :     //    it was created with the new driver
    3324             :     // 2) Else, if spatial_ref and GeoTransform are present in the
    3325             :     //    grid_mapping variable, it was created by the old driver
    3326         639 :     pszValue = FetchAttr("NC_GLOBAL", "GDAL");
    3327             : 
    3328         639 :     if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
    3329             :     {
    3330         294 :         bIsGdalFile = true;
    3331         294 :         bIsGdalCfFile = true;
    3332             :     }
    3333         345 :     else if (pszWKT != nullptr && pszGeoTransform != nullptr)
    3334             :     {
    3335          39 :         bIsGdalFile = true;
    3336          39 :         bIsGdalCfFile = false;
    3337             :     }
    3338             : 
    3339             :     // Set default bottom-up default value.
    3340             :     // Y axis dimension and absence of GT can modify this value.
    3341             :     // Override with Config option GDAL_NETCDF_BOTTOMUP.
    3342             : 
    3343             :     // New driver is bottom-up by default.
    3344         639 :     if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
    3345          41 :         poDS->bBottomUp = false;
    3346             :     else
    3347         598 :         poDS->bBottomUp = true;
    3348             : 
    3349         639 :     CPLDebug("GDAL_netCDF",
    3350             :              "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
    3351         639 :              static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
    3352         639 :              static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
    3353             : 
    3354             :     // Read projection coordinates.
    3355             : 
    3356         639 :     int nGroupDimXID = -1;
    3357         639 :     int nVarDimXID = -1;
    3358         639 :     int nGroupDimYID = -1;
    3359         639 :     int nVarDimYID = -1;
    3360         639 :     if (sg != nullptr)
    3361             :     {
    3362          44 :         nGroupDimXID = sg->get_ncID();
    3363          44 :         nGroupDimYID = sg->get_ncID();
    3364          44 :         nVarDimXID = sg->getNodeCoordVars()[0];
    3365          44 :         nVarDimYID = sg->getNodeCoordVars()[1];
    3366             :     }
    3367             : 
    3368         639 :     if (!bReadSRSOnly)
    3369             :     {
    3370         382 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
    3371             :                        &nVarDimXID);
    3372         382 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
    3373             :                        &nVarDimYID);
    3374             :         // TODO: if above resolving fails we should also search for coordinate
    3375             :         // variables without same name than dimension using the same resolving
    3376             :         // logic. This should handle for example NASA Ocean Color L2 products.
    3377             : 
    3378             :         const bool bIgnoreXYAxisNameChecks =
    3379         764 :             CPLTestBool(CSLFetchNameValueDef(
    3380         382 :                 papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
    3381             :                 CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
    3382         382 :                                    "NO"))) ||
    3383             :             // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
    3384             :             // and transform attributes
    3385         382 :             (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
    3386         764 :              FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
    3387         381 :             FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
    3388             : 
    3389             :         // Check that they are 1D or 2D variables
    3390         382 :         if (nVarDimXID >= 0)
    3391             :         {
    3392         274 :             int ndims = -1;
    3393         274 :             nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
    3394         274 :             if (ndims == 0 || ndims > 2)
    3395           0 :                 nVarDimXID = -1;
    3396         274 :             else if (!bIgnoreXYAxisNameChecks)
    3397             :             {
    3398         272 :                 if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    3399         179 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
    3400             :                     // In case of inversion of X/Y
    3401         483 :                     !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
    3402          32 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
    3403             :                 {
    3404             :                     char szVarNameX[NC_MAX_NAME + 1];
    3405          32 :                     CPL_IGNORE_RET_VAL(
    3406          32 :                         nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    3407          32 :                     if (!(ndims == 1 &&
    3408          31 :                           (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
    3409          30 :                            EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
    3410             :                     {
    3411          31 :                         CPLDebug(
    3412             :                             "netCDF",
    3413             :                             "Georeferencing ignored due to non-specific "
    3414             :                             "enough X axis name. "
    3415             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3416             :                             "as configuration option to bypass this check");
    3417          31 :                         nVarDimXID = -1;
    3418             :                     }
    3419             :                 }
    3420             :             }
    3421             :         }
    3422             : 
    3423         382 :         if (nVarDimYID >= 0)
    3424             :         {
    3425         276 :             int ndims = -1;
    3426         276 :             nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
    3427         276 :             if (ndims == 0 || ndims > 2)
    3428           1 :                 nVarDimYID = -1;
    3429         275 :             else if (!bIgnoreXYAxisNameChecks)
    3430             :             {
    3431         273 :                 if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
    3432         180 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
    3433             :                     // In case of inversion of X/Y
    3434         486 :                     !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    3435          33 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
    3436             :                 {
    3437             :                     char szVarNameY[NC_MAX_NAME + 1];
    3438          33 :                     CPL_IGNORE_RET_VAL(
    3439          33 :                         nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    3440          33 :                     if (!(ndims == 1 &&
    3441          33 :                           (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
    3442          32 :                            EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
    3443             :                     {
    3444          32 :                         CPLDebug(
    3445             :                             "netCDF",
    3446             :                             "Georeferencing ignored due to non-specific "
    3447             :                             "enough Y axis name. "
    3448             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3449             :                             "as configuration option to bypass this check");
    3450          32 :                         nVarDimYID = -1;
    3451             :                     }
    3452             :                 }
    3453             :             }
    3454             :         }
    3455             : 
    3456         382 :         if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
    3457             :         {
    3458           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3459             :                      "1-pixel width/height files not supported, "
    3460             :                      "xdim: %ld ydim: %ld",
    3461             :                      static_cast<long>(xdim), static_cast<long>(ydim));
    3462           0 :             nVarDimXID = -1;
    3463           0 :             nVarDimYID = -1;
    3464             :         }
    3465             :     }
    3466             : 
    3467         639 :     const char *pszUnits = nullptr;
    3468         639 :     if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3469             :     {
    3470         287 :         const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
    3471         287 :         const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
    3472             :         // Normalize degrees_east/degrees_north to degrees
    3473             :         // Cf https://github.com/OSGeo/gdal/issues/11009
    3474         287 :         if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
    3475          81 :             pszUnitsX = "degrees";
    3476         287 :         if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
    3477          81 :             pszUnitsY = "degrees";
    3478             : 
    3479         287 :         if (pszUnitsX && pszUnitsY)
    3480             :         {
    3481         239 :             if (EQUAL(pszUnitsX, pszUnitsY))
    3482         236 :                 pszUnits = pszUnitsX;
    3483           3 :             else if (!pszWKT && !osGridMappingValue.empty())
    3484             :             {
    3485           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3486             :                          "X axis unit (%s) is different from Y axis "
    3487             :                          "unit (%s). SRS will ignore axis unit and be "
    3488             :                          "likely wrong.",
    3489             :                          pszUnitsX, pszUnitsY);
    3490             :             }
    3491             :         }
    3492          48 :         else if (pszUnitsX && !pszWKT && !osGridMappingValue.empty())
    3493             :         {
    3494           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3495             :                      "X axis unit is defined, but not Y one ."
    3496             :                      "SRS will ignore axis unit and be likely wrong.");
    3497             :         }
    3498          48 :         else if (pszUnitsY && !pszWKT && !osGridMappingValue.empty())
    3499             :         {
    3500           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3501             :                      "Y axis unit is defined, but not X one ."
    3502             :                      "SRS will ignore axis unit and be likely wrong.");
    3503             :         }
    3504             :     }
    3505             : 
    3506         639 :     if (!pszWKT && !osGridMappingValue.empty())
    3507             :     {
    3508          32 :         CPLStringList aosGridMappingKeyValues;
    3509          32 :         const size_t nLenGridMappingValue = osGridMappingValue.size();
    3510         817 :         for (const char *pszIter : aosMetadata)
    3511             :         {
    3512        1021 :             if (STARTS_WITH(pszIter, osGridMappingValue.c_str()) &&
    3513         236 :                 pszIter[nLenGridMappingValue] == '#')
    3514             :             {
    3515         236 :                 char *pszKey = nullptr;
    3516         236 :                 pszValue = CPLParseNameValue(pszIter + nLenGridMappingValue + 1,
    3517             :                                              &pszKey);
    3518         236 :                 if (pszKey && pszValue)
    3519         236 :                     aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
    3520         236 :                 CPLFree(pszKey);
    3521             :             }
    3522             :         }
    3523             : 
    3524          32 :         bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
    3525             :                          CF_PP_SEMI_MAJOR_AXIS) != nullptr;
    3526             : 
    3527          32 :         oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
    3528          32 :         bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
    3529             :     }
    3530             :     else
    3531             :     {
    3532             :         // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
    3533             :         // attribute hold on the variable of interest that contains a PROJ.4
    3534             :         // string
    3535         607 :         pszValue = FetchAttr(nGroupId, nVarId, "crs");
    3536         608 :         if (pszValue &&
    3537           1 :             (strstr(pszValue, "+proj=") != nullptr ||
    3538           0 :              strstr(pszValue, "GEOGCS") != nullptr ||
    3539           0 :              strstr(pszValue, "PROJCS") != nullptr ||
    3540         608 :              strstr(pszValue, "EPSG:") != nullptr) &&
    3541           1 :             oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
    3542             :         {
    3543           1 :             bGotCfSRS = true;
    3544             :         }
    3545             :     }
    3546             : 
    3547             :     // Set Projection from CF.
    3548         639 :     double dfLinearUnitsConvFactor = 1.0;
    3549         639 :     if ((bGotGeogCS || bGotCfSRS))
    3550             :     {
    3551          31 :         if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3552             :         {
    3553             :             // Set SRS Units.
    3554             : 
    3555             :             // Check units for x and y.
    3556          28 :             if (oSRS.IsProjected())
    3557             :             {
    3558          25 :                 dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
    3559             : 
    3560             :                 // If the user doesn't ask to preserve the axis unit,
    3561             :                 // then normalize to metre
    3562          31 :                 if (dfLinearUnitsConvFactor != 1.0 &&
    3563           6 :                     !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
    3564             :                                   false))
    3565             :                 {
    3566           5 :                     oSRS.SetLinearUnits("metre", 1.0);
    3567           5 :                     oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
    3568             :                 }
    3569             :                 else
    3570             :                 {
    3571          20 :                     dfLinearUnitsConvFactor = 1.0;
    3572             :                 }
    3573             :             }
    3574             :         }
    3575             : 
    3576             :         // Set projection.
    3577          31 :         char *pszTempProjection = nullptr;
    3578          31 :         oSRS.exportToWkt(&pszTempProjection);
    3579          31 :         if (pszTempProjection)
    3580             :         {
    3581          31 :             CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3582          31 :             if (returnProjStr != nullptr)
    3583             :             {
    3584           2 :                 (*returnProjStr) = std::string(pszTempProjection);
    3585             :             }
    3586             :             else
    3587             :             {
    3588          29 :                 m_bAddedProjectionVarsDefs = true;
    3589          29 :                 m_bAddedProjectionVarsData = true;
    3590          29 :                 SetSpatialRefNoUpdate(&oSRS);
    3591             :             }
    3592             :         }
    3593          31 :         CPLFree(pszTempProjection);
    3594             :     }
    3595             : 
    3596         639 :     if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
    3597             :         ydim > 0)
    3598             :     {
    3599             :         double *pdfXCoord =
    3600         243 :             static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
    3601             :         double *pdfYCoord =
    3602         243 :             static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
    3603             : 
    3604         243 :         size_t start[2] = {0, 0};
    3605         243 :         size_t edge[2] = {xdim, 0};
    3606         243 :         int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
    3607             :                                         pdfXCoord);
    3608         243 :         NCDF_ERR(status);
    3609             : 
    3610         243 :         edge[0] = ydim;
    3611         243 :         status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
    3612             :                                     pdfYCoord);
    3613         243 :         NCDF_ERR(status);
    3614             : 
    3615         243 :         nc_type nc_var_dimx_datatype = NC_NAT;
    3616             :         status =
    3617         243 :             nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
    3618         243 :         NCDF_ERR(status);
    3619             : 
    3620         243 :         nc_type nc_var_dimy_datatype = NC_NAT;
    3621             :         status =
    3622         243 :             nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
    3623         243 :         NCDF_ERR(status);
    3624             : 
    3625         243 :         if (!poDS->bSwitchedXY)
    3626             :         {
    3627             :             // Convert ]180,540] longitude values to ]-180,0].
    3628         334 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3629          93 :                 CPLTestBool(
    3630             :                     CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
    3631             :             {
    3632             :                 // If minimum longitude is > 180, subtract 360 from all.
    3633             :                 // Add a check on the maximum X value too, since
    3634             :                 // NCDFIsVarLongitude() is not very specific by default (see
    3635             :                 // https://github.com/OSGeo/gdal/issues/1440)
    3636         100 :                 if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
    3637           7 :                     std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
    3638             :                 {
    3639           0 :                     CPLDebug(
    3640             :                         "GDAL_netCDF",
    3641             :                         "Offsetting longitudes from ]180,540] to ]-180,180]. "
    3642             :                         "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
    3643           0 :                     for (size_t i = 0; i < xdim; i++)
    3644           0 :                         pdfXCoord[i] -= 360;
    3645             :                 }
    3646             :             }
    3647             :         }
    3648             : 
    3649             :         // Is pixel spacing uniform across the map?
    3650             : 
    3651             :         // Check Longitude.
    3652             : 
    3653         243 :         bool bLonSpacingOK = false;
    3654         243 :         if (xdim == 2)
    3655             :         {
    3656          29 :             bLonSpacingOK = true;
    3657             :         }
    3658             :         else
    3659             :         {
    3660         214 :             bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
    3661             : 
    3662             :             // fix longitudes if longitudes should increase from
    3663             :             // west to east, but west > east
    3664         297 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3665          83 :                 !bWestIsLeft)
    3666             :             {
    3667           2 :                 size_t ndecreases = 0;
    3668             : 
    3669             :                 // there is lon wrap if longitudes increase
    3670             :                 // with one single decrease
    3671         107 :                 for (size_t i = 1; i < xdim; i++)
    3672             :                 {
    3673         105 :                     if (pdfXCoord[i] < pdfXCoord[i - 1])
    3674           1 :                         ndecreases++;
    3675             :                 }
    3676             : 
    3677           2 :                 if (ndecreases == 1)
    3678             :                 {
    3679           1 :                     CPLDebug("GDAL_netCDF", "longitude wrap detected");
    3680           4 :                     for (size_t i = 0; i < xdim; i++)
    3681             :                     {
    3682           3 :                         if (pdfXCoord[i] > pdfXCoord[xdim - 1])
    3683           1 :                             pdfXCoord[i] -= 360;
    3684             :                     }
    3685             :                 }
    3686             :             }
    3687             : 
    3688         214 :             const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
    3689         214 :             const double dfSpacingMiddle =
    3690         214 :                 pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
    3691         214 :             const double dfSpacingLast =
    3692         214 :                 pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
    3693             : 
    3694         214 :             CPLDebug("GDAL_netCDF",
    3695             :                      "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3696             :                      "dfSpacingLast: %f",
    3697             :                      static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
    3698             :                      dfSpacingLast);
    3699             : #ifdef NCDF_DEBUG
    3700             :             CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
    3701             :                      pdfXCoord[1], pdfXCoord[xdim / 2],
    3702             :                      pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
    3703             :                      pdfXCoord[xdim - 1]);
    3704             : #endif
    3705             : 
    3706             :             // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
    3707             :             // requires a 0.02% tolerance, so let's settle for 0.05%
    3708             : 
    3709             :             // For float variables, increase to 0.2% (as seen in
    3710             :             // https://github.com/OSGeo/gdal/issues/3663)
    3711         214 :             const double dfEpsRel =
    3712         214 :                 nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3713             : 
    3714             :             const double dfEps =
    3715             :                 dfEpsRel *
    3716         428 :                 std::max(fabs(dfSpacingBegin),
    3717         214 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3718         422 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3719         422 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3720         208 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3721             :             {
    3722         208 :                 bLonSpacingOK = true;
    3723             :             }
    3724           6 :             else if (CPLTestBool(CPLGetConfigOption(
    3725             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3726             :             {
    3727           0 :                 bLonSpacingOK = true;
    3728           0 :                 CPLDebug(
    3729             :                     "GDAL_netCDF",
    3730             :                     "Longitude/X is not equally spaced, but will be considered "
    3731             :                     "as such because of "
    3732             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3733             :             }
    3734             :         }
    3735             : 
    3736         243 :         if (bLonSpacingOK == false)
    3737             :         {
    3738           6 :             CPLDebug(
    3739             :                 "GDAL_netCDF", "%s",
    3740             :                 "Longitude/X is not equally spaced (with a 0.05% tolerance). "
    3741             :                 "You may set the "
    3742             :                 "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3743             :                 "option to YES to ignore this check");
    3744             :         }
    3745             : 
    3746             :         // Check Latitude.
    3747         243 :         bool bLatSpacingOK = false;
    3748             : 
    3749         243 :         if (ydim == 2)
    3750             :         {
    3751          49 :             bLatSpacingOK = true;
    3752             :         }
    3753             :         else
    3754             :         {
    3755         194 :             const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
    3756         194 :             const double dfSpacingMiddle =
    3757         194 :                 pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
    3758             : 
    3759         194 :             const double dfSpacingLast =
    3760         194 :                 pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
    3761             : 
    3762         194 :             CPLDebug("GDAL_netCDF",
    3763             :                      "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3764             :                      "dfSpacingLast: %f",
    3765             :                      (long)ydim, dfSpacingBegin, dfSpacingMiddle,
    3766             :                      dfSpacingLast);
    3767             : #ifdef NCDF_DEBUG
    3768             :             CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
    3769             :                      pdfYCoord[1], pdfYCoord[ydim / 2],
    3770             :                      pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
    3771             :                      pdfYCoord[ydim - 1]);
    3772             : #endif
    3773             : 
    3774         194 :             const double dfEpsRel =
    3775         194 :                 nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3776             : 
    3777             :             const double dfEps =
    3778             :                 dfEpsRel *
    3779         388 :                 std::max(fabs(dfSpacingBegin),
    3780         194 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3781         386 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3782         386 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3783         183 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3784             :             {
    3785         183 :                 bLatSpacingOK = true;
    3786             :             }
    3787          11 :             else if (CPLTestBool(CPLGetConfigOption(
    3788             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3789             :             {
    3790           0 :                 bLatSpacingOK = true;
    3791           0 :                 CPLDebug(
    3792             :                     "GDAL_netCDF",
    3793             :                     "Latitude/Y is not equally spaced, but will be considered "
    3794             :                     "as such because of "
    3795             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3796             :             }
    3797          11 :             else if (!oSRS.IsProjected() &&
    3798          11 :                      fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
    3799          30 :                      fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
    3800           8 :                      fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
    3801             :             {
    3802           8 :                 bLatSpacingOK = true;
    3803           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3804             :                          "Latitude grid not spaced evenly.  "
    3805             :                          "Setting projection for grid spacing is "
    3806             :                          "within 0.1 degrees threshold.");
    3807             : 
    3808           8 :                 CPLDebug("GDAL_netCDF",
    3809             :                          "Latitude grid not spaced evenly, but within 0.1 "
    3810             :                          "degree threshold (probably a Gaussian grid).  "
    3811             :                          "Saving original latitude values in Y_VALUES "
    3812             :                          "geolocation metadata");
    3813           8 :                 Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
    3814             :             }
    3815             : 
    3816         194 :             if (bLatSpacingOK == false)
    3817             :             {
    3818           3 :                 CPLDebug(
    3819             :                     "GDAL_netCDF", "%s",
    3820             :                     "Latitude/Y is not equally spaced (with a 0.05% "
    3821             :                     "tolerance). "
    3822             :                     "You may set the "
    3823             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3824             :                     "option to YES to ignore this check");
    3825             :             }
    3826             :         }
    3827             : 
    3828         243 :         if (bLonSpacingOK && bLatSpacingOK)
    3829             :         {
    3830             :             // We have gridded data so we can set the Georeferencing info.
    3831             : 
    3832             :             // Enable GeoTransform.
    3833             : 
    3834             :             // In the following "actual_range" and "node_offset"
    3835             :             // are attributes used by netCDF files created by GMT.
    3836             :             // If we find them we know how to proceed. Else, use
    3837             :             // the original algorithm.
    3838         236 :             bGotCfGT = true;
    3839             : 
    3840         236 :             int node_offset = 0;
    3841             :             const bool bUseActualRange =
    3842         236 :                 NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
    3843         236 :                                   &node_offset) == CE_None;
    3844             : 
    3845         236 :             double adfActualRange[2] = {0.0, 0.0};
    3846         236 :             double xMinMax[2] = {0.0, 0.0};
    3847         236 :             double yMinMax[2] = {0.0, 0.0};
    3848             : 
    3849             :             const auto RoundMinMaxForFloatVals =
    3850          60 :                 [](double &dfMin, double &dfMax, int nIntervals)
    3851             :             {
    3852             :                 // Helps for a case where longitudes range from
    3853             :                 // -179.99 to 180.0 with a 0.01 degree spacing.
    3854             :                 // However as this is encoded in a float array,
    3855             :                 // -179.99 is actually read as -179.99000549316406 as
    3856             :                 // a double. Try to detect that and correct the rounding
    3857             : 
    3858          88 :                 const auto IsAlmostInteger = [](double dfVal)
    3859             :                 {
    3860          88 :                     constexpr double THRESHOLD_INTEGER = 1e-3;
    3861          88 :                     return std::fabs(dfVal - std::round(dfVal)) <=
    3862          88 :                            THRESHOLD_INTEGER;
    3863             :                 };
    3864             : 
    3865          60 :                 const double dfSpacing = (dfMax - dfMin) / nIntervals;
    3866          60 :                 if (dfSpacing > 0)
    3867             :                 {
    3868          48 :                     const double dfInvSpacing = 1.0 / dfSpacing;
    3869          48 :                     if (IsAlmostInteger(dfInvSpacing))
    3870             :                     {
    3871          20 :                         const double dfRoundedSpacing =
    3872          20 :                             1.0 / std::round(dfInvSpacing);
    3873          20 :                         const double dfMinDivRoundedSpacing =
    3874          20 :                             dfMin / dfRoundedSpacing;
    3875          20 :                         const double dfMaxDivRoundedSpacing =
    3876          20 :                             dfMax / dfRoundedSpacing;
    3877          40 :                         if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
    3878          20 :                             IsAlmostInteger(dfMaxDivRoundedSpacing))
    3879             :                         {
    3880          20 :                             const double dfRoundedMin =
    3881          20 :                                 std::round(dfMinDivRoundedSpacing) *
    3882             :                                 dfRoundedSpacing;
    3883          20 :                             const double dfRoundedMax =
    3884          20 :                                 std::round(dfMaxDivRoundedSpacing) *
    3885             :                                 dfRoundedSpacing;
    3886          20 :                             if (static_cast<float>(dfMin) ==
    3887          20 :                                     static_cast<float>(dfRoundedMin) &&
    3888           8 :                                 static_cast<float>(dfMax) ==
    3889           8 :                                     static_cast<float>(dfRoundedMax))
    3890             :                             {
    3891           7 :                                 dfMin = dfRoundedMin;
    3892           7 :                                 dfMax = dfRoundedMax;
    3893             :                             }
    3894             :                         }
    3895             :                     }
    3896             :                 }
    3897          60 :             };
    3898             : 
    3899         239 :             if (bUseActualRange &&
    3900           3 :                 !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
    3901             :                                    adfActualRange))
    3902             :             {
    3903           1 :                 xMinMax[0] = adfActualRange[0];
    3904           1 :                 xMinMax[1] = adfActualRange[1];
    3905             : 
    3906             :                 // Present xMinMax[] in the same order as padfXCoord
    3907           1 :                 if ((xMinMax[0] - xMinMax[1]) *
    3908           1 :                         (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
    3909             :                     0)
    3910             :                 {
    3911           0 :                     std::swap(xMinMax[0], xMinMax[1]);
    3912             :                 }
    3913             :             }
    3914             :             else
    3915             :             {
    3916         235 :                 xMinMax[0] = pdfXCoord[0];
    3917         235 :                 xMinMax[1] = pdfXCoord[xdim - 1];
    3918         235 :                 node_offset = 0;
    3919             : 
    3920         235 :                 if (nc_var_dimx_datatype == NC_FLOAT)
    3921             :                 {
    3922          30 :                     RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
    3923          30 :                                             poDS->nRasterXSize - 1);
    3924             :                 }
    3925             :             }
    3926             : 
    3927         239 :             if (bUseActualRange &&
    3928           3 :                 !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
    3929             :                                    adfActualRange))
    3930             :             {
    3931           1 :                 yMinMax[0] = adfActualRange[0];
    3932           1 :                 yMinMax[1] = adfActualRange[1];
    3933             : 
    3934             :                 // Present yMinMax[] in the same order as pdfYCoord
    3935           1 :                 if ((yMinMax[0] - yMinMax[1]) *
    3936           1 :                         (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
    3937             :                     0)
    3938             :                 {
    3939           0 :                     std::swap(yMinMax[0], yMinMax[1]);
    3940             :                 }
    3941             :             }
    3942             :             else
    3943             :             {
    3944         235 :                 yMinMax[0] = pdfYCoord[0];
    3945         235 :                 yMinMax[1] = pdfYCoord[ydim - 1];
    3946         235 :                 node_offset = 0;
    3947             : 
    3948         235 :                 if (nc_var_dimy_datatype == NC_FLOAT)
    3949             :                 {
    3950          30 :                     RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
    3951          30 :                                             poDS->nRasterYSize - 1);
    3952             :                 }
    3953             :             }
    3954             : 
    3955         236 :             double dfCoordOffset = 0.0;
    3956         236 :             double dfCoordScale = 1.0;
    3957         236 :             if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
    3958         240 :                                    &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         236 :             if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
    3967         240 :                                    &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         236 :             if (!bSwitchedXY)
    3977             :             {
    3978         234 :                 poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
    3979         234 :                 if (!poDS->bBottomUp)
    3980             :                 {
    3981          34 :                     std::swap(yMinMax[0], yMinMax[1]);
    3982             :                 }
    3983             :             }
    3984             : 
    3985             :             // Geostationary satellites can specify units in (micro)radians
    3986             :             // So we check if they do, and if so convert to linear units
    3987             :             // (meters)
    3988         236 :             const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
    3989         236 :             if (pszProjName != nullptr)
    3990             :             {
    3991          24 :                 if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
    3992             :                 {
    3993             :                     const double satelliteHeight =
    3994           3 :                         oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
    3995           6 :                     std::string osUnits;
    3996           3 :                     if (NCDFGetAttr(nGroupId, nVarDimXID, "units", osUnits) ==
    3997             :                         CE_None)
    3998             :                     {
    3999           3 :                         if (EQUAL(osUnits.c_str(), "microradian"))
    4000             :                         {
    4001           1 :                             xMinMax[0] =
    4002           1 :                                 xMinMax[0] * satelliteHeight * 0.000001;
    4003           1 :                             xMinMax[1] =
    4004           1 :                                 xMinMax[1] * satelliteHeight * 0.000001;
    4005             :                         }
    4006           3 :                         else if (EQUAL(osUnits.c_str(), "rad") ||
    4007           1 :                                  EQUAL(osUnits.c_str(), "radian"))
    4008             :                         {
    4009           2 :                             xMinMax[0] = xMinMax[0] * satelliteHeight;
    4010           2 :                             xMinMax[1] = xMinMax[1] * satelliteHeight;
    4011             :                         }
    4012             :                     }
    4013           3 :                     if (NCDFGetAttr(nGroupId, nVarDimYID, "units", osUnits) ==
    4014             :                         CE_None)
    4015             :                     {
    4016           3 :                         if (EQUAL(osUnits.c_str(), "microradian"))
    4017             :                         {
    4018           1 :                             yMinMax[0] =
    4019           1 :                                 yMinMax[0] * satelliteHeight * 0.000001;
    4020           1 :                             yMinMax[1] =
    4021           1 :                                 yMinMax[1] * satelliteHeight * 0.000001;
    4022             :                         }
    4023           3 :                         else if (EQUAL(osUnits.c_str(), "rad") ||
    4024           1 :                                  EQUAL(osUnits.c_str(), "radian"))
    4025             :                         {
    4026           2 :                             yMinMax[0] = yMinMax[0] * satelliteHeight;
    4027           2 :                             yMinMax[1] = yMinMax[1] * satelliteHeight;
    4028             :                         }
    4029             :                     }
    4030             :                 }
    4031             :             }
    4032             : 
    4033         236 :             tmpGT[0] = xMinMax[0];
    4034         472 :             tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
    4035         236 :                        (poDS->nRasterXSize + (node_offset - 1));
    4036         236 :             tmpGT[2] = 0;
    4037         236 :             if (bSwitchedXY)
    4038             :             {
    4039           2 :                 tmpGT[3] = yMinMax[0];
    4040           2 :                 tmpGT[4] = 0;
    4041           2 :                 tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
    4042           2 :                            (poDS->nRasterYSize + (node_offset - 1));
    4043             :             }
    4044             :             else
    4045             :             {
    4046         234 :                 tmpGT[3] = yMinMax[1];
    4047         234 :                 tmpGT[4] = 0;
    4048         234 :                 tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
    4049         234 :                            (poDS->nRasterYSize + (node_offset - 1));
    4050             :             }
    4051             : 
    4052             :             // Compute the center of the pixel.
    4053         236 :             if (!node_offset)
    4054             :             {
    4055             :                 // Otherwise its already the pixel center.
    4056         236 :                 tmpGT[0] -= (tmpGT[1] / 2);
    4057         236 :                 tmpGT[3] -= (tmpGT[5] / 2);
    4058             :             }
    4059             :         }
    4060             : 
    4061             :         const auto AreSRSEqualThroughProj4String =
    4062           2 :             [](const OGRSpatialReference &oSRS1,
    4063             :                const OGRSpatialReference &oSRS2)
    4064             :         {
    4065           2 :             char *pszProj4Str1 = nullptr;
    4066           2 :             oSRS1.exportToProj4(&pszProj4Str1);
    4067             : 
    4068           2 :             char *pszProj4Str2 = nullptr;
    4069           2 :             oSRS2.exportToProj4(&pszProj4Str2);
    4070             : 
    4071             :             {
    4072           2 :                 char *pszTmp = strstr(pszProj4Str1, "+datum=");
    4073           2 :                 if (pszTmp)
    4074           0 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4075             :             }
    4076             : 
    4077             :             {
    4078           2 :                 char *pszTmp = strstr(pszProj4Str2, "+datum=");
    4079           2 :                 if (pszTmp)
    4080           2 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4081             :             }
    4082             : 
    4083           2 :             bool bRet = false;
    4084           2 :             if (pszProj4Str1 && pszProj4Str2 &&
    4085           2 :                 EQUAL(pszProj4Str1, pszProj4Str2))
    4086             :             {
    4087           1 :                 bRet = true;
    4088             :             }
    4089             : 
    4090           2 :             CPLFree(pszProj4Str1);
    4091           2 :             CPLFree(pszProj4Str2);
    4092           2 :             return bRet;
    4093             :         };
    4094             : 
    4095         243 :         if (dfLinearUnitsConvFactor != 1.0)
    4096             :         {
    4097          35 :             for (int i = 0; i < 6; ++i)
    4098          30 :                 tmpGT[i] *= dfLinearUnitsConvFactor;
    4099             : 
    4100           5 :             if (paosRemovedMDItems)
    4101             :             {
    4102             :                 char szVarNameX[NC_MAX_NAME + 1];
    4103           5 :                 CPL_IGNORE_RET_VAL(
    4104           5 :                     nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4105             : 
    4106             :                 char szVarNameY[NC_MAX_NAME + 1];
    4107           5 :                 CPL_IGNORE_RET_VAL(
    4108           5 :                     nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4109             : 
    4110           5 :                 paosRemovedMDItems->push_back(
    4111             :                     CPLSPrintf("%s#units", szVarNameX));
    4112           5 :                 paosRemovedMDItems->push_back(
    4113             :                     CPLSPrintf("%s#units", szVarNameY));
    4114             :             }
    4115             :         }
    4116             : 
    4117             :         // If there is a global "geospatial_bounds_crs" attribute, check that it
    4118             :         // is consistent with the SRS, and if so, use it as the SRS
    4119             :         const char *pszGBCRS =
    4120         243 :             FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
    4121         243 :         if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
    4122             :         {
    4123           4 :             OGRSpatialReference oSRSFromGBCRS;
    4124           2 :             oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4125           2 :             if (oSRSFromGBCRS.SetFromUserInput(
    4126             :                     pszGBCRS,
    4127             :                     OGRSpatialReference::
    4128           4 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
    4129           2 :                 AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
    4130             :             {
    4131           1 :                 oSRS = std::move(oSRSFromGBCRS);
    4132           1 :                 SetSpatialRefNoUpdate(&oSRS);
    4133             :             }
    4134             :         }
    4135             : 
    4136         243 :         CPLFree(pdfXCoord);
    4137         243 :         CPLFree(pdfYCoord);
    4138             :     }  // end if(has dims)
    4139             : 
    4140             :     // Process custom GeoTransform GDAL value.
    4141         639 :     if (!osGridMappingValue.empty())
    4142             :     {
    4143         266 :         if (pszGeoTransform != nullptr)
    4144             :         {
    4145             :             const CPLStringList aosGeoTransform(
    4146         306 :                 CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
    4147         153 :             if (aosGeoTransform.size() == 6)
    4148             :             {
    4149         153 :                 bool bUseGeoTransformFromAttribute = true;
    4150             : 
    4151         153 :                 GDALGeoTransform gtFromAttribute;
    4152        1071 :                 for (int i = 0; i < 6; i++)
    4153             :                 {
    4154         918 :                     gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
    4155             :                 }
    4156             : 
    4157             :                 // When GDAL writes a raster that is north-up oriented, it
    4158             :                 // writes the "GeoTransform" attribute unmodified, that is with
    4159             :                 // gt.yscale < 0, but the first line is actually the southern-most
    4160             :                 // one, consistently with the values of the "y" coordinate
    4161             :                 // variable. This is wrong... but we have always done that, so
    4162             :                 // this is hard to fix now.
    4163             :                 // However there are datasets like
    4164             :                 // https://public.hub.geosphere.at/datahub/resources/spartacus-v2-1d-1km/filelisting/TN/SPARTACUS2-DAILY_TN_2026.nc
    4165             :                 // that correctly use a positive gt.yscale value. So make sure to not emit
    4166             :                 // a warning when comparing against the geotransform derived from
    4167             :                 // the x/y coordinates.
    4168         153 :                 GDALGeoTransform gtFromAttributeNorthUp = gtFromAttribute;
    4169         158 :                 if (gtFromAttributeNorthUp.yscale > 0 &&
    4170           5 :                     gtFromAttributeNorthUp.IsAxisAligned())
    4171             :                 {
    4172           1 :                     gtFromAttributeNorthUp.yorig +=
    4173           1 :                         poDS->nRasterYSize * gtFromAttributeNorthUp.yscale;
    4174           1 :                     gtFromAttributeNorthUp.yscale =
    4175           1 :                         -gtFromAttributeNorthUp.yscale;
    4176             :                 }
    4177             : 
    4178         153 :                 if (bGotCfGT)
    4179             :                 {
    4180         109 :                     constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
    4181         109 :                     double dfMaxAbsoluteError = 0.0;
    4182         763 :                     for (int i = 0; i < 6; i++)
    4183             :                     {
    4184             :                         double dfAbsoluteError =
    4185         654 :                             std::abs(tmpGT[i] - gtFromAttributeNorthUp[i]);
    4186         654 :                         if (dfAbsoluteError >
    4187         654 :                             std::abs(gtFromAttributeNorthUp[i] *
    4188             :                                      GT_RELERROR_WARN_THRESHOLD))
    4189             :                         {
    4190           3 :                             dfMaxAbsoluteError =
    4191           3 :                                 std::max(dfMaxAbsoluteError, dfAbsoluteError);
    4192             :                         }
    4193             :                     }
    4194             : 
    4195         109 :                     if (dfMaxAbsoluteError > 0)
    4196             :                     {
    4197           3 :                         bUseGeoTransformFromAttribute = false;
    4198           3 :                         CPLError(CE_Warning, CPLE_AppDefined,
    4199             :                                  "GeoTransform read from attribute of %s "
    4200             :                                  "variable differs from value calculated from "
    4201             :                                  "dimension variables (max diff = %g). Using "
    4202             :                                  "value calculated from dimension variables.",
    4203             :                                  osGridMappingValue.c_str(),
    4204             :                                  dfMaxAbsoluteError);
    4205             :                     }
    4206             :                 }
    4207             : 
    4208         153 :                 if (bUseGeoTransformFromAttribute)
    4209             :                 {
    4210         150 :                     if (bGotCfGT)
    4211             :                     {
    4212         106 :                         tmpGT = gtFromAttributeNorthUp;
    4213         106 :                         if (gtFromAttributeNorthUp.IsAxisAligned())
    4214             :                         {
    4215             :                             // Axis direction depends on whether it is GDAL-style CF file
    4216         106 :                             poDS->bBottomUp = bIsGdalCfFile;
    4217             :                         }
    4218             :                     }
    4219             :                     else
    4220             :                     {
    4221          44 :                         tmpGT = gtFromAttribute;
    4222             :                     }
    4223         150 :                     bGotGdalGT = true;
    4224             :                 }
    4225             :             }
    4226             :         }
    4227             :         else
    4228             :         {
    4229             :             // Look for corner array values.
    4230             :             // CPLDebug("GDAL_netCDF",
    4231             :             //           "looking for geotransform corners");
    4232         113 :             bool bGotNN = false;
    4233         113 :             double dfNN = FetchCopyParam(osGridMappingValue.c_str(),
    4234             :                                          "Northernmost_Northing", 0, &bGotNN);
    4235             : 
    4236         113 :             bool bGotSN = false;
    4237         113 :             double dfSN = FetchCopyParam(osGridMappingValue.c_str(),
    4238             :                                          "Southernmost_Northing", 0, &bGotSN);
    4239             : 
    4240         113 :             bool bGotEE = false;
    4241         113 :             double dfEE = FetchCopyParam(osGridMappingValue.c_str(),
    4242             :                                          "Easternmost_Easting", 0, &bGotEE);
    4243             : 
    4244         113 :             bool bGotWE = false;
    4245         113 :             double dfWE = FetchCopyParam(osGridMappingValue.c_str(),
    4246             :                                          "Westernmost_Easting", 0, &bGotWE);
    4247             : 
    4248             :             // Only set the GeoTransform if we got all the values.
    4249         113 :             if (bGotNN && bGotSN && bGotEE && bGotWE)
    4250             :             {
    4251           0 :                 bGotGdalGT = true;
    4252             : 
    4253           0 :                 tmpGT[0] = dfWE;
    4254           0 :                 tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
    4255           0 :                 tmpGT[2] = 0.0;
    4256           0 :                 tmpGT[3] = dfNN;
    4257           0 :                 tmpGT[4] = 0.0;
    4258           0 :                 tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
    4259             :                 // Compute the center of the pixel.
    4260           0 :                 tmpGT[0] = dfWE - (tmpGT[1] / 2);
    4261           0 :                 tmpGT[3] = dfNN - (tmpGT[5] / 2);
    4262             :             }
    4263             :         }  // (pszGeoTransform != NULL)
    4264             : 
    4265         266 :         if (bGotGdalSRS && !bGotGdalGT)
    4266          78 :             CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
    4267             :     }
    4268             : 
    4269         639 :     if (bGotCfGT || bGotGdalGT)
    4270             :     {
    4271         280 :         CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
    4272         280 :                  static_cast<int>(poDS->bBottomUp));
    4273             :     }
    4274             : 
    4275         639 :     if (!pszWKT && !bGotCfSRS)
    4276             :     {
    4277             :         // Some netCDF files have a srid attribute (#6613) like
    4278             :         // urn:ogc:def:crs:EPSG::6931
    4279         374 :         const char *pszSRID = FetchAttr(osGridMappingValue.c_str(), "srid");
    4280         374 :         if (pszSRID != nullptr)
    4281             :         {
    4282           0 :             oSRS.Clear();
    4283           0 :             if (oSRS.SetFromUserInput(
    4284             :                     pszSRID,
    4285             :                     OGRSpatialReference::
    4286           0 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    4287             :             {
    4288           0 :                 CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
    4289           0 :                 std::string osWKTExport = oSRS.exportToWkt();
    4290           0 :                 if (!osWKTExport.empty())
    4291             :                 {
    4292           0 :                     (*returnProjStr) = std::move(osWKTExport);
    4293             :                 }
    4294             :                 else
    4295             :                 {
    4296           0 :                     m_bAddedProjectionVarsDefs = true;
    4297           0 :                     m_bAddedProjectionVarsData = true;
    4298           0 :                     SetSpatialRefNoUpdate(&oSRS);
    4299             :                 }
    4300             :             }
    4301             :         }
    4302             :     }
    4303             : 
    4304         639 :     if (bReadSRSOnly)
    4305         257 :         return;
    4306             : 
    4307             :     // Determines the SRS to be used by the geolocation array, if any
    4308         764 :     std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
    4309         382 :     if (!m_oSRS.IsEmpty())
    4310             :     {
    4311         312 :         OGRSpatialReference oGeogCRS;
    4312         156 :         oGeogCRS.CopyGeogCSFrom(&m_oSRS, true);
    4313         156 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
    4314             : 
    4315         312 :         std::string osWKTTmp = oGeogCRS.exportToWkt(apszOptions);
    4316         156 :         if (!osWKTTmp.empty())
    4317             :         {
    4318         156 :             osGeolocWKT = std::move(osWKTTmp);
    4319             :         }
    4320             :     }
    4321             : 
    4322             :     // Process geolocation arrays from CF "coordinates" attribute.
    4323         764 :     std::string osGeolocXName, osGeolocYName;
    4324         382 :     if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
    4325         382 :                              osGeolocYName))
    4326             :     {
    4327          62 :         bool bCanCancelGT = true;
    4328          62 :         if ((nVarDimXID != -1) && (nVarDimYID != -1))
    4329             :         {
    4330             :             char szVarNameX[NC_MAX_NAME + 1];
    4331          45 :             CPL_IGNORE_RET_VAL(
    4332          45 :                 nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4333             :             char szVarNameY[NC_MAX_NAME + 1];
    4334          45 :             CPL_IGNORE_RET_VAL(
    4335          45 :                 nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4336          45 :             bCanCancelGT =
    4337          45 :                 !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
    4338             :         }
    4339         101 :         if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
    4340          39 :             !bSwitchedXY)
    4341             :         {
    4342          37 :             bGotCfGT = false;
    4343             :         }
    4344             :     }
    4345         125 :     else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
    4346         448 :              (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
    4347           3 :              ((!bSwitchedXY &&
    4348           3 :                NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    4349           1 :                NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
    4350           2 :               (bSwitchedXY &&
    4351           0 :                NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    4352           0 :                NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
    4353             :     {
    4354             :         // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
    4355             :         // which is indexed by lat, lon variables, but lat has irregular
    4356             :         // spacing.
    4357           1 :         const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
    4358           1 :         const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
    4359           1 :         if (bSwitchedXY)
    4360             :         {
    4361           0 :             std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4362           0 :             GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
    4363             :                                             GDAL_MDD_GEOLOCATION);
    4364             :         }
    4365             : 
    4366           1 :         CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4367             :                  pszGeolocXFullName, pszGeolocYFullName);
    4368             : 
    4369           1 :         GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4370             :                                         GDAL_MDD_GEOLOCATION);
    4371             : 
    4372           2 :         CPLString osTMP;
    4373           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4374           1 :                      pszGeolocXFullName);
    4375             : 
    4376           1 :         GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
    4377             :                                         GDAL_MDD_GEOLOCATION);
    4378           1 :         GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
    4379           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4380           1 :                      pszGeolocYFullName);
    4381             : 
    4382           1 :         GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
    4383             :                                         GDAL_MDD_GEOLOCATION);
    4384           1 :         GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
    4385             : 
    4386           1 :         GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
    4387             :                                         GDAL_MDD_GEOLOCATION);
    4388           1 :         GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
    4389             :                                         GDAL_MDD_GEOLOCATION);
    4390             : 
    4391           1 :         GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
    4392             :                                         GDAL_MDD_GEOLOCATION);
    4393           1 :         GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
    4394             : 
    4395           1 :         GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4396             :                                         "PIXEL_CENTER", GDAL_MDD_GEOLOCATION);
    4397             :     }
    4398             : 
    4399             :     // Set GeoTransform if we got a complete one - after projection has been set
    4400         382 :     if (bGotCfGT || bGotGdalGT)
    4401             :     {
    4402         226 :         m_bAddedProjectionVarsDefs = true;
    4403         226 :         m_bAddedProjectionVarsData = true;
    4404         226 :         SetGeoTransformNoUpdate(tmpGT);
    4405             :     }
    4406             : 
    4407             :     // Debugging reports.
    4408         382 :     CPLDebug("GDAL_netCDF",
    4409             :              "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
    4410             :              "bGotGdalSRS=%d bGotGdalGT=%d",
    4411             :              static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
    4412             :              static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
    4413             :              static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
    4414             : 
    4415         382 :     if (!bGotCfGT && !bGotGdalGT)
    4416         156 :         CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
    4417             : 
    4418         382 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
    4419         156 :         CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
    4420             : 
    4421             :     // wish of 6195
    4422             :     // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
    4423         382 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
    4424             :     {
    4425         226 :         if (bGotCfGT || bGotGdalGT)
    4426             :         {
    4427         140 :             bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
    4428          70 :                 papszOpenOptions, "ASSUME_LONGLAT",
    4429             :                 CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
    4430             : 
    4431           2 :             if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
    4432           2 :                 (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
    4433          74 :                 tmpGT[3] <= 90 && tmpGT[3] > -90 &&
    4434           2 :                 (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
    4435             :             {
    4436             : 
    4437           2 :                 poDS->bIsGeographic = true;
    4438             :                 // seems odd to use 4326 so OGC:CRS84
    4439           2 :                 oSRS.SetFromUserInput("OGC:CRS84");
    4440           2 :                 if (returnProjStr != nullptr)
    4441             :                 {
    4442           0 :                     *returnProjStr = oSRS.exportToWkt();
    4443             :                 }
    4444             :                 else
    4445             :                 {
    4446           2 :                     m_bAddedProjectionVarsDefs = true;
    4447           2 :                     m_bAddedProjectionVarsData = true;
    4448           2 :                     SetSpatialRefNoUpdate(&oSRS);
    4449             :                 }
    4450             : 
    4451           2 :                 CPLDebug("netCDF",
    4452             :                          "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
    4453             :                          "none otherwise available and geotransform within "
    4454             :                          "suitable bounds. "
    4455             :                          "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
    4456             :                          "option or "
    4457             :                          "    ASSUME_LONGLAT=NO as open option to bypass this "
    4458             :                          "assumption.");
    4459             :             }
    4460             :         }
    4461             :     }
    4462             : 
    4463             : // Search for Well-known GeogCS if got only CF WKT
    4464             : // Disabled for now, as a named datum also include control points
    4465             : // (see mailing list and bug#4281
    4466             : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
    4467             : 
    4468             : // Disabled for now, but could be set in a config option.
    4469             : #if 0
    4470             :     bool bLookForWellKnownGCS = false;  // This could be a Config Option.
    4471             : 
    4472             :     if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
    4473             :     {
    4474             :         // ET - Could use a more exhaustive method by scanning all EPSG codes in
    4475             :         // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
    4476             :         // for comparing two WKT".
    4477             :         // This code could be contributed to a new function.
    4478             :         // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
    4479             :         //     const OGRSpatialReference *poOther) */
    4480             :         CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
    4481             :         const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
    4482             :         char *pszWKGCS = NULL;
    4483             :         oSRS.exportToPrettyWkt(&pszWKGCS);
    4484             :         for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
    4485             :         {
    4486             :             pszWKGCS = CPLStrdup(pszWKGCSList[i]);
    4487             :             OGRSpatialReference oSRSTmp;
    4488             :             oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
    4489             :             // Set datum to unknown, bug #4281.
    4490             :             if( oSRSTmp.GetAttrNode("DATUM" ) )
    4491             :                 oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
    4492             :             // Could use OGRSpatialReference::StripCTParms(), but let's keep
    4493             :             // TOWGS84.
    4494             :             oSRSTmp.GetRoot()->StripNodes("AXIS");
    4495             :             oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
    4496             :             oSRSTmp.GetRoot()->StripNodes("EXTENSION");
    4497             : 
    4498             :             oSRSTmp.exportToPrettyWkt(&pszWKGCS);
    4499             :             if( oSRS.IsSameGeogCS(&oSRSTmp) )
    4500             :             {
    4501             :                 oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
    4502             :                 oSRS.exportToWkt(&(pszTempProjection));
    4503             :                 SetProjection(pszTempProjection);
    4504             :                 CPLFree(pszTempProjection);
    4505             :             }
    4506             :         }
    4507             :     }
    4508             : #endif
    4509             : }
    4510             : 
    4511         213 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
    4512             :                                          bool bReadSRSOnly)
    4513             : {
    4514         213 :     SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
    4515             :                          nullptr, nullptr);
    4516         213 : }
    4517             : 
    4518         308 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
    4519             : {
    4520             :     // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
    4521             :     // and https://github.com/OSGeo/gdal/issues/7605
    4522             : 
    4523             :     // Check for a structure like:
    4524             :     /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
    4525             :         dimensions:
    4526             :             number_of_lines = 3248 ;
    4527             :             pixels_per_line = 3200 ;
    4528             :             [...]
    4529             :             pixel_control_points = 3200 ;
    4530             :         [...]
    4531             :         group: geophysical_data {
    4532             :           variables:
    4533             :             short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId
    4534             :                 [...]
    4535             :         }
    4536             :         group: navigation_data {
    4537             :           variables:
    4538             :             float longitude(number_of_lines, pixel_control_points) ;
    4539             :                 [...]
    4540             :             float latitude(number_of_lines, pixel_control_points) ;
    4541             :                 [...]
    4542             :         }
    4543             :     }
    4544             :     */
    4545             :     // Note that the longitude and latitude arrays are not indexed by the
    4546             :     // same dimensions. Handle only the case where
    4547             :     // pixel_control_points == pixels_per_line
    4548             :     // If there was a subsampling of the geolocation arrays, we'd need to
    4549             :     // add more logic.
    4550             : 
    4551         616 :     std::string osGroupName;
    4552         308 :     osGroupName.resize(NC_MAX_NAME);
    4553         308 :     NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
    4554         308 :     osGroupName.resize(strlen(osGroupName.data()));
    4555         308 :     if (osGroupName != "geophysical_data")
    4556         307 :         return false;
    4557             : 
    4558           1 :     int nVarDims = 0;
    4559           1 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4560           1 :     if (nVarDims != 2)
    4561           0 :         return false;
    4562             : 
    4563           1 :     int nNavigationDataGrpId = 0;
    4564           1 :     if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
    4565             :         NC_NOERR)
    4566           0 :         return false;
    4567             : 
    4568             :     std::array<int, 2> anVarDimIds;
    4569           1 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4570             : 
    4571           1 :     int nLongitudeId = 0;
    4572           1 :     int nLatitudeId = 0;
    4573           1 :     if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
    4574           2 :             NC_NOERR ||
    4575           1 :         nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
    4576             :             NC_NOERR)
    4577             :     {
    4578           0 :         return false;
    4579             :     }
    4580             : 
    4581           1 :     int nDimsLongitude = 0;
    4582           1 :     NCDF_ERR(
    4583             :         nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
    4584           1 :     int nDimsLatitude = 0;
    4585           1 :     NCDF_ERR(
    4586             :         nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
    4587           1 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4588             :     {
    4589           0 :         return false;
    4590             :     }
    4591             : 
    4592             :     std::array<int, 2> anDimLongitudeIds;
    4593           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
    4594             :                              anDimLongitudeIds.data()));
    4595             :     std::array<int, 2> anDimLatitudeIds;
    4596           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
    4597             :                              anDimLatitudeIds.data()));
    4598           1 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4599             :     {
    4600           0 :         return false;
    4601             :     }
    4602             : 
    4603             :     std::array<size_t, 2> anSizeVarDimIds;
    4604             :     std::array<size_t, 2> anSizeLongLatIds;
    4605           2 :     if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
    4606           1 :               NC_NOERR &&
    4607           1 :           nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
    4608           1 :               NC_NOERR &&
    4609           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
    4610           1 :               NC_NOERR &&
    4611           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
    4612             :               NC_NOERR &&
    4613           1 :           anSizeVarDimIds == anSizeLongLatIds))
    4614             :     {
    4615           0 :         return false;
    4616             :     }
    4617             : 
    4618           1 :     const char *pszGeolocXFullName = "/navigation_data/longitude";
    4619           1 :     const char *pszGeolocYFullName = "/navigation_data/latitude";
    4620             : 
    4621           1 :     if (bSwitchedXY)
    4622             :     {
    4623           0 :         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4624           0 :         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", GDAL_MDD_GEOLOCATION);
    4625             :     }
    4626             : 
    4627           1 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4628             :              pszGeolocXFullName, pszGeolocYFullName);
    4629             : 
    4630           1 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4631             :                                     GDAL_MDD_GEOLOCATION);
    4632             : 
    4633           1 :     CPLString osTMP;
    4634           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4635             : 
    4636           1 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
    4637           1 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
    4638           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4639             : 
    4640           1 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
    4641           1 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
    4642             : 
    4643           1 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", GDAL_MDD_GEOLOCATION);
    4644           1 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", GDAL_MDD_GEOLOCATION);
    4645             : 
    4646           1 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", GDAL_MDD_GEOLOCATION);
    4647           1 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
    4648             : 
    4649           1 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4650             :                                     GDAL_MDD_GEOLOCATION);
    4651           1 :     return true;
    4652             : }
    4653             : 
    4654         307 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
    4655             : {
    4656             :     // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
    4657             : 
    4658             :     // Check for a structure like:
    4659             :     /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
    4660             :         dimensions:
    4661             :             downtrack = 1280 ;
    4662             :             crosstrack = 1242 ;
    4663             :             bands = 285 ;
    4664             :             [...]
    4665             : 
    4666             :         variables:
    4667             :             float reflectance(downtrack, crosstrack, bands) ;
    4668             : 
    4669             :         group: location {
    4670             :           variables:
    4671             :                 double lon(downtrack, crosstrack) ;
    4672             :                         lon:_FillValue = -9999. ;
    4673             :                         lon:long_name = "Longitude (WGS-84)" ;
    4674             :                         lon:units = "degrees east" ;
    4675             :                 double lat(downtrack, crosstrack) ;
    4676             :                         lat:_FillValue = -9999. ;
    4677             :                         lat:long_name = "Latitude (WGS-84)" ;
    4678             :                         lat:units = "degrees north" ;
    4679             :           } // group location
    4680             : 
    4681             :     }
    4682             :     or
    4683             :     netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
    4684             :         dimensions:
    4685             :                 downtrack = 1664 ;
    4686             :                 crosstrack = 1242 ;
    4687             :                 [...]
    4688             :         variables:
    4689             :                 float group_1_band_depth(downtrack, crosstrack) ;
    4690             :                         group_1_band_depth:_FillValue = -9999.f ;
    4691             :                         group_1_band_depth:long_name = "Group 1 Band Depth" ;
    4692             :                         group_1_band_depth:units = "unitless" ;
    4693             :                 [...]
    4694             :         group: location {
    4695             :           variables:
    4696             :                 double lon(downtrack, crosstrack) ;
    4697             :                         lon:_FillValue = -9999. ;
    4698             :                         lon:long_name = "Longitude (WGS-84)" ;
    4699             :                         lon:units = "degrees east" ;
    4700             :                 double lat(downtrack, crosstrack) ;
    4701             :                         lat:_FillValue = -9999. ;
    4702             :                         lat:long_name = "Latitude (WGS-84)" ;
    4703             :                         lat:units = "degrees north" ;
    4704             :         }
    4705             :     */
    4706             : 
    4707         307 :     int nVarDims = 0;
    4708         307 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4709         307 :     if (nVarDims != 2 && nVarDims != 3)
    4710          14 :         return false;
    4711             : 
    4712         293 :     int nLocationGrpId = 0;
    4713         293 :     if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
    4714          62 :         return false;
    4715             : 
    4716             :     std::array<int, 3> anVarDimIds;
    4717         231 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4718         231 :     if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
    4719          21 :         return false;
    4720             : 
    4721         210 :     int nLongitudeId = 0;
    4722         210 :     int nLatitudeId = 0;
    4723         248 :     if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
    4724          38 :         nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
    4725             :     {
    4726         172 :         return false;
    4727             :     }
    4728             : 
    4729          38 :     int nDimsLongitude = 0;
    4730          38 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
    4731          38 :     int nDimsLatitude = 0;
    4732          38 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
    4733          38 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4734             :     {
    4735          34 :         return false;
    4736             :     }
    4737             : 
    4738             :     std::array<int, 2> anDimLongitudeIds;
    4739           4 :     NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
    4740             :                              anDimLongitudeIds.data()));
    4741             :     std::array<int, 2> anDimLatitudeIds;
    4742           4 :     NCDF_ERR(
    4743             :         nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
    4744           4 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4745             :     {
    4746           0 :         return false;
    4747             :     }
    4748             : 
    4749           8 :     if (anDimLongitudeIds[0] != anVarDimIds[0] ||
    4750           4 :         anDimLongitudeIds[1] != anVarDimIds[1])
    4751             :     {
    4752           0 :         return false;
    4753             :     }
    4754             : 
    4755           4 :     const char *pszGeolocXFullName = "/location/lon";
    4756           4 :     const char *pszGeolocYFullName = "/location/lat";
    4757             : 
    4758           4 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4759             :              pszGeolocXFullName, pszGeolocYFullName);
    4760             : 
    4761           4 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4762             :                                     GDAL_MDD_GEOLOCATION);
    4763             : 
    4764           4 :     CPLString osTMP;
    4765           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4766             : 
    4767           4 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
    4768           4 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", GDAL_MDD_GEOLOCATION);
    4769           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4770             : 
    4771           4 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, GDAL_MDD_GEOLOCATION);
    4772           4 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", GDAL_MDD_GEOLOCATION);
    4773             : 
    4774           4 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", GDAL_MDD_GEOLOCATION);
    4775           4 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", GDAL_MDD_GEOLOCATION);
    4776             : 
    4777           4 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", GDAL_MDD_GEOLOCATION);
    4778           4 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", GDAL_MDD_GEOLOCATION);
    4779             : 
    4780           4 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4781             :                                     GDAL_MDD_GEOLOCATION);
    4782           4 :     return true;
    4783             : }
    4784             : 
    4785         382 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
    4786             :                                         const std::string &osGeolocWKT,
    4787             :                                         std::string &osGeolocXNameOut,
    4788             :                                         std::string &osGeolocYNameOut)
    4789             : {
    4790         382 :     bool bAddGeoloc = false;
    4791         382 :     char *pszCoordinates = nullptr;
    4792             : 
    4793             :     // If there is no explicit "coordinates" attribute, check if there are
    4794             :     // "lon" and "lat" 2D variables whose dimensions are the last
    4795             :     // 2 ones of the variable of interest.
    4796         382 :     if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
    4797             :         CE_None)
    4798             :     {
    4799         329 :         CPLFree(pszCoordinates);
    4800         329 :         pszCoordinates = nullptr;
    4801             : 
    4802         329 :         int nVarDims = 0;
    4803         329 :         NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4804         329 :         if (nVarDims >= 2)
    4805             :         {
    4806         658 :             std::vector<int> anVarDimIds(nVarDims);
    4807         329 :             NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4808             : 
    4809         329 :             int nLongitudeId = 0;
    4810         329 :             int nLatitudeId = 0;
    4811         402 :             if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
    4812          73 :                 nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
    4813             :             {
    4814          73 :                 int nDimsLongitude = 0;
    4815          73 :                 NCDF_ERR(
    4816             :                     nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
    4817          73 :                 int nDimsLatitude = 0;
    4818          73 :                 NCDF_ERR(
    4819             :                     nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
    4820          73 :                 if (nDimsLongitude == 2 && nDimsLatitude == 2)
    4821             :                 {
    4822          42 :                     std::vector<int> anDimLongitudeIds(2);
    4823          21 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
    4824             :                                              anDimLongitudeIds.data()));
    4825          42 :                     std::vector<int> anDimLatitudeIds(2);
    4826          21 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
    4827             :                                              anDimLatitudeIds.data()));
    4828          21 :                     if (anDimLongitudeIds == anDimLatitudeIds &&
    4829          42 :                         anVarDimIds[anVarDimIds.size() - 2] ==
    4830          63 :                             anDimLongitudeIds[0] &&
    4831          42 :                         anVarDimIds[anVarDimIds.size() - 1] ==
    4832          21 :                             anDimLongitudeIds[1])
    4833             :                     {
    4834          21 :                         pszCoordinates = CPLStrdup("lon lat");
    4835             :                     }
    4836             :                 }
    4837             :             }
    4838             :         }
    4839             :     }
    4840             : 
    4841         382 :     if (pszCoordinates)
    4842             :     {
    4843             :         // Get X and Y geolocation names from coordinates attribute.
    4844             :         const CPLStringList aosCoordinates(
    4845         148 :             NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    4846          74 :         if (aosCoordinates.size() >= 2)
    4847             :         {
    4848             :             char szGeolocXName[NC_MAX_NAME + 1];
    4849             :             char szGeolocYName[NC_MAX_NAME + 1];
    4850          70 :             szGeolocXName[0] = '\0';
    4851          70 :             szGeolocYName[0] = '\0';
    4852             : 
    4853             :             // Test that each variable is longitude/latitude.
    4854         226 :             for (int i = 0; i < aosCoordinates.size(); i++)
    4855             :             {
    4856         156 :                 if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
    4857             :                 {
    4858          59 :                     int nOtherGroupId = -1;
    4859          59 :                     int nOtherVarId = -1;
    4860             :                     // Check that the variable actually exists
    4861             :                     // Needed on Sentinel-3 products
    4862          59 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4863          59 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4864             :                     {
    4865          57 :                         snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
    4866             :                                  aosCoordinates[i]);
    4867             :                     }
    4868             :                 }
    4869          97 :                 else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
    4870             :                 {
    4871          59 :                     int nOtherGroupId = -1;
    4872          59 :                     int nOtherVarId = -1;
    4873             :                     // Check that the variable actually exists
    4874             :                     // Needed on Sentinel-3 products
    4875          59 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4876          59 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4877             :                     {
    4878          57 :                         snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
    4879             :                                  aosCoordinates[i]);
    4880             :                     }
    4881             :                 }
    4882             :             }
    4883             :             // Add GEOLOCATION metadata.
    4884          70 :             if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
    4885             :             {
    4886          57 :                 osGeolocXNameOut = szGeolocXName;
    4887          57 :                 osGeolocYNameOut = szGeolocYName;
    4888             : 
    4889         114 :                 std::string osGeolocXFullName;
    4890         114 :                 std::string osGeolocYFullName;
    4891          57 :                 if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
    4892         114 :                                            osGeolocXFullName) == CE_None &&
    4893          57 :                     NCDFResolveVarFullName(nGroupId, szGeolocYName,
    4894             :                                            osGeolocYFullName) == CE_None)
    4895             :                 {
    4896          57 :                     if (bSwitchedXY)
    4897             :                     {
    4898           2 :                         std::swap(osGeolocXFullName, osGeolocYFullName);
    4899           2 :                         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
    4900             :                                                         GDAL_MDD_GEOLOCATION);
    4901             :                     }
    4902             : 
    4903          57 :                     bAddGeoloc = true;
    4904          57 :                     CPLDebug("GDAL_netCDF",
    4905             :                              "using variables %s and %s for GEOLOCATION",
    4906             :                              osGeolocXFullName.c_str(),
    4907             :                              osGeolocYFullName.c_str());
    4908             : 
    4909          57 :                     GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4910             :                                                     GDAL_MDD_GEOLOCATION);
    4911             : 
    4912         114 :                     CPLString osTMP;
    4913             :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4914          57 :                                  osGeolocXFullName.c_str());
    4915             : 
    4916          57 :                     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
    4917             :                                                     GDAL_MDD_GEOLOCATION);
    4918          57 :                     GDALPamDataset::SetMetadataItem("X_BAND", "1",
    4919             :                                                     GDAL_MDD_GEOLOCATION);
    4920             :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4921          57 :                                  osGeolocYFullName.c_str());
    4922             : 
    4923          57 :                     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
    4924             :                                                     GDAL_MDD_GEOLOCATION);
    4925          57 :                     GDALPamDataset::SetMetadataItem("Y_BAND", "1",
    4926             :                                                     GDAL_MDD_GEOLOCATION);
    4927             : 
    4928          57 :                     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
    4929             :                                                     GDAL_MDD_GEOLOCATION);
    4930          57 :                     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
    4931             :                                                     GDAL_MDD_GEOLOCATION);
    4932             : 
    4933          57 :                     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
    4934             :                                                     GDAL_MDD_GEOLOCATION);
    4935          57 :                     GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
    4936             :                                                     GDAL_MDD_GEOLOCATION);
    4937             : 
    4938          57 :                     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4939             :                                                     "PIXEL_CENTER",
    4940             :                                                     GDAL_MDD_GEOLOCATION);
    4941             :                 }
    4942             :                 else
    4943             :                 {
    4944           0 :                     CPLDebug("GDAL_netCDF",
    4945             :                              "cannot resolve location of "
    4946             :                              "lat/lon variables specified by the coordinates "
    4947             :                              "attribute [%s]",
    4948             :                              pszCoordinates);
    4949          57 :                 }
    4950             :             }
    4951             :             else
    4952             :             {
    4953          13 :                 CPLDebug("GDAL_netCDF",
    4954             :                          "coordinates attribute [%s] is unsupported",
    4955             :                          pszCoordinates);
    4956             :             }
    4957             :         }
    4958             :         else
    4959             :         {
    4960           4 :             CPLDebug("GDAL_netCDF",
    4961             :                      "coordinates attribute [%s] with %d element(s) is "
    4962             :                      "unsupported",
    4963             :                      pszCoordinates, aosCoordinates.size());
    4964             :         }
    4965             :     }
    4966             : 
    4967             :     else
    4968             :     {
    4969         308 :         bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
    4970             : 
    4971         308 :         if (!bAddGeoloc)
    4972         307 :             bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
    4973             :     }
    4974             : 
    4975         382 :     CPLFree(pszCoordinates);
    4976             : 
    4977         382 :     return bAddGeoloc;
    4978             : }
    4979             : 
    4980           8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
    4981             :                                        const char *szDimName)
    4982             : {
    4983             :     // Get values.
    4984           8 :     char *pszVarValues = nullptr;
    4985           8 :     CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
    4986           8 :     if (eErr != CE_None)
    4987           0 :         return eErr;
    4988             : 
    4989             :     // Write metadata.
    4990           8 :     char szTemp[NC_MAX_NAME + 1 + 32] = {};
    4991           8 :     snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
    4992           8 :     GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
    4993             : 
    4994           8 :     CPLFree(pszVarValues);
    4995             : 
    4996           8 :     return CE_None;
    4997             : }
    4998             : 
    4999           0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
    5000             :                                         int &nVarLen)
    5001             : {
    5002           0 :     nVarLen = 0;
    5003             : 
    5004             :     // Get Y_VALUES as tokens.
    5005             :     const CPLStringList aosValues(
    5006           0 :         NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")));
    5007           0 :     if (aosValues.empty())
    5008           0 :         return nullptr;
    5009             : 
    5010             :     // Initialize and fill array.
    5011           0 :     nVarLen = aosValues.size();
    5012             :     double *pdfVarValues =
    5013           0 :         static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
    5014             : 
    5015           0 :     for (int i = 0, j = 0; i < nVarLen; i++)
    5016             :     {
    5017           0 :         if (!bBottomUp)
    5018           0 :             j = nVarLen - 1 - i;
    5019             :         else
    5020           0 :             j = i;  // Invert latitude values.
    5021           0 :         char *pszTemp = nullptr;
    5022           0 :         pdfVarValues[j] = CPLStrtod(aosValues[i], &pszTemp);
    5023             :     }
    5024             : 
    5025           0 :     return pdfVarValues;
    5026             : }
    5027             : 
    5028             : /************************************************************************/
    5029             : /*                       SetSpatialRefNoUpdate()                        */
    5030             : /************************************************************************/
    5031             : 
    5032         312 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
    5033             : {
    5034         312 :     m_oSRS.Clear();
    5035         312 :     if (poSRS)
    5036         304 :         m_oSRS = *poSRS;
    5037         312 :     m_bHasProjection = true;
    5038         312 : }
    5039             : 
    5040             : /************************************************************************/
    5041             : /*                           SetSpatialRef()                            */
    5042             : /************************************************************************/
    5043             : 
    5044          88 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    5045             : {
    5046         176 :     CPLMutexHolderD(&hNCMutex);
    5047             : 
    5048          88 :     if (GetAccess() != GA_Update || m_bHasProjection)
    5049             :     {
    5050           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5051             :                  "netCDFDataset::_SetProjection() should only be called once "
    5052             :                  "in update mode!");
    5053           0 :         return CE_Failure;
    5054             :     }
    5055             : 
    5056          88 :     if (m_bHasGeoTransform)
    5057             :     {
    5058          32 :         SetSpatialRefNoUpdate(poSRS);
    5059             : 
    5060             :         // For NC4/NC4C, writing both projection variables and data,
    5061             :         // followed by redefining nodata value, cancels the projection
    5062             :         // info from the Band variable, so for now only write the
    5063             :         // variable definitions, and write data at the end.
    5064             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5065          32 :         return AddProjectionVars(true, nullptr, nullptr);
    5066             :     }
    5067             : 
    5068          56 :     SetSpatialRefNoUpdate(poSRS);
    5069             : 
    5070          56 :     return CE_None;
    5071             : }
    5072             : 
    5073             : /************************************************************************/
    5074             : /*                      SetGeoTransformNoUpdate()                       */
    5075             : /************************************************************************/
    5076             : 
    5077         315 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform &gt)
    5078             : {
    5079         315 :     m_gt = gt;
    5080         315 :     m_bHasGeoTransform = true;
    5081         315 : }
    5082             : 
    5083             : /************************************************************************/
    5084             : /*                          SetGeoTransform()                           */
    5085             : /************************************************************************/
    5086             : 
    5087          89 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform &gt)
    5088             : {
    5089         178 :     CPLMutexHolderD(&hNCMutex);
    5090             : 
    5091          89 :     if (GetAccess() != GA_Update || m_bHasGeoTransform)
    5092             :     {
    5093           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5094             :                  "netCDFDataset::SetGeoTransform() should only be called once "
    5095             :                  "in update mode!");
    5096           0 :         return CE_Failure;
    5097             :     }
    5098             : 
    5099          89 :     CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt.xorig,
    5100          89 :              gt.xscale, gt.xrot, gt.yorig, gt.yrot, gt.yscale);
    5101             : 
    5102          89 :     SetGeoTransformNoUpdate(gt);
    5103             : 
    5104          89 :     if (m_bHasProjection)
    5105             :     {
    5106             : 
    5107             :         // For NC4/NC4C, writing both projection variables and data,
    5108             :         // followed by redefining nodata value, cancels the projection
    5109             :         // info from the Band variable, so for now only write the
    5110             :         // variable definitions, and write data at the end.
    5111             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5112           3 :         return AddProjectionVars(true, nullptr, nullptr);
    5113             :     }
    5114             : 
    5115          86 :     return CE_None;
    5116             : }
    5117             : 
    5118             : /************************************************************************/
    5119             : /*                        NCDFWriteSRSVariable()                        */
    5120             : /************************************************************************/
    5121             : 
    5122         143 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
    5123             :                          char **ppszCFProjection, bool bWriteGDALTags,
    5124             :                          const std::string &srsVarName)
    5125             : {
    5126         143 :     char *pszCFProjection = nullptr;
    5127         143 :     char **papszKeyValues = nullptr;
    5128         143 :     poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
    5129             : 
    5130         143 :     if (bWriteGDALTags)
    5131             :     {
    5132         142 :         const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
    5133         142 :         if (pszWKT)
    5134             :         {
    5135             :             // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
    5136         142 :             papszKeyValues =
    5137         142 :                 CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
    5138             :         }
    5139             :     }
    5140             : 
    5141         143 :     const int nValues = CSLCount(papszKeyValues);
    5142             : 
    5143             :     int NCDFVarID;
    5144         286 :     std::string varNameRadix(pszCFProjection);
    5145         143 :     int nCounter = 2;
    5146             :     while (true)
    5147             :     {
    5148         145 :         NCDFVarID = -1;
    5149         145 :         nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
    5150         145 :         if (NCDFVarID < 0)
    5151         140 :             break;
    5152             : 
    5153           5 :         int nbAttr = 0;
    5154           5 :         NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
    5155           5 :         bool bSame = nbAttr == nValues;
    5156          41 :         for (int i = 0; bSame && (i < nbAttr); i++)
    5157             :         {
    5158             :             char szAttrName[NC_MAX_NAME + 1];
    5159          38 :             szAttrName[0] = 0;
    5160          38 :             NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
    5161             : 
    5162             :             const char *pszValue =
    5163          38 :                 CSLFetchNameValue(papszKeyValues, szAttrName);
    5164          38 :             if (!pszValue)
    5165             :             {
    5166           0 :                 bSame = false;
    5167           2 :                 break;
    5168             :             }
    5169             : 
    5170          38 :             nc_type atttype = NC_NAT;
    5171          38 :             size_t attlen = 0;
    5172          38 :             NCDF_ERR(
    5173             :                 nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
    5174          38 :             if (atttype != NC_CHAR && atttype != NC_DOUBLE)
    5175             :             {
    5176           0 :                 bSame = false;
    5177           0 :                 break;
    5178             :             }
    5179          38 :             if (atttype == NC_CHAR)
    5180             :             {
    5181          15 :                 if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
    5182             :                 {
    5183           0 :                     bSame = false;
    5184           0 :                     break;
    5185             :                 }
    5186          15 :                 std::string val;
    5187          15 :                 NCDFGetAttr(cdfid, NCDFVarID, szAttrName, val);
    5188          15 :                 if (val != pszValue)
    5189             :                 {
    5190           0 :                     bSame = false;
    5191           0 :                     break;
    5192             :                 }
    5193             :             }
    5194             :             else
    5195             :             {
    5196             :                 const CPLStringList aosTokens(
    5197          23 :                     CSLTokenizeString2(pszValue, ",", 0));
    5198          23 :                 if (static_cast<size_t>(aosTokens.size()) != attlen)
    5199             :                 {
    5200           0 :                     bSame = false;
    5201           0 :                     break;
    5202             :                 }
    5203             :                 double vals[2];
    5204          23 :                 nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
    5205          44 :                 if (vals[0] != CPLAtof(aosTokens[0]) ||
    5206          21 :                     (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
    5207             :                 {
    5208           2 :                     bSame = false;
    5209           2 :                     break;
    5210             :                 }
    5211             :             }
    5212             :         }
    5213           5 :         if (bSame)
    5214             :         {
    5215           3 :             *ppszCFProjection = pszCFProjection;
    5216           3 :             CSLDestroy(papszKeyValues);
    5217           3 :             return NCDFVarID;
    5218             :         }
    5219           2 :         CPLFree(pszCFProjection);
    5220           2 :         pszCFProjection =
    5221           2 :             CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
    5222           2 :         nCounter++;
    5223           2 :     }
    5224             : 
    5225         140 :     *ppszCFProjection = pszCFProjection;
    5226             : 
    5227             :     const char *pszVarName;
    5228             : 
    5229         140 :     if (srsVarName != "")
    5230             :     {
    5231          38 :         pszVarName = srsVarName.c_str();
    5232             :     }
    5233             :     else
    5234             :     {
    5235         102 :         pszVarName = pszCFProjection;
    5236             :     }
    5237             : 
    5238         140 :     int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
    5239         140 :     NCDF_ERR(status);
    5240        1394 :     for (int i = 0; i < nValues; ++i)
    5241             :     {
    5242        1254 :         char *pszKey = nullptr;
    5243        1254 :         const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
    5244        1254 :         if (pszKey && pszValue)
    5245             :         {
    5246        2508 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
    5247        1254 :             double adfValues[2] = {0, 0};
    5248        1254 :             const int nDoubleCount = std::min(2, aosTokens.size());
    5249        1254 :             if (!(aosTokens.size() == 2 &&
    5250        2507 :                   CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
    5251        1253 :                 CPLGetValueType(pszValue) == CPL_VALUE_STRING)
    5252             :             {
    5253         559 :                 status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
    5254             :                                          strlen(pszValue), pszValue);
    5255             :             }
    5256             :             else
    5257             :             {
    5258        1391 :                 for (int j = 0; j < nDoubleCount; ++j)
    5259         696 :                     adfValues[j] = CPLAtof(aosTokens[j]);
    5260         695 :                 status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
    5261             :                                            nDoubleCount, adfValues);
    5262             :             }
    5263        1254 :             NCDF_ERR(status);
    5264             :         }
    5265        1254 :         CPLFree(pszKey);
    5266             :     }
    5267             : 
    5268         140 :     CSLDestroy(papszKeyValues);
    5269         140 :     return NCDFVarID;
    5270             : }
    5271             : 
    5272             : /************************************************************************/
    5273             : /*                   NCDFWriteLonLatVarsAttributes()                    */
    5274             : /************************************************************************/
    5275             : 
    5276         104 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
    5277             :                                    int nVarLatID)
    5278             : {
    5279             : 
    5280             :     try
    5281             :     {
    5282         104 :         vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
    5283         104 :         vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
    5284         104 :         vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
    5285         104 :         vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
    5286         104 :         vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
    5287         104 :         vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
    5288             :     }
    5289           0 :     catch (nccfdriver::SG_Exception &e)
    5290             :     {
    5291           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5292             :     }
    5293         104 : }
    5294             : 
    5295             : /************************************************************************/
    5296             : /*                  NCDFWriteRLonRLatVarsAttributes()                   */
    5297             : /************************************************************************/
    5298             : 
    5299           0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
    5300             :                                      int nVarRLonID, int nVarRLatID)
    5301             : {
    5302             :     try
    5303             :     {
    5304           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
    5305           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
    5306             :                               "latitude in rotated pole grid");
    5307           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
    5308           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
    5309             : 
    5310           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
    5311           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
    5312             :                               "longitude in rotated pole grid");
    5313           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
    5314           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
    5315             :     }
    5316           0 :     catch (nccfdriver::SG_Exception &e)
    5317             :     {
    5318           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5319             :     }
    5320           0 : }
    5321             : 
    5322             : /************************************************************************/
    5323             : /*                       NCDFGetProjectedCFUnit()                       */
    5324             : /************************************************************************/
    5325             : 
    5326          51 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
    5327             : {
    5328          51 :     char *pszUnitsToWrite = nullptr;
    5329          51 :     poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
    5330          51 :     std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
    5331          51 :     CPLFree(pszUnitsToWrite);
    5332         102 :     return osRet;
    5333             : }
    5334             : 
    5335             : /************************************************************************/
    5336             : /*                     NCDFWriteXYVarsAttributes()                      */
    5337             : /************************************************************************/
    5338             : 
    5339          36 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
    5340             :                                int nVarYID, const OGRSpatialReference *poSRS)
    5341             : {
    5342          72 :     const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
    5343             : 
    5344             :     try
    5345             :     {
    5346          36 :         vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
    5347          36 :         vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
    5348          36 :         if (!osUnitsToWrite.empty())
    5349          36 :             vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
    5350          36 :         vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
    5351          36 :         vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
    5352          36 :         if (!osUnitsToWrite.empty())
    5353          36 :             vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
    5354             :     }
    5355           0 :     catch (nccfdriver::SG_Exception &e)
    5356             :     {
    5357           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5358             :     }
    5359          36 : }
    5360             : 
    5361             : /************************************************************************/
    5362             : /*                         AddProjectionVars()                          */
    5363             : /************************************************************************/
    5364             : 
    5365         188 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
    5366             :                                         GDALProgressFunc pfnProgress,
    5367             :                                         void *pProgressData)
    5368             : {
    5369         188 :     if (nCFVersionMajor > 1 || (nCFVersionMajor == 1 && nCFVersionMinor >= 8))
    5370           0 :         return CE_None;  // do nothing
    5371             : 
    5372         188 :     bool bWriteGridMapping = false;
    5373         188 :     bool bWriteLonLat = false;
    5374         188 :     bool bHasGeoloc = false;
    5375         188 :     bool bWriteGDALTags = false;
    5376         188 :     bool bWriteGeoTransform = false;
    5377             : 
    5378             :     // For GEOLOCATION information.
    5379         188 :     GDALDatasetUniquePtr poDS_X;
    5380         188 :     GDALDatasetUniquePtr poDS_Y;
    5381         188 :     GDALRasterBand *poBand_X = nullptr;
    5382         188 :     GDALRasterBand *poBand_Y = nullptr;
    5383             : 
    5384         376 :     OGRSpatialReference oSRS(m_oSRS);
    5385         188 :     if (!m_oSRS.IsEmpty())
    5386             :     {
    5387         160 :         if (oSRS.IsProjected())
    5388          72 :             bIsProjected = true;
    5389          88 :         else if (oSRS.IsGeographic())
    5390          88 :             bIsGeographic = true;
    5391             :     }
    5392             : 
    5393         188 :     if (bDefsOnly)
    5394             :     {
    5395         188 :         const std::string osProjection = m_oSRS.exportToWkt();
    5396         174 :         CPLDebug("GDAL_netCDF",
    5397             :                  "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
    5398          80 :                  osProjection.empty() ? "(null)" : osProjection.c_str(),
    5399          94 :                  static_cast<int>(bIsProjected),
    5400          94 :                  static_cast<int>(bIsGeographic));
    5401             : 
    5402          94 :         if (!m_bHasGeoTransform)
    5403           5 :             CPLDebug("GDAL_netCDF",
    5404             :                      "netCDFDataset::AddProjectionVars() called, "
    5405             :                      "but GeoTransform has not yet been defined!");
    5406             : 
    5407          94 :         if (!m_bHasProjection)
    5408           6 :             CPLDebug("GDAL_netCDF",
    5409             :                      "netCDFDataset::AddProjectionVars() called, "
    5410             :                      "but Projection has not yet been defined!");
    5411             :     }
    5412             : 
    5413             :     // Check GEOLOCATION information.
    5414             :     CSLConstList papszGeolocationInfo =
    5415         188 :         netCDFDataset::GetMetadata(GDAL_MDD_GEOLOCATION);
    5416         188 :     if (papszGeolocationInfo != nullptr)
    5417             :     {
    5418             :         // Look for geolocation datasets.
    5419             :         const char *pszDSName =
    5420          12 :             CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
    5421          12 :         if (pszDSName != nullptr)
    5422          12 :             poDS_X.reset(GDALDataset::Open(
    5423             :                 pszDSName,
    5424             :                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
    5425          12 :         pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
    5426          12 :         if (pszDSName != nullptr)
    5427          12 :             poDS_Y.reset(GDALDataset::Open(
    5428             :                 pszDSName,
    5429             :                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
    5430             : 
    5431          12 :         if (poDS_X != nullptr && poDS_Y != nullptr)
    5432             :         {
    5433          12 :             int nBand = std::max(1, atoi(CSLFetchNameValueDef(
    5434          12 :                                         papszGeolocationInfo, "X_BAND", "0")));
    5435          12 :             poBand_X = poDS_X->GetRasterBand(nBand);
    5436          12 :             nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
    5437          12 :                                                           "Y_BAND", "0")));
    5438          12 :             poBand_Y = poDS_Y->GetRasterBand(nBand);
    5439             : 
    5440             :             // If geoloc bands are found, do basic validation based on their
    5441             :             // dimensions.
    5442          12 :             if (poBand_X != nullptr && poBand_Y != nullptr)
    5443             :             {
    5444          12 :                 const int nXSize_XBand = poBand_X->GetXSize();
    5445          12 :                 const int nYSize_XBand = poBand_X->GetYSize();
    5446          12 :                 const int nXSize_YBand = poBand_Y->GetXSize();
    5447          12 :                 const int nYSize_YBand = poBand_Y->GetYSize();
    5448             : 
    5449             :                 // TODO 1D geolocation arrays not implemented.
    5450          12 :                 if (nYSize_XBand == 1 && nYSize_YBand == 1)
    5451             :                 {
    5452           2 :                     bHasGeoloc = false;
    5453           2 :                     CPLDebug("GDAL_netCDF",
    5454             :                              "1D GEOLOCATION arrays not supported yet");
    5455             :                 }
    5456             :                 // 2D bands must have same sizes as the raster bands.
    5457          10 :                 else if (nXSize_XBand != nRasterXSize ||
    5458          10 :                          nYSize_XBand != nRasterYSize ||
    5459          10 :                          nXSize_YBand != nRasterXSize ||
    5460          10 :                          nYSize_YBand != nRasterYSize)
    5461             :                 {
    5462           0 :                     bHasGeoloc = false;
    5463           0 :                     CPLDebug("GDAL_netCDF",
    5464             :                              "GEOLOCATION array sizes (%dx%d %dx%d) differ "
    5465             :                              "from raster (%dx%d), not supported",
    5466             :                              nXSize_XBand, nYSize_XBand, nXSize_YBand,
    5467             :                              nYSize_YBand, nRasterXSize, nRasterYSize);
    5468             :                 }
    5469             :                 else
    5470             :                 {
    5471          10 :                     bHasGeoloc = true;
    5472          10 :                     CPLDebug("GDAL_netCDF",
    5473             :                              "dataset has GEOLOCATION information, will try to "
    5474             :                              "write it");
    5475             :                 }
    5476             :             }
    5477             :         }
    5478             :     }
    5479             : 
    5480             :     // Process projection options.
    5481         188 :     if (bIsProjected)
    5482             :     {
    5483             :         bool bIsCfProjection =
    5484          72 :             oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
    5485          72 :         bWriteGridMapping = true;
    5486          72 :         bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS", true);
    5487             :         // Force WRITE_GDAL_TAGS if is not a CF projection.
    5488          72 :         if (!bWriteGDALTags && !bIsCfProjection)
    5489           0 :             bWriteGDALTags = true;
    5490          72 :         if (bWriteGDALTags)
    5491          72 :             bWriteGeoTransform = true;
    5492             : 
    5493             :         // Write lon/lat: default is NO, except if has geolocation.
    5494             :         // With IF_NEEDED: write if has geoloc or is not CF projection.
    5495             :         const char *pszValue =
    5496          72 :             aosCreationOptions.FetchNameValue("WRITE_LONLAT");
    5497          72 :         if (pszValue)
    5498             :         {
    5499           6 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5500             :             {
    5501           0 :                 bWriteLonLat = bHasGeoloc || !bIsCfProjection;
    5502             :             }
    5503             :             else
    5504             :             {
    5505           6 :                 bWriteLonLat = CPLTestBool(pszValue);
    5506             :             }
    5507             :         }
    5508             :         else
    5509             :         {
    5510          66 :             bWriteLonLat = bHasGeoloc;
    5511             :         }
    5512             : 
    5513             :         // Save value of pszCFCoordinates for later.
    5514          72 :         if (bWriteLonLat)
    5515             :         {
    5516           8 :             pszCFCoordinates = NCDF_LONLAT;
    5517             :         }
    5518             :     }
    5519             :     else
    5520             :     {
    5521             :         // Files without a Datum will not have a grid_mapping variable and
    5522             :         // geographic information.
    5523         116 :         bWriteGridMapping = bIsGeographic;
    5524             : 
    5525         116 :         if (bHasGeoloc)
    5526             :         {
    5527           8 :             bWriteLonLat = true;
    5528             :         }
    5529             :         else
    5530             :         {
    5531         108 :             bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS",
    5532             :                                                           bWriteGridMapping);
    5533         108 :             if (bWriteGDALTags)
    5534          88 :                 bWriteGeoTransform = true;
    5535             : 
    5536             :             const char *pszValue =
    5537         108 :                 aosCreationOptions.FetchNameValueDef("WRITE_LONLAT", "YES");
    5538         108 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5539           0 :                 bWriteLonLat = true;
    5540             :             else
    5541         108 :                 bWriteLonLat = CPLTestBool(pszValue);
    5542             :             //  Don't write lon/lat if no source geotransform.
    5543         108 :             if (!m_bHasGeoTransform)
    5544           0 :                 bWriteLonLat = false;
    5545             :             // If we don't write lon/lat, set dimnames to X/Y and write gdal
    5546             :             // tags.
    5547         108 :             if (!bWriteLonLat)
    5548             :             {
    5549           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5550             :                          "creating geographic file without lon/lat values!");
    5551           0 :                 if (m_bHasGeoTransform)
    5552             :                 {
    5553           0 :                     bWriteGDALTags = true;  // Not desirable if no geotransform.
    5554           0 :                     bWriteGeoTransform = true;
    5555             :                 }
    5556             :             }
    5557             :         }
    5558             :     }
    5559             : 
    5560             :     // Make sure we write grid_mapping if we need to write GDAL tags.
    5561         188 :     if (bWriteGDALTags)
    5562         160 :         bWriteGridMapping = true;
    5563             : 
    5564             :     // bottom-up value: new driver is bottom-up by default.
    5565             :     // Override with WRITE_BOTTOMUP.
    5566         188 :     bBottomUp = aosCreationOptions.FetchBool("WRITE_BOTTOMUP", true);
    5567             : 
    5568         188 :     if (bDefsOnly)
    5569             :     {
    5570          94 :         CPLDebug(
    5571             :             "GDAL_netCDF",
    5572             :             "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
    5573             :             "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
    5574          94 :             static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
    5575             :             static_cast<int>(bWriteGridMapping),
    5576             :             static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
    5577          94 :             static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
    5578             :     }
    5579             : 
    5580             :     // Exit if nothing to do.
    5581         188 :     if (!bIsProjected && !bWriteLonLat)
    5582           0 :         return CE_None;
    5583             : 
    5584             :     // Define dimension names.
    5585             : 
    5586         188 :     constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
    5587             : 
    5588         188 :     if (bDefsOnly)
    5589             :     {
    5590          94 :         int nVarLonID = -1;
    5591          94 :         int nVarLatID = -1;
    5592          94 :         int nVarXID = -1;
    5593          94 :         int nVarYID = -1;
    5594             : 
    5595          94 :         m_bAddedProjectionVarsDefs = true;
    5596             : 
    5597             :         // Make sure we are in define mode.
    5598          94 :         SetDefineMode(true);
    5599             : 
    5600             :         // Write projection attributes.
    5601          94 :         if (bWriteGridMapping)
    5602             :         {
    5603          80 :             const int NCDFVarID = NCDFWriteSRSVariable(
    5604             :                 cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
    5605          80 :             if (NCDFVarID < 0)
    5606           0 :                 return CE_Failure;
    5607             : 
    5608             :             // Optional GDAL custom projection tags.
    5609          80 :             if (bWriteGDALTags && bWriteGeoTransform && m_bHasGeoTransform)
    5610             :             {
    5611          79 :                 GDALGeoTransform gt(m_gt);
    5612          79 :                 if (!bBottomUp)
    5613             :                 {
    5614             :                     // Change origin from top to bottom and sign of coefficients
    5615             :                     // indexed by row
    5616           2 :                     gt.yorig += nRasterYSize * gt.yscale;
    5617           2 :                     gt.xorig += nRasterYSize * gt.xrot;
    5618           2 :                     gt.xrot = -gt.xrot;
    5619           2 :                     gt.yscale = -gt.yscale;
    5620             :                 }
    5621         158 :                 std::string osGeoTransform = gt.ToString(" ");
    5622          79 :                 CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
    5623             :                          osGeoTransform.c_str());
    5624             : 
    5625          79 :                 const int status = nc_put_att_text(
    5626             :                     cdfid, NCDFVarID, NCDF_GEOTRANSFORM, osGeoTransform.size(),
    5627             :                     osGeoTransform.c_str());
    5628          79 :                 NCDF_ERR(status);
    5629             :             }
    5630             : 
    5631             :             // Write projection variable to band variable.
    5632             :             // Need to call later if there are no bands.
    5633          80 :             AddGridMappingRef();
    5634             :         }  // end if( bWriteGridMapping )
    5635             : 
    5636             :         // Write CF Projection vars.
    5637             : 
    5638          94 :         const bool bIsRotatedPole =
    5639         174 :             pszCFProjection != nullptr &&
    5640          80 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5641             : 
    5642          94 :         if (m_bHasGeoTransform && !m_gt.IsAxisAligned())
    5643             :         {
    5644             :             // Do not write X/Y coordinate arrays
    5645             :         }
    5646             : 
    5647          90 :         else if (bIsRotatedPole)
    5648             :         {
    5649             :             // Rename dims to rlat/rlon.
    5650             :             papszDimName
    5651           0 :                 .Clear();  // If we add other dims one day, this has to change
    5652           0 :             papszDimName.AddString(NCDF_DIMNAME_RLAT);
    5653           0 :             papszDimName.AddString(NCDF_DIMNAME_RLON);
    5654             : 
    5655           0 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
    5656           0 :             NCDF_ERR(status);
    5657           0 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
    5658           0 :             NCDF_ERR(status);
    5659             :         }
    5660             :         // Rename dimensions if lon/lat.
    5661          90 :         else if (!bIsProjected && !bHasGeoloc)
    5662             :         {
    5663             :             // Rename dims to lat/lon.
    5664             :             papszDimName
    5665          54 :                 .Clear();  // If we add other dims one day, this has to change
    5666          54 :             papszDimName.AddString(NCDF_DIMNAME_LAT);
    5667          54 :             papszDimName.AddString(NCDF_DIMNAME_LON);
    5668             : 
    5669          54 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
    5670          54 :             NCDF_ERR(status);
    5671          54 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
    5672          54 :             NCDF_ERR(status);
    5673             :         }
    5674             : 
    5675             :         // Write X/Y attributes.
    5676             :         else /* if( bIsProjected || bHasGeoloc ) */
    5677             :         {
    5678             :             // X
    5679             :             int anXDims[1];
    5680          36 :             anXDims[0] = nXDimID;
    5681          36 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5682             :                      CF_PROJ_X_VAR_NAME, NC_DOUBLE);
    5683          36 :             int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
    5684             :                                     anXDims, &nVarXID);
    5685          36 :             NCDF_ERR(status);
    5686             : 
    5687             :             // Y
    5688             :             int anYDims[1];
    5689          36 :             anYDims[0] = nYDimID;
    5690          36 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5691             :                      CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
    5692          36 :             status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
    5693             :                                 anYDims, &nVarYID);
    5694          36 :             NCDF_ERR(status);
    5695             : 
    5696          36 :             if (bIsProjected)
    5697             :             {
    5698          32 :                 NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
    5699             :             }
    5700             :             else
    5701             :             {
    5702           4 :                 CPLAssert(bHasGeoloc);
    5703             :                 try
    5704             :                 {
    5705           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
    5706           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
    5707             :                                           "x-coordinate in Cartesian system");
    5708           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
    5709           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
    5710           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
    5711             :                                           "y-coordinate in Cartesian system");
    5712           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
    5713             : 
    5714           4 :                     pszCFCoordinates = NCDF_LONLAT;
    5715             :                 }
    5716           0 :                 catch (nccfdriver::SG_Exception &e)
    5717             :                 {
    5718           0 :                     CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5719           0 :                     return CE_Failure;
    5720             :                 }
    5721             :             }
    5722             :         }
    5723             : 
    5724             :         // Write lat/lon attributes if needed.
    5725          94 :         if (bWriteLonLat)
    5726             :         {
    5727          62 :             int anLatDims[2] = {0, 0};
    5728          62 :             int anLonDims[2] = {0, 0};
    5729          62 :             int nLatDims = -1;
    5730          62 :             int nLonDims = -1;
    5731             : 
    5732             :             // Get information.
    5733          62 :             if (bHasGeoloc)
    5734             :             {
    5735             :                 // Geoloc
    5736           5 :                 nLatDims = 2;
    5737           5 :                 anLatDims[0] = nYDimID;
    5738           5 :                 anLatDims[1] = nXDimID;
    5739           5 :                 nLonDims = 2;
    5740           5 :                 anLonDims[0] = nYDimID;
    5741           5 :                 anLonDims[1] = nXDimID;
    5742             :             }
    5743          57 :             else if (bIsProjected)
    5744             :             {
    5745             :                 // Projected
    5746           3 :                 nLatDims = 2;
    5747           3 :                 anLatDims[0] = nYDimID;
    5748           3 :                 anLatDims[1] = nXDimID;
    5749           3 :                 nLonDims = 2;
    5750           3 :                 anLonDims[0] = nYDimID;
    5751           3 :                 anLonDims[1] = nXDimID;
    5752             :             }
    5753             :             else
    5754             :             {
    5755             :                 // Geographic
    5756          54 :                 nLatDims = 1;
    5757          54 :                 anLatDims[0] = nYDimID;
    5758          54 :                 nLonDims = 1;
    5759          54 :                 anLonDims[0] = nXDimID;
    5760             :             }
    5761             : 
    5762          62 :             nc_type eLonLatType = NC_NAT;
    5763          62 :             if (bIsProjected)
    5764             :             {
    5765           4 :                 eLonLatType = NC_FLOAT;
    5766           4 :                 const char *pszValue = aosCreationOptions.FetchNameValueDef(
    5767             :                     "TYPE_LONLAT", "FLOAT");
    5768           4 :                 if (EQUAL(pszValue, "DOUBLE"))
    5769           0 :                     eLonLatType = NC_DOUBLE;
    5770             :             }
    5771             :             else
    5772             :             {
    5773          58 :                 eLonLatType = NC_DOUBLE;
    5774          58 :                 const char *pszValue = aosCreationOptions.FetchNameValueDef(
    5775             :                     "TYPE_LONLAT", "DOUBLE");
    5776          58 :                 if (EQUAL(pszValue, "FLOAT"))
    5777           0 :                     eLonLatType = NC_FLOAT;
    5778             :             }
    5779             : 
    5780             :             // Def vars and attributes.
    5781             :             {
    5782          62 :                 const char *pszVarName =
    5783          62 :                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
    5784          62 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5785             :                                         nLatDims, anLatDims, &nVarLatID);
    5786          62 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5787             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
    5788          62 :                 NCDF_ERR(status);
    5789          62 :                 DefVarDeflate(nVarLatID, false);  // Don't set chunking.
    5790             :             }
    5791             : 
    5792             :             {
    5793          62 :                 const char *pszVarName =
    5794          62 :                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
    5795          62 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5796             :                                         nLonDims, anLonDims, &nVarLonID);
    5797          62 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5798             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
    5799          62 :                 NCDF_ERR(status);
    5800          62 :                 DefVarDeflate(nVarLonID, false);  // Don't set chunking.
    5801             :             }
    5802             : 
    5803          62 :             if (bIsRotatedPole)
    5804           0 :                 NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
    5805             :                                                 nVarLatID);
    5806             :             else
    5807          62 :                 NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
    5808             :         }
    5809             :     }
    5810             : 
    5811         188 :     if (!bDefsOnly)
    5812             :     {
    5813          94 :         m_bAddedProjectionVarsData = true;
    5814             : 
    5815          94 :         int nVarXID = -1;
    5816          94 :         int nVarYID = -1;
    5817             : 
    5818          94 :         nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
    5819          94 :         nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
    5820             : 
    5821          94 :         int nVarLonID = -1;
    5822          94 :         int nVarLatID = -1;
    5823             : 
    5824          94 :         const bool bIsRotatedPole =
    5825         174 :             pszCFProjection != nullptr &&
    5826          80 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5827          94 :         nc_inq_varid(cdfid,
    5828             :                      bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
    5829             :                      &nVarLonID);
    5830          94 :         nc_inq_varid(cdfid,
    5831             :                      bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
    5832             :                      &nVarLatID);
    5833             : 
    5834             :         // Get projection values.
    5835             : 
    5836          94 :         if (bIsProjected)
    5837             :         {
    5838           0 :             std::unique_ptr<OGRSpatialReference> poLatLonSRS;
    5839           0 :             std::unique_ptr<OGRCoordinateTransformation> poTransform;
    5840             : 
    5841             :             size_t startX[1];
    5842             :             size_t countX[1];
    5843             :             size_t startY[1];
    5844             :             size_t countY[1];
    5845             : 
    5846          36 :             CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
    5847             : 
    5848             :             std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
    5849             :                 static_cast<double *>(
    5850          72 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5851          36 :                 VSIFree);
    5852             :             std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
    5853             :                 static_cast<double *>(
    5854          72 :                     VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
    5855          36 :                 VSIFree);
    5856          36 :             double *padXVal = adXValKeeper.get();
    5857          36 :             double *padYVal = adYValKeeper.get();
    5858          36 :             if (!padXVal || !padYVal)
    5859             :             {
    5860           0 :                 return CE_Failure;
    5861             :             }
    5862             : 
    5863             :             // Make sure we are in data mode.
    5864          36 :             SetDefineMode(false);
    5865             : 
    5866          36 :             int status = NC_NOERR;
    5867             : 
    5868          36 :             if (m_gt.IsAxisAligned())
    5869             :             {
    5870             :                 // Get Y values.
    5871          32 :                 const double dfY0 =
    5872          32 :                     (!bBottomUp) ? m_gt.yorig :
    5873             :                                  // Invert latitude values.
    5874          32 :                         m_gt.yorig + (m_gt.yscale * nRasterYSize);
    5875          32 :                 const double dfDY = m_gt.yscale;
    5876             : 
    5877        1583 :                 for (int j = 0; j < nRasterYSize; j++)
    5878             :                 {
    5879             :                     // The data point is centered inside the pixel.
    5880        1551 :                     if (!bBottomUp)
    5881           0 :                         padYVal[j] = dfY0 + (j + 0.5) * dfDY;
    5882             :                     else  // Invert latitude values.
    5883        1551 :                         padYVal[j] = dfY0 - (j + 0.5) * dfDY;
    5884             :                 }
    5885          32 :                 startX[0] = 0;
    5886          32 :                 countX[0] = nRasterXSize;
    5887             : 
    5888             :                 // Get X values.
    5889          32 :                 const double dfX0 = m_gt.xorig;
    5890          32 :                 const double dfDX = m_gt.xscale;
    5891             : 
    5892        1624 :                 for (int i = 0; i < nRasterXSize; i++)
    5893             :                 {
    5894             :                     // The data point is centered inside the pixel.
    5895        1592 :                     padXVal[i] = dfX0 + (i + 0.5) * dfDX;
    5896             :                 }
    5897          32 :                 startY[0] = 0;
    5898          32 :                 countY[0] = nRasterYSize;
    5899             : 
    5900             :                 // Write X/Y values.
    5901             : 
    5902          32 :                 CPLDebug("GDAL_netCDF", "Writing X values");
    5903             :                 status =
    5904          32 :                     nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
    5905          32 :                 NCDF_ERR(status);
    5906             : 
    5907          32 :                 CPLDebug("GDAL_netCDF", "Writing Y values");
    5908             :                 status =
    5909          32 :                     nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
    5910          32 :                 NCDF_ERR(status);
    5911             :             }
    5912             : 
    5913          36 :             if (pfnProgress)
    5914          32 :                 pfnProgress(0.20, nullptr, pProgressData);
    5915             : 
    5916             :             // Write lon/lat arrays (CF coordinates) if requested.
    5917             : 
    5918             :             // Get OGR transform if GEOLOCATION is not available.
    5919          36 :             if (bWriteLonLat && !bHasGeoloc)
    5920             :             {
    5921           3 :                 poLatLonSRS.reset(m_oSRS.CloneGeogCS());
    5922           3 :                 if (poLatLonSRS != nullptr)
    5923             :                 {
    5924           3 :                     poLatLonSRS->SetAxisMappingStrategy(
    5925             :                         OAMS_TRADITIONAL_GIS_ORDER);
    5926           3 :                     poTransform.reset(OGRCreateCoordinateTransformation(
    5927           3 :                         &m_oSRS, poLatLonSRS.get()));
    5928             :                 }
    5929             :                 // If no OGR transform, then don't write CF lon/lat.
    5930           3 :                 if (poTransform == nullptr)
    5931             :                 {
    5932           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5933             :                              "Unable to get Coordinate Transform");
    5934           0 :                     bWriteLonLat = false;
    5935             :                 }
    5936             :             }
    5937             : 
    5938          36 :             if (bWriteLonLat)
    5939             :             {
    5940           4 :                 if (!bHasGeoloc)
    5941           3 :                     CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
    5942             :                 else
    5943           1 :                     CPLDebug("GDAL_netCDF",
    5944             :                              "Writing (lon,lat) from GEOLOCATION arrays");
    5945             : 
    5946           4 :                 bool bOK = true;
    5947           4 :                 double dfProgress = 0.2;
    5948             : 
    5949           4 :                 size_t start[] = {0, 0};
    5950           4 :                 size_t count[] = {1, (size_t)nRasterXSize};
    5951             :                 std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
    5952             :                     static_cast<double *>(
    5953           8 :                         VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5954           4 :                     VSIFree);
    5955             :                 std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    5956             :                     static_cast<double *>(
    5957           8 :                         VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5958           4 :                     VSIFree);
    5959           4 :                 double *padLonVal = adLonValKeeper.get();
    5960           4 :                 double *padLatVal = adLatValKeeper.get();
    5961           4 :                 if (!padLonVal || !padLatVal)
    5962             :                 {
    5963           0 :                     return CE_Failure;
    5964             :                 }
    5965             : 
    5966         103 :                 for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
    5967             :                      j++)
    5968             :                 {
    5969          99 :                     start[0] = j;
    5970             : 
    5971             :                     // Get values from geotransform.
    5972          99 :                     if (!bHasGeoloc)
    5973             :                     {
    5974             :                         // Fill values to transform.
    5975          60 :                         if (m_gt.IsAxisAligned())
    5976             :                         {
    5977         420 :                             for (int i = 0; i < nRasterXSize; i++)
    5978             :                             {
    5979         400 :                                 padLatVal[i] = padYVal[j];
    5980         400 :                                 padLonVal[i] = padXVal[i];
    5981             :                             }
    5982             :                         }
    5983             :                         else
    5984             :                         {
    5985         840 :                             for (int i = 0; i < nRasterXSize; i++)
    5986             :                             {
    5987         800 :                                 if (!bBottomUp)
    5988             :                                 {
    5989         400 :                                     padLatVal[i] = m_gt.yorig +
    5990         400 :                                                    (i + 0.5) * m_gt.yrot +
    5991         400 :                                                    (j + 0.5) * m_gt.yscale;
    5992         400 :                                     padLonVal[i] = m_gt.xorig +
    5993         400 :                                                    (i + 0.5) * m_gt.xscale +
    5994         400 :                                                    (j + 0.5) * m_gt.xrot;
    5995             :                                 }
    5996             :                                 else
    5997             :                                 {
    5998         400 :                                     padLatVal[i] =
    5999         400 :                                         m_gt.yorig + (i + 0.5) * m_gt.yrot +
    6000         400 :                                         (nRasterYSize - j - 0.5) * m_gt.yscale;
    6001         400 :                                     padLonVal[i] =
    6002         400 :                                         m_gt.xorig + (i + 0.5) * m_gt.xscale +
    6003         400 :                                         (nRasterYSize - j - 0.5) * m_gt.xrot;
    6004             :                                 }
    6005             :                             }
    6006             :                         }
    6007             : 
    6008             :                         // Do the transform.
    6009         120 :                         bOK = CPL_TO_BOOL(poTransform->Transform(
    6010          60 :                             nRasterXSize, padLonVal, padLatVal, nullptr));
    6011          60 :                         if (!bOK)
    6012             :                         {
    6013           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    6014             :                                      "Unable to Transform (X,Y) to (lon,lat).");
    6015             :                         }
    6016             :                     }
    6017             :                     // Get values from geoloc arrays.
    6018             :                     else
    6019             :                     {
    6020          39 :                         CPLErr eErr = poBand_Y->RasterIO(
    6021             :                             GF_Read, 0, j, nRasterXSize, 1, padLatVal,
    6022             :                             nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
    6023          39 :                         if (eErr == CE_None)
    6024             :                         {
    6025          39 :                             eErr = poBand_X->RasterIO(
    6026             :                                 GF_Read, 0, j, nRasterXSize, 1, padLonVal,
    6027             :                                 nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
    6028             :                         }
    6029             : 
    6030          39 :                         if (eErr == CE_None)
    6031             :                         {
    6032          39 :                             bOK = true;
    6033             :                         }
    6034             :                         else
    6035             :                         {
    6036           0 :                             bOK = false;
    6037           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    6038             :                                      "Unable to get scanline %d", j);
    6039             :                         }
    6040             :                     }
    6041             : 
    6042             :                     // Write data.
    6043          99 :                     if (bOK)
    6044             :                     {
    6045          99 :                         status = nc_put_vara_double(cdfid, nVarLatID, start,
    6046             :                                                     count, padLatVal);
    6047          99 :                         NCDF_ERR(status);
    6048          99 :                         status = nc_put_vara_double(cdfid, nVarLonID, start,
    6049             :                                                     count, padLonVal);
    6050          99 :                         NCDF_ERR(status);
    6051             :                     }
    6052             : 
    6053          99 :                     if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6054          99 :                         (j % (nRasterYSize / 10) == 0))
    6055             :                     {
    6056          43 :                         dfProgress += 0.08;
    6057          43 :                         pfnProgress(dfProgress, nullptr, pProgressData);
    6058             :                     }
    6059             :                 }
    6060             :             }
    6061             :         }  // Projected
    6062             : 
    6063             :         // If not projected/geographic and has geoloc
    6064          58 :         else if (!bIsGeographic && bHasGeoloc && m_gt.IsAxisAligned())
    6065             :         {
    6066             :             // Use
    6067             :             // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
    6068             : 
    6069           4 :             bool bOK = true;
    6070           4 :             double dfProgress = 0.2;
    6071             : 
    6072             :             // Make sure we are in data mode.
    6073           4 :             SetDefineMode(false);
    6074             : 
    6075             :             size_t startX[1];
    6076             :             size_t countX[1];
    6077             :             size_t startY[1];
    6078             :             size_t countY[1];
    6079           4 :             startX[0] = 0;
    6080           4 :             countX[0] = nRasterXSize;
    6081             : 
    6082           4 :             startY[0] = 0;
    6083           4 :             countY[0] = nRasterYSize;
    6084             : 
    6085           4 :             std::vector<double> adfXVal;
    6086           4 :             std::vector<double> adfYVal;
    6087             :             try
    6088             :             {
    6089           4 :                 adfXVal.resize(nRasterXSize);
    6090           4 :                 adfYVal.resize(nRasterYSize);
    6091             :             }
    6092           0 :             catch (const std::exception &)
    6093             :             {
    6094           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
    6095             :                          "Out of memory allocating temporary array");
    6096           0 :                 return CE_Failure;
    6097             :             }
    6098          16 :             for (int i = 0; i < nRasterXSize; i++)
    6099          12 :                 adfXVal[i] = i;
    6100          12 :             for (int i = 0; i < nRasterYSize; i++)
    6101           8 :                 adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
    6102             : 
    6103           4 :             CPLDebug("GDAL_netCDF", "Writing X values");
    6104           4 :             int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
    6105           4 :                                             adfXVal.data());
    6106           4 :             NCDF_ERR(status);
    6107             : 
    6108           4 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    6109           4 :             status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
    6110           4 :                                         adfYVal.data());
    6111           4 :             NCDF_ERR(status);
    6112             : 
    6113           4 :             if (pfnProgress)
    6114           0 :                 pfnProgress(0.20, nullptr, pProgressData);
    6115             : 
    6116           4 :             size_t start[] = {0, 0};
    6117           4 :             size_t count[] = {1, (size_t)nRasterXSize};
    6118             : 
    6119             :             std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
    6120             :                 static_cast<double *>(
    6121           8 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6122           4 :                 VSIFree);
    6123             :             std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    6124             :                 static_cast<double *>(
    6125           8 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6126           4 :                 VSIFree);
    6127           4 :             double *padLonVal = adLonValKeeper.get();
    6128           4 :             double *padLatVal = adLatValKeeper.get();
    6129           4 :             if (!padLonVal || !padLatVal)
    6130             :             {
    6131           0 :                 return CE_Failure;
    6132             :             }
    6133             : 
    6134          12 :             for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
    6135             :             {
    6136           8 :                 start[0] = j;
    6137             : 
    6138           8 :                 CPLErr eErr = poBand_Y->RasterIO(
    6139           8 :                     GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
    6140             :                     nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
    6141             :                     0, nullptr);
    6142           8 :                 if (eErr == CE_None)
    6143             :                 {
    6144           8 :                     eErr = poBand_X->RasterIO(
    6145           8 :                         GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
    6146             :                         nRasterXSize, 1, padLonVal, nRasterXSize, 1,
    6147             :                         GDT_Float64, 0, 0, nullptr);
    6148             :                 }
    6149             : 
    6150           8 :                 if (eErr == CE_None)
    6151             :                 {
    6152           8 :                     bOK = true;
    6153             :                 }
    6154             :                 else
    6155             :                 {
    6156           0 :                     bOK = false;
    6157           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    6158             :                              "Unable to get scanline %d", j);
    6159             :                 }
    6160             : 
    6161             :                 // Write data.
    6162           8 :                 if (bOK)
    6163             :                 {
    6164           8 :                     status = nc_put_vara_double(cdfid, nVarLatID, start, count,
    6165             :                                                 padLatVal);
    6166           8 :                     NCDF_ERR(status);
    6167           8 :                     status = nc_put_vara_double(cdfid, nVarLonID, start, count,
    6168             :                                                 padLonVal);
    6169           8 :                     NCDF_ERR(status);
    6170             :                 }
    6171             : 
    6172           8 :                 if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6173           0 :                     (j % (nRasterYSize / 10) == 0))
    6174             :                 {
    6175           0 :                     dfProgress += 0.08;
    6176           0 :                     pfnProgress(dfProgress, nullptr, pProgressData);
    6177             :                 }
    6178             :             }
    6179             :         }
    6180             : 
    6181             :         // If not projected, assume geographic to catch grids without Datum.
    6182          54 :         else if (bWriteLonLat)
    6183             :         {
    6184             :             // Get latitude values.
    6185          54 :             const double dfY0 = (!bBottomUp) ? m_gt.yorig :
    6186             :                                              // Invert latitude values.
    6187          54 :                                     m_gt.yorig + (m_gt.yscale * nRasterYSize);
    6188          54 :             const double dfDY = m_gt.yscale;
    6189             : 
    6190             :             std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
    6191          54 :                                                                        VSIFree);
    6192          54 :             double *padLatVal = nullptr;
    6193             :             // Override lat values with the ones in GEOLOCATION/Y_VALUES.
    6194          54 :             if (netCDFDataset::GetMetadataItem("Y_VALUES",
    6195          54 :                                                GDAL_MDD_GEOLOCATION) != nullptr)
    6196             :             {
    6197           0 :                 int nTemp = 0;
    6198           0 :                 adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
    6199           0 :                 padLatVal = adLatValKeeper.get();
    6200             :                 // Make sure we got the correct amount, if not fallback to GT */
    6201             :                 // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
    6202           0 :                 if (nTemp == nRasterYSize)
    6203             :                 {
    6204           0 :                     CPLDebug(
    6205             :                         "GDAL_netCDF",
    6206             :                         "Using Y_VALUES geolocation metadata for lat values");
    6207             :                 }
    6208             :                 else
    6209             :                 {
    6210           0 :                     CPLDebug("GDAL_netCDF",
    6211             :                              "Got %d elements from Y_VALUES geolocation "
    6212             :                              "metadata, need %d",
    6213             :                              nTemp, nRasterYSize);
    6214           0 :                     padLatVal = nullptr;
    6215             :                 }
    6216             :             }
    6217             : 
    6218          54 :             if (padLatVal == nullptr)
    6219             :             {
    6220          54 :                 adLatValKeeper.reset(static_cast<double *>(
    6221          54 :                     VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
    6222          54 :                 padLatVal = adLatValKeeper.get();
    6223          54 :                 if (!padLatVal)
    6224             :                 {
    6225           0 :                     return CE_Failure;
    6226             :                 }
    6227        7126 :                 for (int i = 0; i < nRasterYSize; i++)
    6228             :                 {
    6229             :                     // The data point is centered inside the pixel.
    6230        7072 :                     if (!bBottomUp)
    6231           0 :                         padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
    6232             :                     else  // Invert latitude values.
    6233        7072 :                         padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
    6234             :                 }
    6235             :             }
    6236             : 
    6237          54 :             size_t startLat[1] = {0};
    6238          54 :             size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
    6239             : 
    6240             :             // Get longitude values.
    6241          54 :             const double dfX0 = m_gt.xorig;
    6242          54 :             const double dfDX = m_gt.xscale;
    6243             : 
    6244             :             std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    6245             :                 static_cast<double *>(
    6246         108 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6247          54 :                 VSIFree);
    6248          54 :             double *padLonVal = adLonValKeeper.get();
    6249          54 :             if (!padLonVal)
    6250             :             {
    6251           0 :                 return CE_Failure;
    6252             :             }
    6253        7178 :             for (int i = 0; i < nRasterXSize; i++)
    6254             :             {
    6255             :                 // The data point is centered inside the pixel.
    6256        7124 :                 padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
    6257             :             }
    6258             : 
    6259          54 :             size_t startLon[1] = {0};
    6260          54 :             size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
    6261             : 
    6262             :             // Write latitude and longitude values.
    6263             : 
    6264             :             // Make sure we are in data mode.
    6265          54 :             SetDefineMode(false);
    6266             : 
    6267             :             // Write values.
    6268          54 :             CPLDebug("GDAL_netCDF", "Writing lat values");
    6269             : 
    6270          54 :             int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
    6271             :                                             countLat, padLatVal);
    6272          54 :             NCDF_ERR(status);
    6273             : 
    6274          54 :             CPLDebug("GDAL_netCDF", "Writing lon values");
    6275          54 :             status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
    6276             :                                         padLonVal);
    6277          54 :             NCDF_ERR(status);
    6278             : 
    6279             :         }  // Not projected.
    6280             : 
    6281          94 :         if (pfnProgress)
    6282          53 :             pfnProgress(1.00, nullptr, pProgressData);
    6283             :     }
    6284             : 
    6285         188 :     return CE_None;
    6286             : }
    6287             : 
    6288             : // Write Projection variable to band variable.
    6289             : // Moved from AddProjectionVars() for cases when bands are added after
    6290             : // projection.
    6291         490 : bool netCDFDataset::AddGridMappingRef()
    6292             : {
    6293         490 :     bool bRet = true;
    6294         490 :     bool bOldDefineMode = bDefineMode;
    6295             : 
    6296         706 :     if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
    6297         216 :         ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
    6298         208 :          (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
    6299             :     {
    6300          84 :         bAddedGridMappingRef = true;
    6301             : 
    6302             :         // Make sure we are in define mode.
    6303          84 :         SetDefineMode(true);
    6304             : 
    6305         214 :         for (int i = 1; i <= nBands; i++)
    6306             :         {
    6307             :             const int nVarId =
    6308         130 :                 cpl::down_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
    6309             : 
    6310         130 :             if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
    6311             :             {
    6312             :                 int status =
    6313         252 :                     nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
    6314         126 :                                     strlen(pszCFProjection), pszCFProjection);
    6315         126 :                 NCDF_ERR(status);
    6316         126 :                 if (status != NC_NOERR)
    6317           0 :                     bRet = false;
    6318             :             }
    6319         130 :             if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
    6320             :             {
    6321             :                 int status =
    6322           8 :                     nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
    6323             :                                     strlen(pszCFCoordinates), pszCFCoordinates);
    6324           8 :                 NCDF_ERR(status);
    6325           8 :                 if (status != NC_NOERR)
    6326           0 :                     bRet = false;
    6327             :             }
    6328             :         }
    6329             : 
    6330             :         // Go back to previous define mode.
    6331          84 :         SetDefineMode(bOldDefineMode);
    6332             :     }
    6333         490 :     return bRet;
    6334             : }
    6335             : 
    6336             : /************************************************************************/
    6337             : /*                          GetGeoTransform()                           */
    6338             : /************************************************************************/
    6339             : 
    6340         136 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform &gt) const
    6341             : 
    6342             : {
    6343         136 :     gt = m_gt;
    6344         136 :     if (m_bHasGeoTransform)
    6345         104 :         return CE_None;
    6346             : 
    6347          32 :     return GDALPamDataset::GetGeoTransform(gt);
    6348             : }
    6349             : 
    6350             : /************************************************************************/
    6351             : /*                                rint()                                */
    6352             : /************************************************************************/
    6353             : 
    6354           0 : double netCDFDataset::rint(double dfX)
    6355             : {
    6356           0 :     return std::round(dfX);
    6357             : }
    6358             : 
    6359             : /************************************************************************/
    6360             : /*                        NCDFReadIsoMetadata()                         */
    6361             : /************************************************************************/
    6362             : 
    6363          16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
    6364             : {
    6365          16 :     int nbAttr = 0;
    6366          16 :     NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
    6367             : 
    6368          32 :     std::map<std::string, CPLJSONArray> oMapNameToArray;
    6369          40 :     for (int l = 0; l < nbAttr; l++)
    6370             :     {
    6371             :         char szAttrName[NC_MAX_NAME + 1];
    6372          24 :         szAttrName[0] = 0;
    6373          24 :         NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
    6374             : 
    6375          24 :         char *pszMetaValue = nullptr;
    6376          24 :         if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
    6377             :         {
    6378          24 :             nc_type nAttrType = NC_NAT;
    6379          24 :             size_t nAttrLen = 0;
    6380             : 
    6381          24 :             NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
    6382             :                                 &nAttrLen));
    6383             : 
    6384          24 :             std::string osAttrName(szAttrName);
    6385          24 :             const auto sharpPos = osAttrName.find('#');
    6386          24 :             if (sharpPos == std::string::npos)
    6387             :             {
    6388          16 :                 if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
    6389           4 :                     obj.Add(osAttrName, CPLAtof(pszMetaValue));
    6390             :                 else
    6391          12 :                     obj.Add(osAttrName, pszMetaValue);
    6392             :             }
    6393             :             else
    6394             :             {
    6395           8 :                 osAttrName.resize(sharpPos);
    6396           8 :                 auto iter = oMapNameToArray.find(osAttrName);
    6397           8 :                 if (iter == oMapNameToArray.end())
    6398             :                 {
    6399           8 :                     CPLJSONArray array;
    6400           4 :                     obj.Add(osAttrName, array);
    6401           4 :                     oMapNameToArray[osAttrName] = array;
    6402           4 :                     array.Add(pszMetaValue);
    6403             :                 }
    6404             :                 else
    6405             :                 {
    6406           4 :                     iter->second.Add(pszMetaValue);
    6407             :                 }
    6408             :             }
    6409          24 :             CPLFree(pszMetaValue);
    6410          24 :             pszMetaValue = nullptr;
    6411             :         }
    6412             :     }
    6413             : 
    6414          16 :     int nSubGroups = 0;
    6415          16 :     int *panSubGroupIds = nullptr;
    6416          16 :     NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
    6417          16 :     oMapNameToArray.clear();
    6418          28 :     for (int i = 0; i < nSubGroups; i++)
    6419             :     {
    6420          24 :         CPLJSONObject subObj;
    6421          12 :         NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
    6422             : 
    6423          24 :         std::string osGroupName;
    6424          12 :         osGroupName.resize(NC_MAX_NAME);
    6425          12 :         NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
    6426          12 :         osGroupName.resize(strlen(osGroupName.data()));
    6427          12 :         const auto sharpPos = osGroupName.find('#');
    6428          12 :         if (sharpPos == std::string::npos)
    6429             :         {
    6430           4 :             obj.Add(osGroupName, subObj);
    6431             :         }
    6432             :         else
    6433             :         {
    6434           8 :             osGroupName.resize(sharpPos);
    6435           8 :             auto iter = oMapNameToArray.find(osGroupName);
    6436           8 :             if (iter == oMapNameToArray.end())
    6437             :             {
    6438           8 :                 CPLJSONArray array;
    6439           4 :                 obj.Add(osGroupName, array);
    6440           4 :                 oMapNameToArray[osGroupName] = array;
    6441           4 :                 array.Add(subObj);
    6442             :             }
    6443             :             else
    6444             :             {
    6445           4 :                 iter->second.Add(subObj);
    6446             :             }
    6447             :         }
    6448             :     }
    6449          16 :     CPLFree(panSubGroupIds);
    6450          16 : }
    6451             : 
    6452           4 : std::string NCDFReadMetadataAsJson(int cdfid)
    6453             : {
    6454           8 :     CPLJSONDocument oDoc;
    6455           8 :     CPLJSONObject oRoot = oDoc.GetRoot();
    6456           4 :     NCDFReadMetadataAsJson(cdfid, oRoot);
    6457           8 :     return oDoc.SaveAsString();
    6458             : }
    6459             : 
    6460             : /************************************************************************/
    6461             : /*                           ReadAttributes()                           */
    6462             : /************************************************************************/
    6463        2084 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
    6464             : 
    6465             : {
    6466        4168 :     std::string osVarFullName;
    6467        2084 :     ERR_RET(NCDFGetVarFullName(cdfidIn, var, osVarFullName));
    6468             : 
    6469             :     // For metadata in Sentinel 5
    6470        2084 :     if (cpl::starts_with(osVarFullName, "/METADATA/"))
    6471             :     {
    6472           6 :         for (const char *key :
    6473             :              {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
    6474           8 :               "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
    6475             :         {
    6476          14 :             if (var == NC_GLOBAL &&
    6477          14 :                 osVarFullName == CPLOPrintf("/METADATA/%s/NC_GLOBAL", key))
    6478             :             {
    6479           1 :                 CPLStringList aosList;
    6480           2 :                 aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
    6481           1 :                                       .replaceAll("\\/", '/'));
    6482           1 :                 m_oMapDomainToJSon[key] = std::move(aosList);
    6483           1 :                 return CE_None;
    6484             :             }
    6485             :         }
    6486             :     }
    6487        2083 :     if (cpl::starts_with(osVarFullName, "/PRODUCT/SUPPORT_DATA/"))
    6488             :     {
    6489           0 :         CPLStringList aosList;
    6490             :         aosList.AddString(
    6491           0 :             CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
    6492           0 :         m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
    6493           0 :         return CE_None;
    6494             :     }
    6495             : 
    6496             :     size_t nMetaNameSize =
    6497        2083 :         sizeof(char) * (osVarFullName.size() + 1 + NC_MAX_NAME + 1);
    6498        2083 :     char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
    6499             : 
    6500        2083 :     int nbAttr = 0;
    6501        2083 :     NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
    6502             : 
    6503       10862 :     for (int l = 0; l < nbAttr; l++)
    6504             :     {
    6505             :         char szAttrName[NC_MAX_NAME + 1];
    6506        8779 :         szAttrName[0] = 0;
    6507        8779 :         NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
    6508        8779 :         snprintf(pszMetaName, nMetaNameSize, "%s#%s", osVarFullName.c_str(),
    6509             :                  szAttrName);
    6510             : 
    6511        8779 :         char *pszMetaTemp = nullptr;
    6512        8779 :         if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
    6513             :         {
    6514        8778 :             aosMetadata.SetNameValue(pszMetaName, pszMetaTemp);
    6515        8778 :             CPLFree(pszMetaTemp);
    6516        8778 :             pszMetaTemp = nullptr;
    6517             :         }
    6518             :         else
    6519             :         {
    6520           1 :             CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
    6521             :         }
    6522             :     }
    6523             : 
    6524        2083 :     CPLFree(pszMetaName);
    6525             : 
    6526        2083 :     if (var == NC_GLOBAL)
    6527             :     {
    6528             :         // Recurse on sub-groups.
    6529         586 :         int nSubGroups = 0;
    6530         586 :         int *panSubGroupIds = nullptr;
    6531         586 :         NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
    6532         620 :         for (int i = 0; i < nSubGroups; i++)
    6533             :         {
    6534          34 :             ReadAttributes(panSubGroupIds[i], var);
    6535             :         }
    6536         586 :         CPLFree(panSubGroupIds);
    6537             :     }
    6538             : 
    6539        2083 :     return CE_None;
    6540             : }
    6541             : 
    6542             : /************************************************************************/
    6543             : /*                netCDFDataset::CreateSubDatasetList()                 */
    6544             : /************************************************************************/
    6545          61 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
    6546             : {
    6547         122 :     std::string osVarStdName;
    6548          61 :     int *ponDimIds = nullptr;
    6549             : 
    6550          61 :     netCDFDataset *poDS = this;
    6551             : 
    6552             :     int nVarCount;
    6553          61 :     nc_inq_nvars(nGroupId, &nVarCount);
    6554             : 
    6555          61 :     const bool bListAllArrays = CPLTestBool(
    6556          61 :         CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    6557             : 
    6558         366 :     for (int nVar = 0; nVar < nVarCount; nVar++)
    6559             :     {
    6560             : 
    6561             :         int nDims;
    6562         305 :         nc_inq_varndims(nGroupId, nVar, &nDims);
    6563             : 
    6564         305 :         if ((bListAllArrays && nDims > 0) || nDims >= 2)
    6565             :         {
    6566         177 :             ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
    6567         177 :             nc_inq_vardimid(nGroupId, nVar, ponDimIds);
    6568             : 
    6569             :             // Create Sub dataset list.
    6570         177 :             CPLString osDim;
    6571         545 :             for (int i = 0; i < nDims; i++)
    6572             :             {
    6573             :                 size_t nDimLen;
    6574         368 :                 nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
    6575         368 :                 if (!osDim.empty())
    6576         191 :                     osDim += 'x';
    6577         368 :                 osDim += CPLSPrintf("%d", (int)nDimLen);
    6578             :             }
    6579         177 :             CPLFree(ponDimIds);
    6580             : 
    6581             :             nc_type nVarType;
    6582         177 :             nc_inq_vartype(nGroupId, nVar, &nVarType);
    6583         177 :             const char *pszType = "";
    6584         177 :             switch (nVarType)
    6585             :             {
    6586          42 :                 case NC_BYTE:
    6587          42 :                     pszType = "8-bit integer";
    6588          42 :                     break;
    6589           2 :                 case NC_CHAR:
    6590           2 :                     pszType = "8-bit character";
    6591           2 :                     break;
    6592           6 :                 case NC_SHORT:
    6593           6 :                     pszType = "16-bit integer";
    6594           6 :                     break;
    6595          10 :                 case NC_INT:
    6596          10 :                     pszType = "32-bit integer";
    6597          10 :                     break;
    6598          62 :                 case NC_FLOAT:
    6599          62 :                     pszType = "32-bit floating-point";
    6600          62 :                     break;
    6601          34 :                 case NC_DOUBLE:
    6602          34 :                     pszType = "64-bit floating-point";
    6603          34 :                     break;
    6604           4 :                 case NC_UBYTE:
    6605           4 :                     pszType = "8-bit unsigned integer";
    6606           4 :                     break;
    6607           1 :                 case NC_USHORT:
    6608           1 :                     pszType = "16-bit unsigned integer";
    6609           1 :                     break;
    6610           1 :                 case NC_UINT:
    6611           1 :                     pszType = "32-bit unsigned integer";
    6612           1 :                     break;
    6613           1 :                 case NC_INT64:
    6614           1 :                     pszType = "64-bit integer";
    6615           1 :                     break;
    6616           1 :                 case NC_UINT64:
    6617           1 :                     pszType = "64-bit unsigned integer";
    6618           1 :                     break;
    6619          13 :                 default:
    6620          13 :                     break;
    6621             :             }
    6622             : 
    6623         177 :             std::string osVarName;
    6624         177 :             if (NCDFGetVarFullName(nGroupId, nVar, osVarName) != CE_None)
    6625           0 :                 continue;
    6626             : 
    6627         177 :             nSubDatasets++;
    6628             : 
    6629         177 :             if (NCDFGetAttr(nGroupId, nVar, CF_STD_NAME, osVarStdName) !=
    6630             :                 CE_None)
    6631             :             {
    6632         113 :                 osVarStdName = osVarName;
    6633             :             }
    6634             : 
    6635             :             const std::string osSubDatasetName =
    6636         354 :                 CPLOPrintf("SUBDATASET_%d_NAME", nSubDatasets);
    6637             : 
    6638         354 :             if (osVarName.find(' ') != std::string::npos ||
    6639         177 :                 osVarName.find(':') != std::string::npos)
    6640             :             {
    6641             :                 poDS->aosSubDatasets.SetNameValue(
    6642             :                     osSubDatasetName.c_str(),
    6643             :                     CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
    6644           1 :                                osVarName.c_str()));
    6645             :             }
    6646             :             else
    6647             :             {
    6648             :                 poDS->aosSubDatasets.SetNameValue(
    6649             :                     osSubDatasetName.c_str(),
    6650             :                     CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
    6651         176 :                                osVarName.c_str()));
    6652             :             }
    6653             : 
    6654             :             const std::string osSubDatasetDesc =
    6655         354 :                 CPLOPrintf("SUBDATASET_%d_DESC", nSubDatasets);
    6656             : 
    6657             :             poDS->aosSubDatasets.SetNameValue(
    6658             :                 osSubDatasetDesc.c_str(),
    6659             :                 CPLSPrintf("[%s] %s (%s)", osDim.c_str(), osVarStdName.c_str(),
    6660         177 :                            pszType));
    6661             :         }
    6662             :     }
    6663             : 
    6664             :     // Recurse on sub groups.
    6665          61 :     int nSubGroups = 0;
    6666          61 :     int *panSubGroupIds = nullptr;
    6667          61 :     NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
    6668          69 :     for (int i = 0; i < nSubGroups; i++)
    6669             :     {
    6670           8 :         CreateSubDatasetList(panSubGroupIds[i]);
    6671             :     }
    6672          61 :     CPLFree(panSubGroupIds);
    6673          61 : }
    6674             : 
    6675             : /************************************************************************/
    6676             : /*                           TestCapability()                           */
    6677             : /************************************************************************/
    6678             : 
    6679         244 : bool netCDFDataset::TestCapability(const char *pszCap) const
    6680             : {
    6681         244 :     if (EQUAL(pszCap, ODsCCreateLayer))
    6682             :     {
    6683         221 :         return eAccess == GA_Update && nBands == 0 &&
    6684         215 :                (eMultipleLayerBehavior != SINGLE_LAYER ||
    6685         226 :                 this->GetLayerCount() == 0 || bSGSupport);
    6686             :     }
    6687         133 :     else if (EQUAL(pszCap, ODsCZGeometries))
    6688           2 :         return true;
    6689             : 
    6690         131 :     return false;
    6691             : }
    6692             : 
    6693             : /************************************************************************/
    6694             : /*                              GetLayer()                              */
    6695             : /************************************************************************/
    6696             : 
    6697         443 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
    6698             : {
    6699         443 :     if (nIdx < 0 || nIdx >= this->GetLayerCount())
    6700           2 :         return nullptr;
    6701         441 :     return papoLayers[nIdx].get();
    6702             : }
    6703             : 
    6704             : /************************************************************************/
    6705             : /*                            ICreateLayer()                            */
    6706             : /************************************************************************/
    6707             : 
    6708          59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
    6709             :                                       const OGRGeomFieldDefn *poGeomFieldDefn,
    6710             :                                       CSLConstList papszOptions)
    6711             : {
    6712          59 :     int nLayerCDFId = cdfid;
    6713          59 :     if (!TestCapability(ODsCCreateLayer))
    6714           0 :         return nullptr;
    6715             : 
    6716          59 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    6717             :     const auto poSpatialRef =
    6718          59 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6719             : 
    6720         118 :     CPLString osNetCDFLayerName(pszName);
    6721          59 :     const netCDFWriterConfigLayer *poLayerConfig = nullptr;
    6722          59 :     if (oWriterConfig.m_bIsValid)
    6723             :     {
    6724             :         std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
    6725           3 :             oLayerIter = oWriterConfig.m_oLayers.find(pszName);
    6726           3 :         if (oLayerIter != oWriterConfig.m_oLayers.end())
    6727             :         {
    6728           1 :             poLayerConfig = &(oLayerIter->second);
    6729           1 :             osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
    6730             :         }
    6731             :     }
    6732             : 
    6733          59 :     netCDFDataset *poLayerDataset = nullptr;
    6734          59 :     if (eMultipleLayerBehavior == SEPARATE_FILES)
    6735             :     {
    6736           3 :         if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
    6737             :             osNetCDFLayerName)
    6738             :         {
    6739           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6740             :                      "Illegal characters in '%s' to form a valid filename",
    6741             :                      osNetCDFLayerName.c_str());
    6742           1 :             return nullptr;
    6743             :         }
    6744           2 :         CPLStringList aosDatasetOptions;
    6745             :         aosDatasetOptions.SetNameValue(
    6746           2 :             "CONFIG_FILE", aosCreationOptions.FetchNameValue("CONFIG_FILE"));
    6747             :         aosDatasetOptions.SetNameValue(
    6748           2 :             "FORMAT", aosCreationOptions.FetchNameValue("FORMAT"));
    6749             :         aosDatasetOptions.SetNameValue(
    6750             :             "WRITE_GDAL_TAGS",
    6751           2 :             aosCreationOptions.FetchNameValue("WRITE_GDAL_TAGS"));
    6752             :         const CPLString osLayerFilename(
    6753           2 :             CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
    6754           2 :         CPLAcquireMutex(hNCMutex, 1000.0);
    6755           2 :         poLayerDataset =
    6756           2 :             CreateLL(osLayerFilename, 0, 0, 0, aosDatasetOptions.List());
    6757           2 :         CPLReleaseMutex(hNCMutex);
    6758           2 :         if (poLayerDataset == nullptr)
    6759           0 :             return nullptr;
    6760             : 
    6761           2 :         nLayerCDFId = poLayerDataset->cdfid;
    6762           2 :         NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
    6763           2 :                            bWriteGDALHistory, "", "Create",
    6764             :                            NCDF_CONVENTIONS_CF_V1_6);
    6765             :     }
    6766          56 :     else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
    6767             :     {
    6768           2 :         SetDefineMode(true);
    6769             : 
    6770           2 :         nLayerCDFId = -1;
    6771           2 :         int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
    6772           2 :         NCDF_ERR(status);
    6773           2 :         if (status != NC_NOERR)
    6774           0 :             return nullptr;
    6775             : 
    6776           2 :         NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
    6777           2 :                            bWriteGDALHistory, "", "Create",
    6778             :                            NCDF_CONVENTIONS_CF_V1_6);
    6779             :     }
    6780             : 
    6781             :     // Make a clone to workaround a bug in released MapServer versions
    6782             :     // that destroys the passed SRS instead of releasing it .
    6783          58 :     OGRSpatialReference *poSRS = nullptr;
    6784          58 :     if (poSpatialRef)
    6785             :     {
    6786          43 :         poSRS = poSpatialRef->Clone();
    6787          43 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6788             :     }
    6789             :     std::shared_ptr<netCDFLayer> poLayer(
    6790          58 :         new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
    6791         116 :                         osNetCDFLayerName, eGType, poSRS));
    6792          58 :     if (poSRS != nullptr)
    6793          43 :         poSRS->Release();
    6794             : 
    6795             :     // Fetch layer creation options coming from config file
    6796         116 :     CPLStringList aosNewOptions(CSLDuplicate(papszOptions));
    6797          58 :     if (oWriterConfig.m_bIsValid)
    6798             :     {
    6799           2 :         for (const auto &[osName, osValue] :
    6800           5 :              oWriterConfig.m_oLayerCreationOptions)
    6801             :         {
    6802           1 :             aosNewOptions.SetNameValue(osName, osValue);
    6803             :         }
    6804           3 :         if (poLayerConfig != nullptr)
    6805             :         {
    6806           4 :             for (const auto &[osName, osValue] :
    6807           5 :                  poLayerConfig->m_oLayerCreationOptions)
    6808             :             {
    6809           2 :                 aosNewOptions.SetNameValue(osName, osValue);
    6810             :             }
    6811             :         }
    6812             :     }
    6813             : 
    6814          58 :     const bool bRet = poLayer->Create(aosNewOptions.List(), poLayerConfig);
    6815             : 
    6816          58 :     if (!bRet)
    6817             :     {
    6818           0 :         return nullptr;
    6819             :     }
    6820             : 
    6821          58 :     if (poLayerDataset != nullptr)
    6822           2 :         apoVectorDatasets.push_back(poLayerDataset);
    6823             : 
    6824          58 :     papoLayers.push_back(poLayer);
    6825          58 :     return poLayer.get();
    6826             : }
    6827             : 
    6828             : /************************************************************************/
    6829             : /*                          CloneAttributes()                           */
    6830             : /************************************************************************/
    6831             : 
    6832         137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
    6833             :                                     int nDstVarId)
    6834             : {
    6835         137 :     int nAttCount = -1;
    6836         137 :     int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
    6837         137 :     NCDF_ERR(status);
    6838             : 
    6839         693 :     for (int i = 0; i < nAttCount; i++)
    6840             :     {
    6841             :         char szName[NC_MAX_NAME + 1];
    6842         556 :         szName[0] = 0;
    6843         556 :         status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
    6844         556 :         NCDF_ERR(status);
    6845             : 
    6846             :         status =
    6847         556 :             nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
    6848         556 :         NCDF_ERR(status);
    6849         556 :         if (status != NC_NOERR)
    6850           0 :             return false;
    6851             :     }
    6852             : 
    6853         137 :     return true;
    6854             : }
    6855             : 
    6856             : /************************************************************************/
    6857             : /*                        CloneVariableContent()                        */
    6858             : /************************************************************************/
    6859             : 
    6860         121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
    6861             :                                          int nSrcVarId, int nDstVarId)
    6862             : {
    6863         121 :     int nVarDimCount = -1;
    6864         121 :     int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
    6865         121 :     NCDF_ERR(status);
    6866         121 :     int anDimIds[] = {-1, 1};
    6867         121 :     status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
    6868         121 :     NCDF_ERR(status);
    6869         121 :     nc_type nc_datatype = NC_NAT;
    6870         121 :     status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
    6871         121 :     NCDF_ERR(status);
    6872         121 :     size_t nTypeSize = 0;
    6873         121 :     switch (nc_datatype)
    6874             :     {
    6875          35 :         case NC_BYTE:
    6876             :         case NC_CHAR:
    6877          35 :             nTypeSize = 1;
    6878          35 :             break;
    6879           4 :         case NC_SHORT:
    6880           4 :             nTypeSize = 2;
    6881           4 :             break;
    6882          24 :         case NC_INT:
    6883          24 :             nTypeSize = 4;
    6884          24 :             break;
    6885           4 :         case NC_FLOAT:
    6886           4 :             nTypeSize = 4;
    6887           4 :             break;
    6888          43 :         case NC_DOUBLE:
    6889          43 :             nTypeSize = 8;
    6890          43 :             break;
    6891           2 :         case NC_UBYTE:
    6892           2 :             nTypeSize = 1;
    6893           2 :             break;
    6894           2 :         case NC_USHORT:
    6895           2 :             nTypeSize = 2;
    6896           2 :             break;
    6897           2 :         case NC_UINT:
    6898           2 :             nTypeSize = 4;
    6899           2 :             break;
    6900           4 :         case NC_INT64:
    6901             :         case NC_UINT64:
    6902           4 :             nTypeSize = 8;
    6903           4 :             break;
    6904           1 :         case NC_STRING:
    6905           1 :             nTypeSize = sizeof(char *);
    6906           1 :             break;
    6907           0 :         default:
    6908             :         {
    6909           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
    6910             :                      nc_datatype);
    6911           0 :             return false;
    6912             :         }
    6913             :     }
    6914             : 
    6915         121 :     size_t nElems = 1;
    6916             :     size_t anStart[NC_MAX_DIMS];
    6917             :     size_t anCount[NC_MAX_DIMS];
    6918         121 :     size_t nRecords = 1;
    6919         261 :     for (int i = 0; i < nVarDimCount; i++)
    6920             :     {
    6921         140 :         anStart[i] = 0;
    6922         140 :         if (i == 0)
    6923             :         {
    6924         116 :             anCount[i] = 1;
    6925         116 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
    6926         116 :             NCDF_ERR(status);
    6927             :         }
    6928             :         else
    6929             :         {
    6930          24 :             anCount[i] = 0;
    6931          24 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
    6932          24 :             NCDF_ERR(status);
    6933          24 :             nElems *= anCount[i];
    6934             :         }
    6935             :     }
    6936             : 
    6937             :     /* Workaround in some cases a netCDF bug:
    6938             :      * https://github.com/Unidata/netcdf-c/pull/1442 */
    6939         121 :     if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
    6940             :     {
    6941         119 :         nElems *= nRecords;
    6942         119 :         anCount[0] = nRecords;
    6943         119 :         nRecords = 1;
    6944             :     }
    6945             : 
    6946         121 :     void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
    6947         121 :     if (pBuffer == nullptr)
    6948           0 :         return false;
    6949             : 
    6950         240 :     for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
    6951             :     {
    6952         119 :         anStart[0] = iRecord;
    6953             : 
    6954         119 :         switch (nc_datatype)
    6955             :         {
    6956           5 :             case NC_BYTE:
    6957             :                 status =
    6958           5 :                     nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
    6959             :                                       static_cast<signed char *>(pBuffer));
    6960           5 :                 if (!status)
    6961           5 :                     status = nc_put_vara_schar(
    6962             :                         new_cdfid, nDstVarId, anStart, anCount,
    6963             :                         static_cast<signed char *>(pBuffer));
    6964           5 :                 break;
    6965          28 :             case NC_CHAR:
    6966             :                 status =
    6967          28 :                     nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
    6968             :                                      static_cast<char *>(pBuffer));
    6969          28 :                 if (!status)
    6970             :                     status =
    6971          28 :                         nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
    6972             :                                          static_cast<char *>(pBuffer));
    6973          28 :                 break;
    6974           4 :             case NC_SHORT:
    6975             :                 status =
    6976           4 :                     nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
    6977             :                                       static_cast<short *>(pBuffer));
    6978           4 :                 if (!status)
    6979           4 :                     status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
    6980             :                                                anCount,
    6981             :                                                static_cast<short *>(pBuffer));
    6982           4 :                 break;
    6983          24 :             case NC_INT:
    6984          24 :                 status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
    6985             :                                          static_cast<int *>(pBuffer));
    6986          24 :                 if (!status)
    6987             :                     status =
    6988          24 :                         nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
    6989             :                                         static_cast<int *>(pBuffer));
    6990          24 :                 break;
    6991           4 :             case NC_FLOAT:
    6992             :                 status =
    6993           4 :                     nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
    6994             :                                       static_cast<float *>(pBuffer));
    6995           4 :                 if (!status)
    6996           4 :                     status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
    6997             :                                                anCount,
    6998             :                                                static_cast<float *>(pBuffer));
    6999           4 :                 break;
    7000          43 :             case NC_DOUBLE:
    7001             :                 status =
    7002          43 :                     nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
    7003             :                                        static_cast<double *>(pBuffer));
    7004          43 :                 if (!status)
    7005          43 :                     status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
    7006             :                                                 anCount,
    7007             :                                                 static_cast<double *>(pBuffer));
    7008          43 :                 break;
    7009           1 :             case NC_STRING:
    7010             :                 status =
    7011           1 :                     nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
    7012             :                                        static_cast<char **>(pBuffer));
    7013           1 :                 if (!status)
    7014             :                 {
    7015           1 :                     status = nc_put_vara_string(
    7016             :                         new_cdfid, nDstVarId, anStart, anCount,
    7017             :                         static_cast<const char **>(pBuffer));
    7018           1 :                     nc_free_string(nElems, static_cast<char **>(pBuffer));
    7019             :                 }
    7020           1 :                 break;
    7021             : 
    7022           2 :             case NC_UBYTE:
    7023             :                 status =
    7024           2 :                     nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
    7025             :                                       static_cast<unsigned char *>(pBuffer));
    7026           2 :                 if (!status)
    7027           2 :                     status = nc_put_vara_uchar(
    7028             :                         new_cdfid, nDstVarId, anStart, anCount,
    7029             :                         static_cast<unsigned char *>(pBuffer));
    7030           2 :                 break;
    7031           2 :             case NC_USHORT:
    7032             :                 status =
    7033           2 :                     nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
    7034             :                                        static_cast<unsigned short *>(pBuffer));
    7035           2 :                 if (!status)
    7036           2 :                     status = nc_put_vara_ushort(
    7037             :                         new_cdfid, nDstVarId, anStart, anCount,
    7038             :                         static_cast<unsigned short *>(pBuffer));
    7039           2 :                 break;
    7040           2 :             case NC_UINT:
    7041             :                 status =
    7042           2 :                     nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
    7043             :                                      static_cast<unsigned int *>(pBuffer));
    7044           2 :                 if (!status)
    7045             :                     status =
    7046           2 :                         nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
    7047             :                                          static_cast<unsigned int *>(pBuffer));
    7048           2 :                 break;
    7049           2 :             case NC_INT64:
    7050             :                 status =
    7051           2 :                     nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
    7052             :                                          static_cast<long long *>(pBuffer));
    7053           2 :                 if (!status)
    7054           2 :                     status = nc_put_vara_longlong(
    7055             :                         new_cdfid, nDstVarId, anStart, anCount,
    7056             :                         static_cast<long long *>(pBuffer));
    7057           2 :                 break;
    7058           2 :             case NC_UINT64:
    7059           2 :                 status = nc_get_vara_ulonglong(
    7060             :                     old_cdfid, nSrcVarId, anStart, anCount,
    7061             :                     static_cast<unsigned long long *>(pBuffer));
    7062           2 :                 if (!status)
    7063           2 :                     status = nc_put_vara_ulonglong(
    7064             :                         new_cdfid, nDstVarId, anStart, anCount,
    7065             :                         static_cast<unsigned long long *>(pBuffer));
    7066           2 :                 break;
    7067           0 :             default:
    7068           0 :                 status = NC_EBADTYPE;
    7069             :         }
    7070             : 
    7071         119 :         NCDF_ERR(status);
    7072         119 :         if (status != NC_NOERR)
    7073             :         {
    7074           0 :             VSIFree(pBuffer);
    7075           0 :             return false;
    7076             :         }
    7077             :     }
    7078             : 
    7079         121 :     VSIFree(pBuffer);
    7080         121 :     return true;
    7081             : }
    7082             : 
    7083             : /************************************************************************/
    7084             : /*                         NCDFIsUnlimitedDim()                         */
    7085             : /************************************************************************/
    7086             : 
    7087          80 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
    7088             : {
    7089          80 :     if (bIsNC4)
    7090             :     {
    7091          16 :         int nUnlimitedDims = 0;
    7092          16 :         nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
    7093          16 :         bool bFound = false;
    7094          16 :         if (nUnlimitedDims)
    7095             :         {
    7096             :             int *panUnlimitedDimIds =
    7097          16 :                 static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
    7098          16 :             nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
    7099          30 :             for (int i = 0; i < nUnlimitedDims; i++)
    7100             :             {
    7101          22 :                 if (panUnlimitedDimIds[i] == nDimId)
    7102             :                 {
    7103           8 :                     bFound = true;
    7104           8 :                     break;
    7105             :                 }
    7106             :             }
    7107          16 :             CPLFree(panUnlimitedDimIds);
    7108             :         }
    7109          16 :         return bFound;
    7110             :     }
    7111             :     else
    7112             :     {
    7113          64 :         int nUnlimitedDimId = -1;
    7114          64 :         nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
    7115          64 :         return nDimId == nUnlimitedDimId;
    7116             :     }
    7117             : }
    7118             : 
    7119             : /************************************************************************/
    7120             : /*                              CloneGrp()                              */
    7121             : /************************************************************************/
    7122             : 
    7123          16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
    7124             :                              int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7125             : {
    7126             :     // Clone dimensions
    7127          16 :     int nDimCount = -1;
    7128          16 :     int status = nc_inq_ndims(nOldGrpId, &nDimCount);
    7129          16 :     NCDF_ERR(status);
    7130          16 :     if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
    7131           0 :         return false;
    7132             :     int anDimIds[NC_MAX_DIMS];
    7133          16 :     int nUnlimiDimID = -1;
    7134          16 :     status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
    7135          16 :     NCDF_ERR(status);
    7136          16 :     if (bIsNC4)
    7137             :     {
    7138             :         // In NC4, the dimension ids of a group are not necessarily in
    7139             :         // [0,nDimCount-1] range
    7140           8 :         int nDimCount2 = -1;
    7141           8 :         status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
    7142           8 :         NCDF_ERR(status);
    7143           8 :         CPLAssert(nDimCount == nDimCount2);
    7144             :     }
    7145             :     else
    7146             :     {
    7147          36 :         for (int i = 0; i < nDimCount; i++)
    7148          28 :             anDimIds[i] = i;
    7149             :     }
    7150          60 :     for (int i = 0; i < nDimCount; i++)
    7151             :     {
    7152             :         char szDimName[NC_MAX_NAME + 1];
    7153          44 :         szDimName[0] = 0;
    7154          44 :         size_t nLen = 0;
    7155          44 :         const int nDimId = anDimIds[i];
    7156          44 :         status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
    7157          44 :         NCDF_ERR(status);
    7158          44 :         if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
    7159          16 :             nLen = NC_UNLIMITED;
    7160          28 :         else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
    7161          13 :             nLen = nNewSize;
    7162          44 :         int nNewDimId = -1;
    7163          44 :         status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
    7164          44 :         NCDF_ERR(status);
    7165          44 :         CPLAssert(nDimId == nNewDimId);
    7166          44 :         if (status != NC_NOERR)
    7167             :         {
    7168           0 :             return false;
    7169             :         }
    7170             :     }
    7171             : 
    7172             :     // Clone main attributes
    7173          16 :     if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
    7174             :     {
    7175           0 :         return false;
    7176             :     }
    7177             : 
    7178             :     // Clone variable definitions
    7179          16 :     int nVarCount = -1;
    7180          16 :     status = nc_inq_nvars(nOldGrpId, &nVarCount);
    7181          16 :     NCDF_ERR(status);
    7182             : 
    7183         137 :     for (int i = 0; i < nVarCount; i++)
    7184             :     {
    7185             :         char szVarName[NC_MAX_NAME + 1];
    7186         121 :         szVarName[0] = 0;
    7187         121 :         status = nc_inq_varname(nOldGrpId, i, szVarName);
    7188         121 :         NCDF_ERR(status);
    7189         121 :         nc_type nc_datatype = NC_NAT;
    7190         121 :         status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
    7191         121 :         NCDF_ERR(status);
    7192         121 :         int nVarDimCount = -1;
    7193         121 :         status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
    7194         121 :         NCDF_ERR(status);
    7195         121 :         status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
    7196         121 :         NCDF_ERR(status);
    7197         121 :         int nNewVarId = -1;
    7198         121 :         status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
    7199             :                             anDimIds, &nNewVarId);
    7200         121 :         NCDF_ERR(status);
    7201         121 :         CPLAssert(i == nNewVarId);
    7202         121 :         if (status != NC_NOERR)
    7203             :         {
    7204           0 :             return false;
    7205             :         }
    7206             : 
    7207         121 :         if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
    7208             :         {
    7209           0 :             return false;
    7210             :         }
    7211             :     }
    7212             : 
    7213          16 :     status = nc_enddef(nNewGrpId);
    7214          16 :     NCDF_ERR(status);
    7215          16 :     if (status != NC_NOERR)
    7216             :     {
    7217           0 :         return false;
    7218             :     }
    7219             : 
    7220             :     // Clone variable content
    7221         137 :     for (int i = 0; i < nVarCount; i++)
    7222             :     {
    7223         121 :         if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
    7224             :         {
    7225           0 :             return false;
    7226             :         }
    7227             :     }
    7228             : 
    7229          16 :     return true;
    7230             : }
    7231             : 
    7232             : /************************************************************************/
    7233             : /*                              GrowDim()                               */
    7234             : /************************************************************************/
    7235             : 
    7236          13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7237             : {
    7238             :     int nCreationMode;
    7239             :     // Set nCreationMode based on eFormat.
    7240          13 :     switch (eFormat)
    7241             :     {
    7242             : #ifdef NETCDF_HAS_NC2
    7243           0 :         case NCDF_FORMAT_NC2:
    7244           0 :             nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
    7245           0 :             break;
    7246             : #endif
    7247           5 :         case NCDF_FORMAT_NC4:
    7248           5 :             nCreationMode = NC_CLOBBER | NC_NETCDF4;
    7249           5 :             break;
    7250           0 :         case NCDF_FORMAT_NC4C:
    7251           0 :             nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
    7252           0 :             break;
    7253           8 :         case NCDF_FORMAT_NC:
    7254             :         default:
    7255           8 :             nCreationMode = NC_CLOBBER;
    7256           8 :             break;
    7257             :     }
    7258             : 
    7259          13 :     int new_cdfid = -1;
    7260          26 :     CPLString osTmpFilename(osFilename + ".tmp");
    7261          26 :     CPLString osFilenameForNCCreate(osTmpFilename);
    7262             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7263             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7264             :     {
    7265             :         char *pszTemp =
    7266             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    7267             :         osFilenameForNCCreate = pszTemp;
    7268             :         CPLFree(pszTemp);
    7269             :     }
    7270             : #endif
    7271          13 :     int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
    7272          13 :     NCDF_ERR(status);
    7273          13 :     if (status != NC_NOERR)
    7274           0 :         return false;
    7275             : 
    7276          13 :     if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
    7277             :                   nDimIdToGrow, nNewSize))
    7278             :     {
    7279           0 :         GDAL_nc_close(new_cdfid);
    7280           0 :         return false;
    7281             :     }
    7282             : 
    7283          13 :     int nGroupCount = 0;
    7284          26 :     std::vector<CPLString> oListGrpName;
    7285          31 :     if (eFormat == NCDF_FORMAT_NC4 &&
    7286          18 :         nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
    7287           5 :         nGroupCount > 0)
    7288             :     {
    7289             :         int *panGroupIds =
    7290           2 :             static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
    7291           2 :         status = nc_inq_grps(cdfid, nullptr, panGroupIds);
    7292           2 :         NCDF_ERR(status);
    7293           5 :         for (int i = 0; i < nGroupCount; i++)
    7294             :         {
    7295             :             char szGroupName[NC_MAX_NAME + 1];
    7296           3 :             szGroupName[0] = 0;
    7297           3 :             NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
    7298           3 :             int nNewGrpId = -1;
    7299           3 :             status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
    7300           3 :             NCDF_ERR(status);
    7301           3 :             if (status != NC_NOERR)
    7302             :             {
    7303           0 :                 CPLFree(panGroupIds);
    7304           0 :                 GDAL_nc_close(new_cdfid);
    7305           0 :                 return false;
    7306             :             }
    7307           3 :             if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
    7308             :                           nDimIdToGrow, nNewSize))
    7309             :             {
    7310           0 :                 CPLFree(panGroupIds);
    7311           0 :                 GDAL_nc_close(new_cdfid);
    7312           0 :                 return false;
    7313             :             }
    7314             :         }
    7315           2 :         CPLFree(panGroupIds);
    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             :                 char szGroupName[NC_MAX_NAME + 1];
    7323           3 :                 szGroupName[0] = 0;
    7324           3 :                 status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
    7325           3 :                 NCDF_ERR(status);
    7326           3 :                 oListGrpName.push_back(szGroupName);
    7327             :             }
    7328             :         }
    7329             :     }
    7330             : 
    7331          13 :     GDAL_nc_close(cdfid);
    7332          13 :     cdfid = -1;
    7333          13 :     GDAL_nc_close(new_cdfid);
    7334             : 
    7335          26 :     CPLString osOriFilename(osFilename + ".ori");
    7336          26 :     if (VSIRename(osFilename, osOriFilename) != 0 ||
    7337          13 :         VSIRename(osTmpFilename, osFilename) != 0)
    7338             :     {
    7339           0 :         CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
    7340           0 :         return false;
    7341             :     }
    7342          13 :     VSIUnlink(osOriFilename);
    7343             : 
    7344          26 :     CPLString osFilenameForNCOpen(osFilename);
    7345             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7346             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7347             :     {
    7348             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    7349             :         osFilenameForNCOpen = pszTemp;
    7350             :         CPLFree(pszTemp);
    7351             :     }
    7352             : #endif
    7353          13 :     status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
    7354          13 :     NCDF_ERR(status);
    7355          13 :     if (status != NC_NOERR)
    7356           0 :         return false;
    7357          13 :     bDefineMode = false;
    7358             : 
    7359          13 :     if (!oListGrpName.empty())
    7360             :     {
    7361           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7362             :         {
    7363           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7364           3 :             if (poLayer)
    7365             :             {
    7366           3 :                 int nNewLayerCDFID = -1;
    7367           3 :                 status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
    7368             :                                      &nNewLayerCDFID);
    7369           3 :                 NCDF_ERR(status);
    7370           3 :                 poLayer->SetCDFID(nNewLayerCDFID);
    7371             :             }
    7372             :         }
    7373             :     }
    7374             :     else
    7375             :     {
    7376          22 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7377             :         {
    7378          11 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7379          11 :             if (poLayer)
    7380          11 :                 poLayer->SetCDFID(cdfid);
    7381             :         }
    7382             :     }
    7383             : 
    7384          13 :     return true;
    7385             : }
    7386             : 
    7387             : #ifdef ENABLE_NCDUMP
    7388             : 
    7389             : /************************************************************************/
    7390             : /*                    netCDFDatasetCreateTempFile()                     */
    7391             : /************************************************************************/
    7392             : 
    7393             : /* Create a netCDF file from a text dump (format of ncdump) */
    7394             : /* Mostly to easy fuzzing of the driver, while still generating valid */
    7395             : /* netCDF files. */
    7396             : /* Note: not all data types are supported ! */
    7397           4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
    7398             :                                  const char *pszTmpFilename, VSILFILE *fpSrc)
    7399             : {
    7400           4 :     CPL_IGNORE_RET_VAL(eFormat);
    7401           4 :     int nCreateMode = NC_CLOBBER;
    7402           4 :     if (eFormat == NCDF_FORMAT_NC4)
    7403           1 :         nCreateMode |= NC_NETCDF4;
    7404           3 :     else if (eFormat == NCDF_FORMAT_NC4C)
    7405           0 :         nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
    7406           4 :     int nCdfId = -1;
    7407           4 :     int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
    7408           4 :     if (status != NC_NOERR)
    7409             :     {
    7410           0 :         return false;
    7411             :     }
    7412           4 :     VSIFSeekL(fpSrc, 0, SEEK_SET);
    7413             :     const char *pszLine;
    7414           4 :     constexpr int SECTION_NONE = 0;
    7415           4 :     constexpr int SECTION_DIMENSIONS = 1;
    7416           4 :     constexpr int SECTION_VARIABLES = 2;
    7417           4 :     constexpr int SECTION_DATA = 3;
    7418           4 :     int nActiveSection = SECTION_NONE;
    7419           8 :     std::map<CPLString, int> oMapDimToId;
    7420           8 :     std::map<int, int> oMapDimIdToDimLen;
    7421           8 :     std::map<CPLString, int> oMapVarToId;
    7422           8 :     std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
    7423           8 :     std::map<int, int> oMapVarIdToType;
    7424           4 :     std::set<CPLString> oSetAttrDefined;
    7425           4 :     oMapVarToId[""] = -1;
    7426           4 :     size_t nTotalVarSize = 0;
    7427         208 :     while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
    7428             :     {
    7429         204 :         if (STARTS_WITH(pszLine, "dimensions:") &&
    7430             :             nActiveSection == SECTION_NONE)
    7431             :         {
    7432           4 :             nActiveSection = SECTION_DIMENSIONS;
    7433             :         }
    7434         200 :         else if (STARTS_WITH(pszLine, "variables:") &&
    7435             :                  nActiveSection == SECTION_DIMENSIONS)
    7436             :         {
    7437           4 :             nActiveSection = SECTION_VARIABLES;
    7438             :         }
    7439         196 :         else if (STARTS_WITH(pszLine, "data:") &&
    7440             :                  nActiveSection == SECTION_VARIABLES)
    7441             :         {
    7442           4 :             nActiveSection = SECTION_DATA;
    7443           4 :             status = nc_enddef(nCdfId);
    7444           4 :             if (status != NC_NOERR)
    7445             :             {
    7446           0 :                 CPLDebug("netCDF", "nc_enddef() failed: %s",
    7447             :                          nc_strerror(status));
    7448             :             }
    7449             :         }
    7450         192 :         else if (nActiveSection == SECTION_DIMENSIONS)
    7451             :         {
    7452             :             const CPLStringList aosTokens(
    7453           9 :                 CSLTokenizeString2(pszLine, " \t=;", 0));
    7454           9 :             if (aosTokens.size() == 2)
    7455             :             {
    7456           9 :                 const char *pszDimName = aosTokens[0];
    7457           9 :                 bool bValidName = true;
    7458           9 :                 if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
    7459             :                 {
    7460             :                     // This is an internal netcdf prefix. Using it may
    7461             :                     // cause memory leaks.
    7462           0 :                     bValidName = false;
    7463             :                 }
    7464           9 :                 if (!bValidName)
    7465             :                 {
    7466           0 :                     CPLDebug("netCDF",
    7467             :                              "nc_def_dim(%s) failed: invalid name found",
    7468             :                              pszDimName);
    7469           0 :                     continue;
    7470             :                 }
    7471             : 
    7472             :                 const bool bIsASCII =
    7473           9 :                     CPLIsASCII(pszDimName, static_cast<size_t>(-1));
    7474           9 :                 if (!bIsASCII)
    7475             :                 {
    7476             :                     // Workaround https://github.com/Unidata/netcdf-c/pull/450
    7477           0 :                     CPLDebug("netCDF",
    7478             :                              "nc_def_dim(%s) failed: rejected because "
    7479             :                              "of non-ASCII characters",
    7480             :                              pszDimName);
    7481           0 :                     continue;
    7482             :                 }
    7483           9 :                 int nDimSize = EQUAL(aosTokens[1], "UNLIMITED")
    7484             :                                    ? NC_UNLIMITED
    7485           9 :                                    : atoi(aosTokens[1]);
    7486           9 :                 if (nDimSize >= 1000)
    7487           1 :                     nDimSize = 1000;  // to avoid very long processing
    7488           9 :                 if (nDimSize >= 0)
    7489             :                 {
    7490           9 :                     int nDimId = -1;
    7491           9 :                     status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
    7492           9 :                     if (status != NC_NOERR)
    7493             :                     {
    7494           0 :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
    7495             :                                  pszDimName, nDimSize, nc_strerror(status));
    7496             :                     }
    7497             :                     else
    7498             :                     {
    7499             : #ifdef DEBUG_VERBOSE
    7500             :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
    7501             :                                  pszDimName, nDimSize, pszLine);
    7502             : #endif
    7503           9 :                         oMapDimToId[pszDimName] = nDimId;
    7504           9 :                         oMapDimIdToDimLen[nDimId] = nDimSize;
    7505             :                     }
    7506             :                 }
    7507             :             }
    7508             :         }
    7509         183 :         else if (nActiveSection == SECTION_VARIABLES)
    7510             :         {
    7511         390 :             while (*pszLine == ' ' || *pszLine == '\t')
    7512         249 :                 pszLine++;
    7513         141 :             const char *pszColumn = strchr(pszLine, ':');
    7514         141 :             const char *pszEqual = strchr(pszLine, '=');
    7515         141 :             if (pszColumn == nullptr)
    7516             :             {
    7517             :                 const CPLStringList aosTokens(
    7518          21 :                     CSLTokenizeString2(pszLine, " \t=(),;", 0));
    7519          21 :                 if (aosTokens.size() >= 2)
    7520             :                 {
    7521          17 :                     const char *pszVarName = aosTokens[1];
    7522          17 :                     bool bValidName = true;
    7523          17 :                     if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
    7524             :                     {
    7525             :                         // This is an internal netcdf prefix. Using it may
    7526             :                         // cause memory leaks.
    7527           0 :                         bValidName = false;
    7528             :                     }
    7529         138 :                     for (int i = 0; pszVarName[i]; i++)
    7530             :                     {
    7531         121 :                         if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
    7532          28 :                               (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
    7533           9 :                               (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
    7534           6 :                               pszVarName[i] == '_'))
    7535             :                         {
    7536           0 :                             bValidName = false;
    7537             :                         }
    7538             :                     }
    7539          17 :                     if (!bValidName)
    7540             :                     {
    7541           0 :                         CPLDebug(
    7542             :                             "netCDF",
    7543             :                             "nc_def_var(%s) failed: illegal character found",
    7544             :                             pszVarName);
    7545           0 :                         continue;
    7546             :                     }
    7547          17 :                     if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
    7548             :                     {
    7549           0 :                         CPLDebug("netCDF",
    7550             :                                  "nc_def_var(%s) failed: already defined",
    7551             :                                  pszVarName);
    7552           0 :                         continue;
    7553             :                     }
    7554          17 :                     const char *pszVarType = aosTokens[0];
    7555          17 :                     int nc_datatype = NC_BYTE;
    7556          17 :                     size_t nDataTypeSize = 1;
    7557          17 :                     if (EQUAL(pszVarType, "char"))
    7558             :                     {
    7559           6 :                         nc_datatype = NC_CHAR;
    7560           6 :                         nDataTypeSize = 1;
    7561             :                     }
    7562          11 :                     else if (EQUAL(pszVarType, "byte"))
    7563             :                     {
    7564           3 :                         nc_datatype = NC_BYTE;
    7565           3 :                         nDataTypeSize = 1;
    7566             :                     }
    7567           8 :                     else if (EQUAL(pszVarType, "short"))
    7568             :                     {
    7569           0 :                         nc_datatype = NC_SHORT;
    7570           0 :                         nDataTypeSize = 2;
    7571             :                     }
    7572           8 :                     else if (EQUAL(pszVarType, "int"))
    7573             :                     {
    7574           0 :                         nc_datatype = NC_INT;
    7575           0 :                         nDataTypeSize = 4;
    7576             :                     }
    7577           8 :                     else if (EQUAL(pszVarType, "float"))
    7578             :                     {
    7579           0 :                         nc_datatype = NC_FLOAT;
    7580           0 :                         nDataTypeSize = 4;
    7581             :                     }
    7582           8 :                     else if (EQUAL(pszVarType, "double"))
    7583             :                     {
    7584           8 :                         nc_datatype = NC_DOUBLE;
    7585           8 :                         nDataTypeSize = 8;
    7586             :                     }
    7587           0 :                     else if (EQUAL(pszVarType, "ubyte"))
    7588             :                     {
    7589           0 :                         nc_datatype = NC_UBYTE;
    7590           0 :                         nDataTypeSize = 1;
    7591             :                     }
    7592           0 :                     else if (EQUAL(pszVarType, "ushort"))
    7593             :                     {
    7594           0 :                         nc_datatype = NC_USHORT;
    7595           0 :                         nDataTypeSize = 2;
    7596             :                     }
    7597           0 :                     else if (EQUAL(pszVarType, "uint"))
    7598             :                     {
    7599           0 :                         nc_datatype = NC_UINT;
    7600           0 :                         nDataTypeSize = 4;
    7601             :                     }
    7602           0 :                     else if (EQUAL(pszVarType, "int64"))
    7603             :                     {
    7604           0 :                         nc_datatype = NC_INT64;
    7605           0 :                         nDataTypeSize = 8;
    7606             :                     }
    7607           0 :                     else if (EQUAL(pszVarType, "uint64"))
    7608             :                     {
    7609           0 :                         nc_datatype = NC_UINT64;
    7610           0 :                         nDataTypeSize = 8;
    7611             :                     }
    7612             : 
    7613          17 :                     int nDims = aosTokens.size() - 2;
    7614          17 :                     if (nDims >= 32)
    7615             :                     {
    7616             :                         // The number of dimensions in a netCDFv4 file is
    7617             :                         // limited by #define H5S_MAX_RANK    32
    7618             :                         // but libnetcdf doesn't check that...
    7619           0 :                         CPLDebug("netCDF",
    7620             :                                  "nc_def_var(%s) failed: too many dimensions",
    7621             :                                  pszVarName);
    7622           0 :                         continue;
    7623             :                     }
    7624          17 :                     std::vector<int> aoDimIds;
    7625          17 :                     bool bFailed = false;
    7626          17 :                     size_t nSize = 1;
    7627          35 :                     for (int i = 0; i < nDims; i++)
    7628             :                     {
    7629          18 :                         const char *pszDimName = aosTokens[2 + i];
    7630          18 :                         if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
    7631             :                         {
    7632           0 :                             bFailed = true;
    7633           0 :                             break;
    7634             :                         }
    7635          18 :                         const int nDimId = oMapDimToId[pszDimName];
    7636          18 :                         aoDimIds.push_back(nDimId);
    7637             : 
    7638          18 :                         const size_t nDimSize = oMapDimIdToDimLen[nDimId];
    7639          18 :                         if (nDimSize != 0)
    7640             :                         {
    7641          18 :                             if (nSize >
    7642          18 :                                 std::numeric_limits<size_t>::max() / nDimSize)
    7643             :                             {
    7644           0 :                                 bFailed = true;
    7645           0 :                                 break;
    7646             :                             }
    7647             :                             else
    7648             :                             {
    7649          18 :                                 nSize *= nDimSize;
    7650             :                             }
    7651             :                         }
    7652             :                     }
    7653          17 :                     if (bFailed)
    7654             :                     {
    7655           0 :                         CPLDebug("netCDF",
    7656             :                                  "nc_def_var(%s) failed: unknown dimension(s)",
    7657             :                                  pszVarName);
    7658           0 :                         continue;
    7659             :                     }
    7660          17 :                     if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
    7661             :                     {
    7662           0 :                         CPLDebug("netCDF",
    7663             :                                  "nc_def_var(%s) failed: too large data",
    7664             :                                  pszVarName);
    7665           0 :                         continue;
    7666             :                     }
    7667          17 :                     if (nTotalVarSize >
    7668          34 :                             std::numeric_limits<size_t>::max() - nSize ||
    7669          17 :                         nTotalVarSize + nSize > 100 * 1024 * 1024)
    7670             :                     {
    7671           0 :                         CPLDebug("netCDF",
    7672             :                                  "nc_def_var(%s) failed: too large data",
    7673             :                                  pszVarName);
    7674           0 :                         continue;
    7675             :                     }
    7676          17 :                     nTotalVarSize += nSize;
    7677             : 
    7678          17 :                     int nVarId = -1;
    7679             :                     status =
    7680          30 :                         nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
    7681          13 :                                    (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
    7682          17 :                     if (status != NC_NOERR)
    7683             :                     {
    7684           0 :                         CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
    7685             :                                  pszVarName, nc_strerror(status));
    7686             :                     }
    7687             :                     else
    7688             :                     {
    7689             : #ifdef DEBUG_VERBOSE
    7690             :                         CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
    7691             :                                  pszVarName, pszLine);
    7692             : #endif
    7693          17 :                         oMapVarToId[pszVarName] = nVarId;
    7694          17 :                         oMapVarIdToType[nVarId] = nc_datatype;
    7695          17 :                         oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
    7696             :                     }
    7697             :                 }
    7698             :             }
    7699         120 :             else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
    7700             :             {
    7701         116 :                 CPLString osVarName(pszLine, pszColumn - pszLine);
    7702         116 :                 CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
    7703         116 :                 osAttrName.Trim();
    7704         116 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7705             :                 {
    7706           0 :                     CPLDebug("netCDF",
    7707             :                              "nc_put_att(%s:%s) failed: "
    7708             :                              "no corresponding variable",
    7709             :                              osVarName.c_str(), osAttrName.c_str());
    7710           0 :                     continue;
    7711             :                 }
    7712         116 :                 bool bValidName = true;
    7713        1743 :                 for (size_t i = 0; i < osAttrName.size(); i++)
    7714             :                 {
    7715        1865 :                     if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
    7716         238 :                           (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
    7717         158 :                           (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
    7718         158 :                           osAttrName[i] == '_'))
    7719             :                     {
    7720           0 :                         bValidName = false;
    7721             :                     }
    7722             :                 }
    7723         116 :                 if (!bValidName)
    7724             :                 {
    7725           0 :                     CPLDebug(
    7726             :                         "netCDF",
    7727             :                         "nc_put_att(%s:%s) failed: illegal character found",
    7728             :                         osVarName.c_str(), osAttrName.c_str());
    7729           0 :                     continue;
    7730             :                 }
    7731         116 :                 if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
    7732         232 :                     oSetAttrDefined.end())
    7733             :                 {
    7734           0 :                     CPLDebug("netCDF",
    7735             :                              "nc_put_att(%s:%s) failed: already defined",
    7736             :                              osVarName.c_str(), osAttrName.c_str());
    7737           0 :                     continue;
    7738             :                 }
    7739             : 
    7740         116 :                 const int nVarId = oMapVarToId[osVarName];
    7741         116 :                 const char *pszValue = pszEqual + 1;
    7742         232 :                 while (*pszValue == ' ')
    7743         116 :                     pszValue++;
    7744             : 
    7745         116 :                 status = NC_EBADTYPE;
    7746         116 :                 if (*pszValue == '"')
    7747             :                 {
    7748             :                     // For _FillValue, the attribute type should match
    7749             :                     // the variable type. Leaks memory with NC4 otherwise
    7750          74 :                     if (osAttrName == "_FillValue")
    7751             :                     {
    7752           0 :                         CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7753             :                                  osVarName.c_str(), osAttrName.c_str(),
    7754             :                                  nc_strerror(status));
    7755           0 :                         continue;
    7756             :                     }
    7757             : 
    7758             :                     // Unquote and unescape string value
    7759          74 :                     CPLString osVal(pszValue + 1);
    7760         222 :                     while (!osVal.empty())
    7761             :                     {
    7762         222 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7763             :                         {
    7764         148 :                             osVal.pop_back();
    7765             :                         }
    7766          74 :                         else if (osVal.back() == '"')
    7767             :                         {
    7768          74 :                             osVal.pop_back();
    7769          74 :                             break;
    7770             :                         }
    7771             :                         else
    7772             :                         {
    7773           0 :                             break;
    7774             :                         }
    7775             :                     }
    7776          74 :                     osVal.replaceAll("\\\"", '"');
    7777          74 :                     status = nc_put_att_text(nCdfId, nVarId, osAttrName,
    7778             :                                              osVal.size(), osVal.c_str());
    7779             :                 }
    7780             :                 else
    7781             :                 {
    7782          84 :                     CPLString osVal(pszValue);
    7783         126 :                     while (!osVal.empty())
    7784             :                     {
    7785         126 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7786             :                         {
    7787          84 :                             osVal.pop_back();
    7788             :                         }
    7789             :                         else
    7790             :                         {
    7791          42 :                             break;
    7792             :                         }
    7793             :                     }
    7794          42 :                     int nc_datatype = -1;
    7795          42 :                     if (!osVal.empty() && osVal.back() == 'b')
    7796             :                     {
    7797           3 :                         nc_datatype = NC_BYTE;
    7798           3 :                         osVal.pop_back();
    7799             :                     }
    7800          39 :                     else if (!osVal.empty() && osVal.back() == 's')
    7801             :                     {
    7802           3 :                         nc_datatype = NC_SHORT;
    7803           3 :                         osVal.pop_back();
    7804             :                     }
    7805          42 :                     if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
    7806             :                     {
    7807           7 :                         if (nc_datatype < 0)
    7808           4 :                             nc_datatype = NC_INT;
    7809             :                     }
    7810          35 :                     else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
    7811             :                     {
    7812          32 :                         nc_datatype = NC_DOUBLE;
    7813             :                     }
    7814             :                     else
    7815             :                     {
    7816           3 :                         nc_datatype = -1;
    7817             :                     }
    7818             : 
    7819             :                     // For _FillValue, check that the attribute type matches
    7820             :                     // the variable type. Leaks memory with NC4 otherwise
    7821          42 :                     if (osAttrName == "_FillValue")
    7822             :                     {
    7823           6 :                         if (nVarId < 0 ||
    7824           3 :                             nc_datatype != oMapVarIdToType[nVarId])
    7825             :                         {
    7826           0 :                             nc_datatype = -1;
    7827             :                         }
    7828             :                     }
    7829             : 
    7830          42 :                     if (nc_datatype == NC_BYTE)
    7831             :                     {
    7832             :                         signed char chVal =
    7833           3 :                             static_cast<signed char>(atoi(osVal));
    7834           3 :                         status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
    7835             :                                                   NC_BYTE, 1, &chVal);
    7836             :                     }
    7837          39 :                     else if (nc_datatype == NC_SHORT)
    7838             :                     {
    7839           0 :                         short nVal = static_cast<short>(atoi(osVal));
    7840           0 :                         status = nc_put_att_short(nCdfId, nVarId, osAttrName,
    7841             :                                                   NC_SHORT, 1, &nVal);
    7842             :                     }
    7843          39 :                     else if (nc_datatype == NC_INT)
    7844             :                     {
    7845           4 :                         int nVal = static_cast<int>(atoi(osVal));
    7846           4 :                         status = nc_put_att_int(nCdfId, nVarId, osAttrName,
    7847             :                                                 NC_INT, 1, &nVal);
    7848             :                     }
    7849          35 :                     else if (nc_datatype == NC_DOUBLE)
    7850             :                     {
    7851          32 :                         double dfVal = CPLAtof(osVal);
    7852          32 :                         status = nc_put_att_double(nCdfId, nVarId, osAttrName,
    7853             :                                                    NC_DOUBLE, 1, &dfVal);
    7854             :                     }
    7855             :                 }
    7856         116 :                 if (status != NC_NOERR)
    7857             :                 {
    7858           3 :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7859             :                              osVarName.c_str(), osAttrName.c_str(),
    7860             :                              nc_strerror(status));
    7861             :                 }
    7862             :                 else
    7863             :                 {
    7864         113 :                     oSetAttrDefined.insert(osVarName + ":" + osAttrName);
    7865             : #ifdef DEBUG_VERBOSE
    7866             :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
    7867             :                              osVarName.c_str(), osAttrName.c_str(), pszLine);
    7868             : #endif
    7869             :                 }
    7870             :             }
    7871             :         }
    7872          42 :         else if (nActiveSection == SECTION_DATA)
    7873             :         {
    7874          55 :             while (*pszLine == ' ' || *pszLine == '\t')
    7875          17 :                 pszLine++;
    7876          38 :             const char *pszEqual = strchr(pszLine, '=');
    7877          38 :             if (pszEqual)
    7878             :             {
    7879          17 :                 CPLString osVarName(pszLine, pszEqual - pszLine);
    7880          17 :                 osVarName.Trim();
    7881          17 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7882           0 :                     continue;
    7883          17 :                 const int nVarId = oMapVarToId[osVarName];
    7884          17 :                 CPLString osAccVal(pszEqual + 1);
    7885          17 :                 osAccVal.Trim();
    7886         153 :                 while (osAccVal.empty() || osAccVal.back() != ';')
    7887             :                 {
    7888         136 :                     pszLine = CPLReadLineL(fpSrc);
    7889         136 :                     if (pszLine == nullptr)
    7890           0 :                         break;
    7891         272 :                     CPLString osVal(pszLine);
    7892         136 :                     osVal.Trim();
    7893         136 :                     osAccVal += osVal;
    7894             :                 }
    7895          17 :                 if (pszLine == nullptr)
    7896           0 :                     break;
    7897          17 :                 osAccVal.pop_back();
    7898             : 
    7899             :                 const std::vector<int> aoDimIds =
    7900          34 :                     oMapVarIdToVectorOfDimId[nVarId];
    7901          17 :                 size_t nSize = 1;
    7902          34 :                 std::vector<size_t> aoStart, aoEdge;
    7903          17 :                 aoStart.resize(aoDimIds.size());
    7904          17 :                 aoEdge.resize(aoDimIds.size());
    7905          35 :                 for (size_t i = 0; i < aoDimIds.size(); ++i)
    7906             :                 {
    7907          18 :                     const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
    7908          36 :                     if (nDimSize != 0 &&
    7909          18 :                         nSize > std::numeric_limits<size_t>::max() / nDimSize)
    7910             :                     {
    7911           0 :                         nSize = 0;
    7912             :                     }
    7913             :                     else
    7914             :                     {
    7915          18 :                         nSize *= nDimSize;
    7916             :                     }
    7917          18 :                     aoStart[i] = 0;
    7918          18 :                     aoEdge[i] = nDimSize;
    7919             :                 }
    7920             : 
    7921          17 :                 status = NC_EBADTYPE;
    7922          17 :                 if (nSize == 0)
    7923             :                 {
    7924             :                     // Might happen with an unlimited dimension
    7925             :                 }
    7926          17 :                 else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
    7927             :                 {
    7928           8 :                     if (!aoStart.empty())
    7929             :                     {
    7930             :                         const CPLStringList aosTokens(
    7931          16 :                             CSLTokenizeString2(osAccVal, " ,;", 0));
    7932           8 :                         size_t nTokens = aosTokens.size();
    7933           8 :                         if (nTokens >= nSize)
    7934             :                         {
    7935             :                             double *padfVals = static_cast<double *>(
    7936           8 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
    7937           8 :                             if (padfVals)
    7938             :                             {
    7939         132 :                                 for (size_t i = 0; i < nSize; i++)
    7940             :                                 {
    7941         124 :                                     padfVals[i] = CPLAtof(aosTokens[i]);
    7942             :                                 }
    7943           8 :                                 status = nc_put_vara_double(
    7944           8 :                                     nCdfId, nVarId, &aoStart[0], &aoEdge[0],
    7945             :                                     padfVals);
    7946           8 :                                 VSIFree(padfVals);
    7947             :                             }
    7948             :                         }
    7949             :                     }
    7950             :                 }
    7951           9 :                 else if (oMapVarIdToType[nVarId] == NC_BYTE)
    7952             :                 {
    7953           3 :                     if (!aoStart.empty())
    7954             :                     {
    7955             :                         const CPLStringList aosTokens(
    7956           6 :                             CSLTokenizeString2(osAccVal, " ,;", 0));
    7957           3 :                         size_t nTokens = aosTokens.size();
    7958           3 :                         if (nTokens >= nSize)
    7959             :                         {
    7960             :                             signed char *panVals = static_cast<signed char *>(
    7961           3 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
    7962           3 :                             if (panVals)
    7963             :                             {
    7964        1203 :                                 for (size_t i = 0; i < nSize; i++)
    7965             :                                 {
    7966        1200 :                                     panVals[i] = static_cast<signed char>(
    7967        1200 :                                         atoi(aosTokens[i]));
    7968             :                                 }
    7969           3 :                                 status = nc_put_vara_schar(nCdfId, nVarId,
    7970           3 :                                                            &aoStart[0],
    7971           3 :                                                            &aoEdge[0], panVals);
    7972           3 :                                 VSIFree(panVals);
    7973             :                             }
    7974             :                         }
    7975             :                     }
    7976             :                 }
    7977           6 :                 else if (oMapVarIdToType[nVarId] == NC_CHAR)
    7978             :                 {
    7979           6 :                     if (aoStart.size() == 2)
    7980             :                     {
    7981           4 :                         std::vector<CPLString> aoStrings;
    7982           2 :                         bool bInString = false;
    7983           4 :                         CPLString osCurString;
    7984         935 :                         for (size_t i = 0; i < osAccVal.size();)
    7985             :                         {
    7986         933 :                             if (!bInString)
    7987             :                             {
    7988           8 :                                 if (osAccVal[i] == '"')
    7989             :                                 {
    7990           4 :                                     bInString = true;
    7991           4 :                                     osCurString.clear();
    7992             :                                 }
    7993           8 :                                 i++;
    7994             :                             }
    7995         926 :                             else if (osAccVal[i] == '\\' &&
    7996         926 :                                      i + 1 < osAccVal.size() &&
    7997           1 :                                      osAccVal[i + 1] == '"')
    7998             :                             {
    7999           1 :                                 osCurString += '"';
    8000           1 :                                 i += 2;
    8001             :                             }
    8002         924 :                             else if (osAccVal[i] == '"')
    8003             :                             {
    8004           4 :                                 aoStrings.push_back(osCurString);
    8005           4 :                                 osCurString.clear();
    8006           4 :                                 bInString = false;
    8007           4 :                                 i++;
    8008             :                             }
    8009             :                             else
    8010             :                             {
    8011         920 :                                 osCurString += osAccVal[i];
    8012         920 :                                 i++;
    8013             :                             }
    8014             :                         }
    8015           2 :                         const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
    8016           2 :                         const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
    8017           2 :                         size_t nIters = aoStrings.size();
    8018           2 :                         if (nIters > nRecords)
    8019           0 :                             nIters = nRecords;
    8020           6 :                         for (size_t i = 0; i < nIters; i++)
    8021             :                         {
    8022             :                             size_t anIndex[2];
    8023           4 :                             anIndex[0] = i;
    8024           4 :                             anIndex[1] = 0;
    8025             :                             size_t anCount[2];
    8026           4 :                             anCount[0] = 1;
    8027           4 :                             anCount[1] = aoStrings[i].size();
    8028           4 :                             if (anCount[1] > nWidth)
    8029           0 :                                 anCount[1] = nWidth;
    8030             :                             status =
    8031           4 :                                 nc_put_vara_text(nCdfId, nVarId, anIndex,
    8032           4 :                                                  anCount, aoStrings[i].c_str());
    8033           4 :                             if (status != NC_NOERR)
    8034           0 :                                 break;
    8035             :                         }
    8036             :                     }
    8037             :                 }
    8038          17 :                 if (status != NC_NOERR)
    8039             :                 {
    8040           4 :                     CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
    8041             :                              osVarName.c_str(), nc_strerror(status));
    8042             :                 }
    8043             :             }
    8044             :         }
    8045             :     }
    8046             : 
    8047           4 :     GDAL_nc_close(nCdfId);
    8048           4 :     return true;
    8049             : }
    8050             : 
    8051             : #endif  // ENABLE_NCDUMP
    8052             : 
    8053             : /************************************************************************/
    8054             : /*                                Open()                                */
    8055             : /************************************************************************/
    8056             : 
    8057         875 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
    8058             : 
    8059             : {
    8060             : #ifdef NCDF_DEBUG
    8061             :     CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
    8062             :              poOpenInfo->pszFilename);
    8063             : #endif
    8064             : 
    8065             :     // Does this appear to be a netcdf file?
    8066         875 :     NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
    8067         875 :     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8068             :     {
    8069         806 :         eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
    8070             : #ifdef NCDF_DEBUG
    8071             :         CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
    8072             : #endif
    8073             :         // Note: not calling Identify() directly, because we want the file type.
    8074             :         // Only support NCDF_FORMAT* formats.
    8075         806 :         if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
    8076           2 :             NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
    8077             :         {
    8078             :             // ok
    8079             :         }
    8080           2 :         else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
    8081           0 :                  poOpenInfo->IsSingleAllowedDriver("netCDF"))
    8082             :         {
    8083             :             // ok
    8084             :         }
    8085             :         else
    8086             :         {
    8087           2 :             return nullptr;
    8088             :         }
    8089             :     }
    8090             :     else
    8091             :     {
    8092             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    8093             :         // We don't necessarily want to catch bugs in libnetcdf ...
    8094             :         if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
    8095             :         {
    8096             :             return nullptr;
    8097             :         }
    8098             : #endif
    8099             :     }
    8100             : 
    8101         873 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
    8102             :     {
    8103         320 :         return OpenMultiDim(poOpenInfo);
    8104             :     }
    8105             : 
    8106        1106 :     CPLMutexHolderD(&hNCMutex);
    8107             : 
    8108         553 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    8109             :     // GDALDataset own mutex.
    8110         553 :     netCDFDataset *poDS = new netCDFDataset();
    8111         553 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    8112         553 :     CPLAcquireMutex(hNCMutex, 1000.0);
    8113             : 
    8114         553 :     poDS->SetDescription(poOpenInfo->pszFilename);
    8115             : 
    8116             :     // Check if filename start with NETCDF: tag.
    8117         553 :     bool bTreatAsSubdataset = false;
    8118        1106 :     CPLString osSubdatasetName;
    8119             : 
    8120             : #ifdef ENABLE_NCDUMP
    8121         553 :     const char *pszHeader =
    8122             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    8123         553 :     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
    8124           3 :         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
    8125             :     {
    8126             :         // By default create a temporary file that will be destroyed,
    8127             :         // unless NETCDF_TMP_FILE is defined. Can be useful to see which
    8128             :         // netCDF file has been generated from a potential fuzzed input.
    8129           3 :         poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
    8130           3 :         if (poDS->osFilename.empty())
    8131             :         {
    8132           3 :             poDS->bFileToDestroyAtClosing = true;
    8133           3 :             poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
    8134             :         }
    8135           3 :         if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
    8136             :                                          poOpenInfo->fpL))
    8137             :         {
    8138           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8139             :             // deadlock with GDALDataset own mutex.
    8140           0 :             delete poDS;
    8141           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8142           0 :             return nullptr;
    8143             :         }
    8144           3 :         bTreatAsSubdataset = false;
    8145           3 :         poDS->eFormat = eTmpFormat;
    8146             :     }
    8147             :     else
    8148             : #endif
    8149             : 
    8150         550 :         if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8151             :     {
    8152             :         GDALSubdatasetInfoH hInfo =
    8153          69 :             GDALGetSubdatasetInfo(poOpenInfo->pszFilename);
    8154          69 :         if (hInfo)
    8155             :         {
    8156          69 :             char *pszPath = GDALSubdatasetInfoGetPathComponent(hInfo);
    8157          69 :             poDS->osFilename = pszPath;
    8158          69 :             CPLFree(pszPath);
    8159             : 
    8160             :             char *pszSubdataset =
    8161          69 :                 GDALSubdatasetInfoGetSubdatasetComponent(hInfo);
    8162          69 :             if (pszSubdataset && pszSubdataset[0] != '\0')
    8163             :             {
    8164          69 :                 osSubdatasetName = pszSubdataset;
    8165          69 :                 bTreatAsSubdataset = true;
    8166             :             }
    8167             :             else
    8168             :             {
    8169           0 :                 osSubdatasetName = "";
    8170           0 :                 bTreatAsSubdataset = false;
    8171             :             }
    8172          69 :             CPLFree(pszSubdataset);
    8173             : 
    8174          69 :             GDALDestroySubdatasetInfo(hInfo);
    8175             :         }
    8176             : 
    8177         138 :         if (!STARTS_WITH(poDS->osFilename, "http://") &&
    8178          69 :             !STARTS_WITH(poDS->osFilename, "https://"))
    8179             :         {
    8180             :             // Identify Format from real file, with bCheckExt=FALSE.
    8181             :             auto poOpenInfo2 = std::make_unique<GDALOpenInfo>(
    8182          69 :                 poDS->osFilename.c_str(), GA_ReadOnly);
    8183          69 :             poDS->eFormat = netCDFIdentifyFormat(poOpenInfo2.get(),
    8184             :                                                  /* bCheckExt = */ false);
    8185          69 :             if (NCDF_FORMAT_NONE == poDS->eFormat ||
    8186          69 :                 NCDF_FORMAT_UNKNOWN == poDS->eFormat)
    8187             :             {
    8188           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8189             :                 // deadlock with GDALDataset own mutex.
    8190           0 :                 delete poDS;
    8191           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    8192           0 :                 return nullptr;
    8193             :             }
    8194             :         }
    8195             :     }
    8196             :     else
    8197             :     {
    8198         481 :         poDS->osFilename = poOpenInfo->pszFilename;
    8199         481 :         bTreatAsSubdataset = false;
    8200         481 :         poDS->eFormat = eTmpFormat;
    8201             :     }
    8202             : 
    8203             : // Try opening the dataset.
    8204             : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
    8205             :     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
    8206             :              poDS->osFilename.c_str());
    8207             : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
    8208             :     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
    8209             : #endif
    8210         553 :     int cdfid = -1;
    8211         553 :     const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
    8212             :                           ? NC_WRITE
    8213             :                           : NC_NOWRITE;
    8214        1106 :     CPLString osFilenameForNCOpen(poDS->osFilename);
    8215             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    8216             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    8217             :     {
    8218             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    8219             :         osFilenameForNCOpen = pszTemp;
    8220             :         CPLFree(pszTemp);
    8221             :     }
    8222             : #endif
    8223         553 :     int status2 = -1;
    8224             : 
    8225             : #ifdef ENABLE_UFFD
    8226         553 :     cpl_uffd_context *pCtx = nullptr;
    8227             : #endif
    8228             : 
    8229         568 :     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
    8230          15 :         poOpenInfo->eAccess == GA_ReadOnly)
    8231             :     {
    8232          15 :         vsi_l_offset nLength = 0;
    8233          15 :         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
    8234          15 :         if (poDS->fpVSIMEM)
    8235             :         {
    8236             :             // We assume that the file will not be modified. If it is, then
    8237             :             // pabyBuffer might become invalid.
    8238             :             GByte *pabyBuffer =
    8239          15 :                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
    8240          15 :             if (pabyBuffer)
    8241             :             {
    8242          15 :                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
    8243             :                                       nMode, static_cast<size_t>(nLength),
    8244             :                                       pabyBuffer, &cdfid);
    8245             :             }
    8246             :         }
    8247             :     }
    8248             :     else
    8249             :     {
    8250             :         const bool bVsiFile =
    8251         538 :             !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
    8252             : #ifdef ENABLE_UFFD
    8253         538 :         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
    8254         538 :         void *pVma = nullptr;
    8255         538 :         uint64_t nVmaSize = 0;
    8256             : 
    8257         538 :         if (bVsiFile)
    8258             :         {
    8259           2 :             if (bReadOnly)
    8260             :             {
    8261           2 :                 if (CPLIsUserFaultMappingSupported())
    8262             :                 {
    8263           2 :                     pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
    8264             :                                                      &nVmaSize);
    8265             :                 }
    8266             :                 else
    8267             :                 {
    8268           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    8269             :                              "Opening a /vsi file with the netCDF driver "
    8270             :                              "requires Linux userfaultfd to be available. "
    8271             :                              "If running from Docker, "
    8272             :                              "--security-opt seccomp=unconfined might be "
    8273             :                              "needed.%s",
    8274           0 :                              ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8275           0 :                                poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8276           0 :                               GDALGetDriverByName("HDF5"))
    8277             :                                  ? " Or you may set the GDAL_SKIP=netCDF "
    8278             :                                    "configuration option to force the use of "
    8279             :                                    "the HDF5 driver."
    8280             :                                  : "");
    8281             :                 }
    8282             :             }
    8283             :             else
    8284             :             {
    8285           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    8286             :                          "Opening a /vsi file with the netCDF driver is only "
    8287             :                          "supported in read-only mode");
    8288             :             }
    8289             :         }
    8290         538 :         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
    8291             :         {
    8292             :             // netCDF code, at least for netCDF 4.7.0, is confused by filenames
    8293             :             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
    8294             :             // final part
    8295           2 :             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
    8296             :                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);
    8297             :         }
    8298             :         else
    8299         536 :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8300             : #else
    8301             :         if (bVsiFile)
    8302             :         {
    8303             :             CPLError(
    8304             :                 CE_Failure, CPLE_AppDefined,
    8305             :                 "Opening a /vsi file with the netCDF driver requires Linux "
    8306             :                 "userfaultfd to be available.%s",
    8307             :                 ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8308             :                   poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8309             :                  GDALGetDriverByName("HDF5"))
    8310             :                     ? " Or you may set the GDAL_SKIP=netCDF "
    8311             :                       "configuration option to force the use of the HDF5 "
    8312             :                       "driver."
    8313             :                     : "");
    8314             :             status2 = NC_EIO;
    8315             :         }
    8316             :         else
    8317             :         {
    8318             :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8319             :         }
    8320             : #endif
    8321             :     }
    8322         553 :     if (status2 != NC_NOERR)
    8323             :     {
    8324             : #ifdef NCDF_DEBUG
    8325             :         CPLDebug("GDAL_netCDF", "error opening");
    8326             : #endif
    8327           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8328             :         // with GDALDataset own mutex.
    8329           0 :         delete poDS;
    8330           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8331           0 :         return nullptr;
    8332             :     }
    8333             : #ifdef NCDF_DEBUG
    8334             :     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
    8335             : #endif
    8336             : 
    8337             : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
    8338             :     // Try to destroy the temporary file right now on Unix
    8339         553 :     if (poDS->bFileToDestroyAtClosing)
    8340             :     {
    8341           3 :         if (VSIUnlink(poDS->osFilename) == 0)
    8342             :         {
    8343           3 :             poDS->bFileToDestroyAtClosing = false;
    8344             :         }
    8345             :     }
    8346             : #endif
    8347             : 
    8348             :     // Is this a real netCDF file?
    8349             :     int ndims;
    8350             :     int ngatts;
    8351             :     int nvars;
    8352             :     int unlimdimid;
    8353         553 :     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
    8354         553 :     if (status != NC_NOERR)
    8355             :     {
    8356           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8357             :         // with GDALDataset own mutex.
    8358           0 :         delete poDS;
    8359           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8360           0 :         return nullptr;
    8361             :     }
    8362             : 
    8363             :     // Get file type from netcdf.
    8364         553 :     int nTmpFormat = NCDF_FORMAT_NONE;
    8365         553 :     status = nc_inq_format(cdfid, &nTmpFormat);
    8366         553 :     if (status != NC_NOERR)
    8367             :     {
    8368           0 :         NCDF_ERR(status);
    8369             :     }
    8370             :     else
    8371             :     {
    8372         553 :         CPLDebug("GDAL_netCDF",
    8373             :                  "driver detected file type=%d, libnetcdf detected type=%d",
    8374         553 :                  poDS->eFormat, nTmpFormat);
    8375         553 :         if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
    8376             :         {
    8377             :             // Warn if file detection conflicts with that from libnetcdf
    8378             :             // except for NC4C, which we have no way of detecting initially.
    8379          26 :             if (nTmpFormat != NCDF_FORMAT_NC4C &&
    8380          13 :                 !STARTS_WITH(poDS->osFilename, "http://") &&
    8381           0 :                 !STARTS_WITH(poDS->osFilename, "https://"))
    8382             :             {
    8383           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    8384             :                          "NetCDF driver detected file type=%d, but libnetcdf "
    8385             :                          "detected type=%d",
    8386           0 :                          poDS->eFormat, nTmpFormat);
    8387             :             }
    8388          13 :             CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
    8389          13 :                      nTmpFormat, poDS->eFormat);
    8390          13 :             poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
    8391             :         }
    8392             :     }
    8393             : 
    8394             :     // Does the request variable exist?
    8395         553 :     if (bTreatAsSubdataset)
    8396             :     {
    8397             :         int dummy;
    8398          69 :         if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
    8399          69 :                                &dummy) != CE_None)
    8400             :         {
    8401           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    8402             :                      "%s is a netCDF file, but %s is not a variable.",
    8403             :                      poOpenInfo->pszFilename, osSubdatasetName.c_str());
    8404             : 
    8405           0 :             GDAL_nc_close(cdfid);
    8406             : #ifdef ENABLE_UFFD
    8407           0 :             NETCDF_UFFD_UNMAP(pCtx);
    8408             : #endif
    8409           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8410             :             // deadlock with GDALDataset own mutex.
    8411           0 :             delete poDS;
    8412           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8413           0 :             return nullptr;
    8414             :         }
    8415             :     }
    8416             : 
    8417             :     // Figure out whether or not the listed dataset has support for simple
    8418             :     // geometries (CF-1.8)
    8419         553 :     nccfdriver::getCFVersion(cdfid, poDS->nCFVersionMajor,
    8420         553 :                              poDS->nCFVersionMinor);
    8421         553 :     bool bHasSimpleGeometries = false;  // but not necessarily valid
    8422         553 :     if (poDS->nCFVersionMajor > 1 ||
    8423         553 :         (poDS->nCFVersionMajor == 1 && poDS->nCFVersionMinor >= 8))
    8424             :     {
    8425          74 :         bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
    8426          74 :         if (bHasSimpleGeometries)
    8427             :         {
    8428          66 :             poDS->bSGSupport = true;
    8429          66 :             poDS->vcdf.enableFullVirtualMode();
    8430             :         }
    8431             :     }
    8432             : 
    8433        1106 :     std::string osConventions;
    8434         553 :     if (NCDFGetAttr(cdfid, NC_GLOBAL, "Conventions", osConventions) != CE_None)
    8435             :     {
    8436          60 :         CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
    8437             :         // Note that 'Conventions' is always capital 'C' in CF spec.
    8438             :     }
    8439             : 
    8440             :     // Create band information objects.
    8441         553 :     CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
    8442             : 
    8443             :     // Create a corresponding GDALDataset.
    8444             :     // Create Netcdf Subdataset if filename as NETCDF tag.
    8445         553 :     poDS->cdfid = cdfid;
    8446             : #ifdef ENABLE_UFFD
    8447         553 :     poDS->pCtx = pCtx;
    8448             : #endif
    8449         553 :     poDS->eAccess = poOpenInfo->eAccess;
    8450         553 :     poDS->bDefineMode = false;
    8451             : 
    8452         553 :     poDS->ReadAttributes(cdfid, NC_GLOBAL);
    8453             : 
    8454             :     // Identify coordinate and boundary variables that we should
    8455             :     // ignore as Raster Bands.
    8456        1106 :     CPLStringList aosIgnoreVars;
    8457         553 :     NCDFGetCoordAndBoundVarFullNames(cdfid, aosIgnoreVars);
    8458             :     // Filter variables to keep only valid 2+D raster bands and vector fields.
    8459         553 :     int nRasterVars = 0;
    8460         553 :     int nIgnoredVars = 0;
    8461         553 :     int nGroupID = -1;
    8462         553 :     int nVarID = -1;
    8463             : 
    8464             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
    8465        1106 :         oMap2DDimsToGroupAndVar;
    8466        1462 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8467         356 :         STARTS_WITH(
    8468             :             poDS->aosMetadata.FetchNameValueDef("NC_GLOBAL#mission_name", ""),
    8469           1 :             "Sentinel 3") &&
    8470           1 :         EQUAL(poDS->aosMetadata.FetchNameValueDef(
    8471             :                   "NC_GLOBAL#altimeter_sensor_name", ""),
    8472         909 :               "SRAL") &&
    8473           1 :         EQUAL(poDS->aosMetadata.FetchNameValueDef(
    8474             :                   "NC_GLOBAL#radiometer_sensor_name", ""),
    8475             :               "MWR"))
    8476             :     {
    8477           1 :         if (poDS->eAccess == GA_Update)
    8478             :         {
    8479           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8480             :             // deadlock with GDALDataset own mutex.
    8481           0 :             delete poDS;
    8482           0 :             return nullptr;
    8483             :         }
    8484           1 :         poDS->ProcessSentinel3_SRAL_MWR();
    8485             :     }
    8486             :     else
    8487             :     {
    8488         552 :         poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
    8489         907 :                          (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8490         355 :                              !bHasSimpleGeometries,
    8491             :                          aosIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
    8492             :                          &nIgnoredVars, oMap2DDimsToGroupAndVar);
    8493             :     }
    8494             : 
    8495         553 :     const bool bListAllArrays = CPLTestBool(
    8496         553 :         CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    8497             : 
    8498             :     // Case where there is no raster variable
    8499         553 :     if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
    8500             :     {
    8501         138 :         poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
    8502         138 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8503             :         // with GDALDataset own mutex.
    8504         138 :         poDS->TryLoadXML();
    8505             :         // If the dataset has been opened in raster mode only, exit
    8506         138 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    8507           6 :             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    8508             :         {
    8509           4 :             delete poDS;
    8510           4 :             poDS = nullptr;
    8511             :         }
    8512             :         // Otherwise if the dataset is opened in vector mode, that there is
    8513             :         // no vector layer and we are in read-only, exit too.
    8514         134 :         else if (poDS->GetLayerCount() == 0 &&
    8515         142 :                  (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8516           8 :                  poOpenInfo->eAccess == GA_ReadOnly)
    8517             :         {
    8518           8 :             delete poDS;
    8519           8 :             poDS = nullptr;
    8520             :         }
    8521         138 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8522         138 :         return poDS;
    8523             :     }
    8524             : 
    8525             :     // We have more than one variable with 2 dimensions in the
    8526             :     // file, then treat this as a subdataset container dataset.
    8527         415 :     bool bSeveralVariablesAsBands = false;
    8528         415 :     if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
    8529             :     {
    8530          30 :         if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
    8531          36 :                          false) &&
    8532           6 :             oMap2DDimsToGroupAndVar.size() == 1)
    8533             :         {
    8534           6 :             std::tie(nGroupID, nVarID) =
    8535          12 :                 oMap2DDimsToGroupAndVar.begin()->second.front();
    8536           6 :             bSeveralVariablesAsBands = true;
    8537             :         }
    8538             :         else
    8539             :         {
    8540          24 :             poDS->CreateSubDatasetList(cdfid);
    8541          24 :             poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
    8542          24 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8543             :             // deadlock with GDALDataset own mutex.
    8544          24 :             poDS->TryLoadXML();
    8545          24 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8546          24 :             return poDS;
    8547             :         }
    8548             :     }
    8549             : 
    8550             :     // If we are not treating things as a subdataset, then capture
    8551             :     // the name of the single available variable as the subdataset.
    8552         391 :     if (!bTreatAsSubdataset)
    8553             :     {
    8554         322 :         NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, osSubdatasetName));
    8555             :     }
    8556             : 
    8557             :     // We have ignored at least one variable, so we should report them
    8558             :     // as subdatasets for reference.
    8559         391 :     if (nIgnoredVars > 0 && !bTreatAsSubdataset)
    8560             :     {
    8561          29 :         CPLDebug("GDAL_netCDF",
    8562             :                  "As %d variables were ignored, creating subdataset list "
    8563             :                  "for reference. Variable #%d [%s] is the main variable",
    8564             :                  nIgnoredVars, nVarID, osSubdatasetName.c_str());
    8565          29 :         poDS->CreateSubDatasetList(cdfid);
    8566             :     }
    8567             : 
    8568             :     // Open the NETCDF subdataset NETCDF:"filename":subdataset.
    8569         391 :     int var = -1;
    8570         391 :     NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
    8571             :     // Now we can forget the root cdfid and only use the selected group.
    8572         391 :     cdfid = nGroupID;
    8573         391 :     int nd = 0;
    8574         391 :     nc_inq_varndims(cdfid, var, &nd);
    8575             : 
    8576         391 :     poDS->m_anDimIds.resize(nd);
    8577             : 
    8578             :     // X, Y, Z position in array
    8579         782 :     std::vector<int> anBandDimPos(nd);
    8580             : 
    8581         391 :     nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
    8582             : 
    8583             :     // Check if somebody tried to pass a variable with less than 1D.
    8584         391 :     if (nd < 1)
    8585             :     {
    8586           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8587             :                  "Variable has %d dimension(s) - not supported.", nd);
    8588           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8589             :         // with GDALDataset own mutex.
    8590           0 :         delete poDS;
    8591           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8592           0 :         return nullptr;
    8593             :     }
    8594             : 
    8595             :     // CF-1 Convention
    8596             :     //
    8597             :     // Dimensions to appear in the relative order T, then Z, then Y,
    8598             :     // then X  to the file. All other dimensions should, whenever
    8599             :     // possible, be placed to the left of the spatiotemporal
    8600             :     // dimensions.
    8601             : 
    8602             :     // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
    8603             :     // Ideally we should detect for other ordering and act accordingly
    8604             :     // Only done if file has Conventions=CF-* and only prints warning
    8605             :     // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
    8606             :     // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
    8607             :     const bool bCheckDims =
    8608         782 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
    8609         391 :         STARTS_WITH_CI(osConventions.c_str(), "CF");
    8610             : 
    8611         391 :     bool bYXBandOrder = false;
    8612         391 :     if (nd == 3)
    8613             :     {
    8614             :         // If there's a coordinates attributes, and the variable it points to
    8615             :         // are 2D variables indexed by the same first and second dimension than
    8616             :         // our variable of interest, then it is Y,X,Band order.
    8617          50 :         char *pszCoordinates = nullptr;
    8618          50 :         if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
    8619          69 :                 CE_None &&
    8620          19 :             pszCoordinates)
    8621             :         {
    8622             :             const CPLStringList aosCoordinates(
    8623          38 :                 NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    8624          19 :             if (aosCoordinates.size() == 2)
    8625             :             {
    8626             :                 // Test that each variable is longitude/latitude.
    8627          13 :                 for (int i = 0; i < aosCoordinates.size(); i++)
    8628             :                 {
    8629          13 :                     if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
    8630           4 :                         NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
    8631             :                     {
    8632           9 :                         int nOtherGroupId = -1;
    8633           9 :                         int nOtherVarId = -1;
    8634           9 :                         if (NCDFResolveVar(cdfid, aosCoordinates[i],
    8635             :                                            &nOtherGroupId,
    8636           9 :                                            &nOtherVarId) == CE_None)
    8637             :                         {
    8638           9 :                             int coordDimCount = 0;
    8639           9 :                             nc_inq_varndims(nOtherGroupId, nOtherVarId,
    8640             :                                             &coordDimCount);
    8641           9 :                             if (coordDimCount == 2)
    8642             :                             {
    8643           3 :                                 int coordDimIds[2] = {0, 0};
    8644           3 :                                 nc_inq_vardimid(nOtherGroupId, nOtherVarId,
    8645             :                                                 coordDimIds);
    8646           4 :                                 if (coordDimIds[0] == poDS->m_anDimIds[0] &&
    8647           1 :                                     coordDimIds[1] == poDS->m_anDimIds[1])
    8648             :                                 {
    8649           1 :                                     bYXBandOrder = true;
    8650           1 :                                     break;
    8651             :                                 }
    8652             :                             }
    8653             :                         }
    8654             :                     }
    8655             :                 }
    8656             :             }
    8657             :         }
    8658          50 :         CPLFree(pszCoordinates);
    8659             : 
    8660          50 :         if (!bYXBandOrder)
    8661             :         {
    8662          49 :             char szDim0Name[NC_MAX_NAME + 1] = {};
    8663          49 :             char szDim1Name[NC_MAX_NAME + 1] = {};
    8664          49 :             status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
    8665          49 :             NCDF_ERR(status);
    8666          49 :             status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
    8667          49 :             NCDF_ERR(status);
    8668             : 
    8669          49 :             if (strcmp(szDim0Name, "number_of_lines") == 0 &&
    8670           1 :                 strcmp(szDim1Name, "pixels_per_line") == 0)
    8671             :             {
    8672             :                 // Like in PACE OCI products
    8673           1 :                 bYXBandOrder = true;
    8674             :             }
    8675             :             else
    8676             :             {
    8677             :                 // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
    8678             :                 // dimension order is downtrack, crosstrack, bands
    8679          48 :                 char szDim2Name[NC_MAX_NAME + 1] = {};
    8680          48 :                 status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
    8681          48 :                 NCDF_ERR(status);
    8682          94 :                 bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
    8683          46 :                                strcmp(szDim2Name, "band") == 0;
    8684             :             }
    8685             :         }
    8686             :     }
    8687             : 
    8688         391 :     if (nd >= 2 && bCheckDims && !bYXBandOrder)
    8689             :     {
    8690         298 :         char szDimName1[NC_MAX_NAME + 1] = {};
    8691         298 :         char szDimName2[NC_MAX_NAME + 1] = {};
    8692         298 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
    8693         298 :         NCDF_ERR(status);
    8694         298 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
    8695         298 :         NCDF_ERR(status);
    8696         484 :         if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
    8697         186 :             NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
    8698             :         {
    8699           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8700             :                      "dimension #%d (%s) is not a Longitude/X dimension.",
    8701             :                      nd - 1, szDimName1);
    8702             :         }
    8703         484 :         if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
    8704         186 :             NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
    8705             :         {
    8706           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8707             :                      "dimension #%d (%s) is not a Latitude/Y dimension.",
    8708             :                      nd - 2, szDimName2);
    8709             :         }
    8710         298 :         if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
    8711         300 :              NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
    8712           2 :             (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
    8713           0 :              NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
    8714             :         {
    8715           2 :             poDS->bSwitchedXY = true;
    8716             :         }
    8717         298 :         if (nd >= 3)
    8718             :         {
    8719          55 :             char szDimName3[NC_MAX_NAME + 1] = {};
    8720             :             status =
    8721          55 :                 nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
    8722          55 :             NCDF_ERR(status);
    8723          55 :             if (nd >= 4)
    8724             :             {
    8725          13 :                 char szDimName4[NC_MAX_NAME + 1] = {};
    8726             :                 status =
    8727          13 :                     nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
    8728          13 :                 NCDF_ERR(status);
    8729          13 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
    8730             :                 {
    8731           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8732             :                              "dimension #%d (%s) is not a Vertical dimension.",
    8733             :                              nd - 3, szDimName3);
    8734             :                 }
    8735          13 :                 if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
    8736             :                 {
    8737           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8738             :                              "dimension #%d (%s) is not a Time dimension.",
    8739             :                              nd - 4, szDimName4);
    8740             :                 }
    8741             :             }
    8742             :             else
    8743             :             {
    8744          81 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
    8745          39 :                     NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
    8746             :                 {
    8747           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8748             :                              "dimension #%d (%s) is not a "
    8749             :                              "Time or Vertical dimension.",
    8750             :                              nd - 3, szDimName3);
    8751             :                 }
    8752             :             }
    8753             :         }
    8754             :     }
    8755             : 
    8756             :     // Get X dimensions information.
    8757             :     size_t xdim;
    8758         391 :     poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
    8759         391 :     nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
    8760             : 
    8761             :     // Get Y dimension information.
    8762             :     size_t ydim;
    8763         391 :     if (nd >= 2)
    8764             :     {
    8765         382 :         poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
    8766         382 :         nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
    8767             :     }
    8768             :     else
    8769             :     {
    8770           9 :         poDS->nYDimID = -1;
    8771           9 :         ydim = 1;
    8772             :     }
    8773             : 
    8774         391 :     if (xdim > INT_MAX || ydim > INT_MAX)
    8775             :     {
    8776           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8777             :                  "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
    8778             :                  static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
    8779           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8780             :         // with GDALDataset own mutex.
    8781           0 :         delete poDS;
    8782           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8783           0 :         return nullptr;
    8784             :     }
    8785             : 
    8786         391 :     poDS->nRasterXSize = static_cast<int>(xdim);
    8787         391 :     poDS->nRasterYSize = static_cast<int>(ydim);
    8788             : 
    8789         391 :     unsigned int k = 0;
    8790        1249 :     for (int j = 0; j < nd; j++)
    8791             :     {
    8792         858 :         if (poDS->m_anDimIds[j] == poDS->nXDimID)
    8793             :         {
    8794         391 :             anBandDimPos[0] = j;  // Save Position of XDim
    8795         391 :             k++;
    8796             :         }
    8797         858 :         if (poDS->m_anDimIds[j] == poDS->nYDimID)
    8798             :         {
    8799         382 :             anBandDimPos[1] = j;  // Save Position of YDim
    8800         382 :             k++;
    8801             :         }
    8802             :     }
    8803             :     // X and Y Dimension Ids were not found!
    8804         391 :     if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
    8805             :     {
    8806           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8807             :         // with GDALDataset own mutex.
    8808           0 :         delete poDS;
    8809           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8810           0 :         return nullptr;
    8811             :     }
    8812             : 
    8813             :     // Read Metadata for this variable.
    8814             : 
    8815             :     // Should disable as is also done at band level, except driver needs the
    8816             :     // variables as metadata (e.g. projection).
    8817         391 :     poDS->ReadAttributes(cdfid, var);
    8818             : 
    8819             :     // Read Metadata for each dimension.
    8820         391 :     int *panDimIds = nullptr;
    8821         391 :     NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
    8822             :     // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
    8823             :     // in NetCDF-3 because we see only the dimensions of the selected group
    8824             :     // and its parents.
    8825             :     // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
    8826             :     // [0..max(panDimIds)], but they are not all useful so we fill names
    8827             :     // of useless dims with empty string.
    8828         391 :     if (panDimIds)
    8829             :     {
    8830         391 :         const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
    8831         391 :         std::set<int> oSetExistingDimIds;
    8832        1297 :         for (int i = 0; i < ndims; i++)
    8833             :         {
    8834         906 :             oSetExistingDimIds.insert(panDimIds[i]);
    8835             :         }
    8836         391 :         std::set<int> oSetDimIdsUsedByVar;
    8837        1249 :         for (int i = 0; i < nd; i++)
    8838             :         {
    8839         858 :             oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
    8840             :         }
    8841        1299 :         for (int j = 0; j <= nMaxDimId; j++)
    8842             :         {
    8843             :             // Is j dim used?
    8844         908 :             if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
    8845             :             {
    8846             :                 // Useful dim.
    8847         906 :                 char szTemp[NC_MAX_NAME + 1] = {};
    8848         906 :                 status = nc_inq_dimname(cdfid, j, szTemp);
    8849         906 :                 if (status != NC_NOERR)
    8850             :                 {
    8851           0 :                     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8852             :                     // deadlock with GDALDataset own
    8853             :                     // mutex.
    8854           0 :                     delete poDS;
    8855           0 :                     CPLAcquireMutex(hNCMutex, 1000.0);
    8856           0 :                     return nullptr;
    8857             :                 }
    8858         906 :                 poDS->papszDimName.AddString(szTemp);
    8859             : 
    8860         906 :                 if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
    8861             :                 {
    8862         858 :                     int nDimGroupId = -1;
    8863         858 :                     int nDimVarId = -1;
    8864         858 :                     if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
    8865         858 :                                        &nDimGroupId, &nDimVarId) == CE_None)
    8866             :                     {
    8867         629 :                         poDS->ReadAttributes(nDimGroupId, nDimVarId);
    8868             :                     }
    8869             :                 }
    8870             :             }
    8871             :             else
    8872             :             {
    8873             :                 // Useless dim.
    8874           2 :                 poDS->papszDimName.AddString("");
    8875             :             }
    8876             :         }
    8877         391 :         CPLFree(panDimIds);
    8878             :     }
    8879             : 
    8880             :     // Set projection info.
    8881         782 :     std::vector<std::string> aosRemovedMDItems;
    8882         391 :     if (nd > 1)
    8883             :     {
    8884         382 :         poDS->SetProjectionFromVar(cdfid, var,
    8885             :                                    /*bReadSRSOnly=*/false,
    8886             :                                    /* pszGivenGM = */ nullptr,
    8887             :                                    /* returnProjStr = */ nullptr,
    8888             :                                    /* sg = */ nullptr, &aosRemovedMDItems);
    8889             :     }
    8890             : 
    8891             :     // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
    8892         391 :     const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
    8893         391 :     if (pszValue)
    8894             :     {
    8895          24 :         poDS->bBottomUp = CPLTestBool(pszValue);
    8896          24 :         CPLDebug("GDAL_netCDF",
    8897             :                  "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
    8898          24 :                  static_cast<int>(poDS->bBottomUp), pszValue);
    8899             :     }
    8900             : 
    8901             :     // Save non-spatial dimension info.
    8902             : 
    8903         391 :     int *panBandZLev = nullptr;
    8904         391 :     int nDim = (nd >= 2) ? 2 : 1;
    8905             :     size_t lev_count;
    8906         391 :     size_t nTotLevCount = 1;
    8907         391 :     nc_type nType = NC_NAT;
    8908             : 
    8909         391 :     if (nd > 2)
    8910             :     {
    8911          66 :         nDim = 2;
    8912          66 :         panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
    8913             : 
    8914         132 :         CPLString osExtraDimNames = "{";
    8915             : 
    8916          66 :         char szDimName[NC_MAX_NAME + 1] = {};
    8917             : 
    8918          66 :         bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
    8919         283 :         for (int j = 0; j < nd; j++)
    8920             :         {
    8921         368 :             if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
    8922         151 :                 (poDS->m_anDimIds[j] != poDS->nYDimID))
    8923             :             {
    8924          85 :                 nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
    8925          85 :                 nTotLevCount *= lev_count;
    8926          85 :                 panBandZLev[nDim - 2] = static_cast<int>(lev_count);
    8927          85 :                 anBandDimPos[nDim] = j;  // Save Position of ZDim
    8928             :                 // Save non-spatial dimension names.
    8929          85 :                 if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
    8930             :                     NC_NOERR)
    8931             :                 {
    8932          85 :                     osExtraDimNames += szDimName;
    8933          85 :                     if (j < nd - 3)
    8934             :                     {
    8935          19 :                         osExtraDimNames += ",";
    8936             :                     }
    8937             : 
    8938          85 :                     int nIdxGroupID = -1;
    8939          85 :                     int nIdxVarID = Get1DVariableIndexedByDimension(
    8940          85 :                         cdfid, poDS->m_anDimIds[j], szDimName, true,
    8941          85 :                         &nIdxGroupID);
    8942          85 :                     poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
    8943          85 :                     poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
    8944             : 
    8945          85 :                     if (nIdxVarID >= 0)
    8946             :                     {
    8947          76 :                         nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
    8948             :                         char szExtraDimDef[NC_MAX_NAME + 1];
    8949          76 :                         snprintf(szExtraDimDef, sizeof(szExtraDimDef),
    8950             :                                  "{%ld,%d}", (long)lev_count, nType);
    8951             :                         char szTemp[NC_MAX_NAME + 32 + 1];
    8952          76 :                         snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    8953             :                                  szDimName);
    8954          76 :                         poDS->aosMetadata.SetNameValue(szTemp, szExtraDimDef);
    8955             : 
    8956             :                         // Retrieving data for unlimited dimensions might be
    8957             :                         // costly on network storage, so don't do it.
    8958             :                         // Each band will capture the value along the extra
    8959             :                         // dimension in its NETCDF_DIM_xxxx band metadata item
    8960             :                         // Addresses use case of
    8961             :                         // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
    8962             :                         const bool bIsLocal =
    8963          76 :                             VSIIsLocal(osFilenameForNCOpen.c_str());
    8964             :                         bool bListDimValues =
    8965          77 :                             bIsLocal || lev_count == 1 ||
    8966           1 :                             !NCDFIsUnlimitedDim(poDS->eFormat ==
    8967             :                                                     NCDF_FORMAT_NC4,
    8968           1 :                                                 cdfid, poDS->m_anDimIds[j]);
    8969             :                         const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
    8970          76 :                             CPLGetConfigOption(
    8971             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
    8972          76 :                         if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
    8973             :                         {
    8974           2 :                             bListDimValues = CPLTestBool(
    8975             :                                 pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
    8976             :                         }
    8977          74 :                         else if (!bListDimValues && !bIsLocal &&
    8978           1 :                                  !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
    8979             :                         {
    8980           1 :                             bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
    8981           1 :                             CPLDebug(
    8982             :                                 "GDAL_netCDF",
    8983             :                                 "Listing extra dimension values is skipped "
    8984             :                                 "because this dataset is hosted on a network "
    8985             :                                 "file system, and such an operation could be "
    8986             :                                 "slow. If you still want to proceed, set the "
    8987             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
    8988             :                                 "configuration option to YES");
    8989             :                         }
    8990          76 :                         if (bListDimValues)
    8991             :                         {
    8992          74 :                             char *pszTemp = nullptr;
    8993          74 :                             if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
    8994          74 :                                              &pszTemp) == CE_None)
    8995             :                             {
    8996          74 :                                 snprintf(szTemp, sizeof(szTemp),
    8997             :                                          "NETCDF_DIM_%s_VALUES", szDimName);
    8998          74 :                                 poDS->aosMetadata.SetNameValue(szTemp, pszTemp);
    8999          74 :                                 CPLFree(pszTemp);
    9000             :                             }
    9001             :                         }
    9002             :                     }
    9003             :                 }
    9004             :                 else
    9005             :                 {
    9006           0 :                     poDS->m_anExtraDimGroupIds.push_back(-1);
    9007           0 :                     poDS->m_anExtraDimVarIds.push_back(-1);
    9008             :                 }
    9009             : 
    9010          85 :                 nDim++;
    9011             :             }
    9012             :         }
    9013          66 :         osExtraDimNames += "}";
    9014          66 :         poDS->aosMetadata.SetNameValue("NETCDF_DIM_EXTRA", osExtraDimNames);
    9015             :     }
    9016             : 
    9017             :     // Store Metadata.
    9018         401 :     for (const auto &osStr : aosRemovedMDItems)
    9019          10 :         poDS->aosMetadata.SetNameValue(osStr.c_str(), nullptr);
    9020             : 
    9021         391 :     poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
    9022             : 
    9023             :     // Create bands.
    9024             : 
    9025             :     // Arbitrary threshold.
    9026             :     int nMaxBandCount =
    9027         391 :         atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
    9028         391 :     if (nMaxBandCount <= 0)
    9029           0 :         nMaxBandCount = 32768;
    9030         391 :     if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
    9031             :     {
    9032           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9033             :                  "Limiting number of bands to %d instead of %u", nMaxBandCount,
    9034             :                  static_cast<unsigned int>(nTotLevCount));
    9035           0 :         nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
    9036             :     }
    9037         391 :     if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
    9038             :     {
    9039           0 :         poDS->nRasterXSize = 0;
    9040           0 :         poDS->nRasterYSize = 0;
    9041           0 :         nTotLevCount = 0;
    9042           0 :         if (poDS->GetLayerCount() == 0)
    9043             :         {
    9044           0 :             CPLFree(panBandZLev);
    9045           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9046             :             // deadlock with GDALDataset own mutex.
    9047           0 :             delete poDS;
    9048           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9049           0 :             return nullptr;
    9050             :         }
    9051             :     }
    9052         391 :     if (bSeveralVariablesAsBands)
    9053             :     {
    9054           6 :         const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
    9055          24 :         for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
    9056             :              ++iBand)
    9057             :         {
    9058          18 :             int bandVarGroupId = listVariables[iBand].first;
    9059          18 :             int bandVarId = listVariables[iBand].second;
    9060             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    9061           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
    9062          18 :                 bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
    9063          18 :             poDS->SetBand(iBand + 1, poBand);
    9064             :         }
    9065             :     }
    9066             :     else
    9067             :     {
    9068         886 :         for (unsigned int lev = 0; lev < nTotLevCount; lev++)
    9069             :         {
    9070             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    9071           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
    9072         501 :                 lev, panBandZLev, anBandDimPos.data(), lev + 1);
    9073         501 :             poDS->SetBand(lev + 1, poBand);
    9074             :         }
    9075             :     }
    9076             : 
    9077         391 :     if (panBandZLev)
    9078          66 :         CPLFree(panBandZLev);
    9079             :     // Handle angular geographic coordinates here
    9080             : 
    9081             :     // Initialize any PAM information.
    9082         391 :     if (bTreatAsSubdataset)
    9083             :     {
    9084          69 :         poDS->SetPhysicalFilename(poDS->osFilename);
    9085          69 :         poDS->SetSubdatasetName(osSubdatasetName);
    9086             :     }
    9087             : 
    9088         391 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9089             :     // GDALDataset own mutex.
    9090         391 :     poDS->TryLoadXML();
    9091             : 
    9092         391 :     if (bTreatAsSubdataset)
    9093          69 :         poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
    9094             :     else
    9095         322 :         poDS->oOvManager.Initialize(poDS, poDS->osFilename);
    9096             : 
    9097         391 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9098             : 
    9099         391 :     return poDS;
    9100             : }
    9101             : 
    9102             : /************************************************************************/
    9103             : /*                            CopyMetadata()                            */
    9104             : /*                                                                      */
    9105             : /*      Create a copy of metadata for NC_GLOBAL or a variable           */
    9106             : /************************************************************************/
    9107             : 
    9108         182 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
    9109             :                          GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
    9110             :                          const char *pszPrefix)
    9111             : {
    9112             :     // Remove the following band meta but set them later from band data.
    9113         182 :     const char *const papszIgnoreBand[] = {
    9114             :         CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    9115             :         NCDF_FillValue, "coordinates",   nullptr};
    9116         182 :     const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
    9117             : 
    9118         182 :     CSLConstList papszMetadata = nullptr;
    9119         182 :     if (poSrcDS)
    9120             :     {
    9121          79 :         papszMetadata = poSrcDS->GetMetadata();
    9122             :     }
    9123         103 :     else if (poSrcBand)
    9124             :     {
    9125         103 :         papszMetadata = poSrcBand->GetMetadata();
    9126             :     }
    9127             : 
    9128         976 :     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
    9129             :     {
    9130             : #ifdef NCDF_DEBUG
    9131             :         CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
    9132             : #endif
    9133             : 
    9134         794 :         CPLString osMetaName(pszKey);
    9135             : 
    9136             :         // Check for items that match pszPrefix if applicable.
    9137         794 :         if (pszPrefix && !EQUAL(pszPrefix, ""))
    9138             :         {
    9139             :             // Remove prefix.
    9140         171 :             if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
    9141             :             {
    9142          21 :                 osMetaName = osMetaName.substr(strlen(pszPrefix));
    9143             :             }
    9144             :             // Only copy items that match prefix.
    9145             :             else
    9146             :             {
    9147         150 :                 continue;
    9148             :             }
    9149             :         }
    9150             : 
    9151             :         // Fix various issues with metadata translation.
    9152         644 :         if (CDFVarID == NC_GLOBAL)
    9153             :         {
    9154             :             // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
    9155         874 :             if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
    9156         434 :                 (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
    9157          24 :                 continue;
    9158             :             // Remove NC_GLOBAL prefix for netcdf global Metadata.
    9159         416 :             else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
    9160             :             {
    9161          59 :                 osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
    9162             :             }
    9163             :             // GDAL Metadata renamed as GDAL-[meta].
    9164         357 :             else if (strstr(osMetaName, "#") == nullptr)
    9165             :             {
    9166          22 :                 osMetaName = "GDAL_" + osMetaName;
    9167             :             }
    9168             :             // Keep time, lev and depth information for safe-keeping.
    9169             :             // Time and vertical coordinate handling need improvements.
    9170             :             /*
    9171             :             else if( STARTS_WITH(szMetaName, "time#") )
    9172             :             {
    9173             :                 szMetaName[4] = '-';
    9174             :             }
    9175             :             else if( STARTS_WITH(szMetaName, "lev#") )
    9176             :             {
    9177             :                 szMetaName[3] = '-';
    9178             :             }
    9179             :             else if( STARTS_WITH(szMetaName, "depth#") )
    9180             :             {
    9181             :                 szMetaName[5] = '-';
    9182             :             }
    9183             :             */
    9184             :             // Only copy data without # (previously all data was copied).
    9185         416 :             if (strstr(osMetaName, "#") != nullptr)
    9186         335 :                 continue;
    9187             :             // netCDF attributes do not like the '#' character.
    9188             :             // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
    9189             :             //     if( szMetaName[h] == '#') szMetaName[h] = '-';
    9190             :             // }
    9191             :         }
    9192             :         else
    9193             :         {
    9194             :             // Do not copy varname, stats, NETCDF_DIM_*, nodata
    9195             :             // and items in papszIgnoreBand.
    9196         204 :             if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
    9197         166 :                 STARTS_WITH(osMetaName, "STATISTICS_") ||
    9198         166 :                 STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
    9199         132 :                 STARTS_WITH(osMetaName, "missing_value") ||
    9200         474 :                 STARTS_WITH(osMetaName, "_FillValue") ||
    9201         104 :                 CSLFindString(papszIgnoreBand, osMetaName) != -1)
    9202         116 :                 continue;
    9203             :         }
    9204             : 
    9205             : #ifdef NCDF_DEBUG
    9206             :         CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
    9207             :                  pszValue);
    9208             : #endif
    9209         169 :         if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
    9210             :         {
    9211           0 :             CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
    9212             :                      nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
    9213             :         }
    9214             :     }
    9215             : 
    9216             :     // Set add_offset and scale_factor here if present.
    9217         182 :     if (poSrcBand && poDstBand)
    9218             :     {
    9219             : 
    9220         103 :         int bGotAddOffset = FALSE;
    9221         103 :         const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
    9222         103 :         int bGotScale = FALSE;
    9223         103 :         const double dfScale = poSrcBand->GetScale(&bGotScale);
    9224             : 
    9225         103 :         if (bGotAddOffset && dfAddOffset != 0.0)
    9226           6 :             poDstBand->SetOffset(dfAddOffset);
    9227         103 :         if (bGotScale && dfScale != 1.0)
    9228           1 :             poDstBand->SetScale(dfScale);
    9229             :     }
    9230         182 : }
    9231             : 
    9232             : /************************************************************************/
    9233             : /*                            CreateLL()                                */
    9234             : /*                                                                      */
    9235             : /*      Shared functionality between netCDFDataset::Create() and        */
    9236             : /*      netCDF::CreateCopy() for creating netcdf file based on a set of */
    9237             : /*      options and a configuration.                                    */
    9238             : /************************************************************************/
    9239             : 
    9240         232 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
    9241             :                                        int nYSize, int nBandsIn,
    9242             :                                        CSLConstList papszOptions)
    9243             : {
    9244         232 :     if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
    9245         137 :           (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
    9246             :     {
    9247           1 :         return nullptr;
    9248             :     }
    9249             : 
    9250         231 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9251             :     // GDALDataset own mutex.
    9252         231 :     netCDFDataset *poDS = new netCDFDataset();
    9253         231 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9254             : 
    9255         231 :     poDS->nRasterXSize = nXSize;
    9256         231 :     poDS->nRasterYSize = nYSize;
    9257         231 :     poDS->eAccess = GA_Update;
    9258         231 :     poDS->osFilename = pszFilename;
    9259             : 
    9260             :     // From gtiff driver, is this ok?
    9261             :     /*
    9262             :     poDS->nBlockXSize = nXSize;
    9263             :     poDS->nBlockYSize = 1;
    9264             :     poDS->nBlocksPerBand =
    9265             :         DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
    9266             :         * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
    9267             :         */
    9268             : 
    9269             :     // process options.
    9270         231 :     poDS->aosCreationOptions = CSLDuplicate(papszOptions);
    9271         231 :     if (!poDS->ProcessCreationOptions())
    9272             :     {
    9273          23 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9274             :         // deadlock with GDALDataset own
    9275             :         // mutex.
    9276          23 :         delete poDS;
    9277          23 :         CPLAcquireMutex(hNCMutex, 1000.0);
    9278          23 :         return nullptr;
    9279             :     }
    9280             : 
    9281         208 :     if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
    9282             :     {
    9283             :         VSIStatBuf sStat;
    9284           3 :         if (VSIStat(pszFilename, &sStat) == 0)
    9285             :         {
    9286           0 :             if (!VSI_ISDIR(sStat.st_mode))
    9287             :             {
    9288           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    9289             :                          "%s is an existing file, but not a directory",
    9290             :                          pszFilename);
    9291           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9292             :                 // deadlock with GDALDataset own
    9293             :                 // mutex.
    9294           0 :                 delete poDS;
    9295           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    9296           0 :                 return nullptr;
    9297             :             }
    9298             :         }
    9299           3 :         else if (VSIMkdir(pszFilename, 0755) != 0)
    9300             :         {
    9301           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
    9302             :                      pszFilename);
    9303           1 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9304             :             // deadlock with GDALDataset own mutex.
    9305           1 :             delete poDS;
    9306           1 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9307           1 :             return nullptr;
    9308             :         }
    9309             : 
    9310           2 :         return poDS;
    9311             :     }
    9312             :     // Create the dataset.
    9313         410 :     CPLString osFilenameForNCCreate(pszFilename);
    9314             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    9315             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    9316             :     {
    9317             :         char *pszTemp =
    9318             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    9319             :         osFilenameForNCCreate = pszTemp;
    9320             :         CPLFree(pszTemp);
    9321             :     }
    9322             : #endif
    9323             : 
    9324             : #if defined(_WIN32)
    9325             :     {
    9326             :         // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
    9327             :         // crashes
    9328             :         VSIStatBuf sStat;
    9329             :         const std::string osDirname =
    9330             :             CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
    9331             :         if (VSIStat(osDirname.c_str(), &sStat) != 0)
    9332             :         {
    9333             :             CPLError(CE_Failure, CPLE_OpenFailed,
    9334             :                      "Unable to create netCDF file %s: non existing output "
    9335             :                      "directory",
    9336             :                      pszFilename);
    9337             :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9338             :             // deadlock with GDALDataset own mutex.
    9339             :             delete poDS;
    9340             :             CPLAcquireMutex(hNCMutex, 1000.0);
    9341             :             return nullptr;
    9342             :         }
    9343             :     }
    9344             : #endif
    9345             : 
    9346             :     int status =
    9347         205 :         nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
    9348             : 
    9349             :     // Put into define mode.
    9350         205 :     poDS->SetDefineMode(true);
    9351             : 
    9352         205 :     if (status != NC_NOERR)
    9353             :     {
    9354          29 :         CPLError(CE_Failure, CPLE_OpenFailed,
    9355             :                  "Unable to create netCDF file %s (Error code %d): %s .",
    9356             :                  pszFilename, status, nc_strerror(status));
    9357          29 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    9358             :         // with GDALDataset own mutex.
    9359          29 :         delete poDS;
    9360          29 :         CPLAcquireMutex(hNCMutex, 1000.0);
    9361          29 :         return nullptr;
    9362             :     }
    9363             : 
    9364             :     // Define dimensions.
    9365         176 :     if (nXSize > 0 && nYSize > 0)
    9366             :     {
    9367         124 :         poDS->papszDimName.AddString(NCDF_DIMNAME_X);
    9368             :         status =
    9369         124 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
    9370         124 :         NCDF_ERR(status);
    9371         124 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9372             :                  poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
    9373             : 
    9374         124 :         poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
    9375             :         status =
    9376         124 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
    9377         124 :         NCDF_ERR(status);
    9378         124 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9379             :                  poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
    9380             :     }
    9381             : 
    9382         176 :     return poDS;
    9383             : }
    9384             : 
    9385             : /************************************************************************/
    9386             : /*                               Create()                               */
    9387             : /************************************************************************/
    9388             : 
    9389         149 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
    9390             :                                    int nYSize, int nBandsIn, GDALDataType eType,
    9391             :                                    CSLConstList papszOptions)
    9392             : {
    9393         149 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
    9394             :              pszFilename);
    9395             : 
    9396             :     const char *legacyCreationOp =
    9397         149 :         CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9398         298 :     std::string legacyCreationOp_s = std::string(legacyCreationOp);
    9399             : 
    9400             :     // Check legacy creation op FIRST
    9401             : 
    9402         149 :     bool legacyCreateMode = false;
    9403             : 
    9404         149 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
    9405             :     {
    9406          56 :         legacyCreateMode = true;
    9407             :     }
    9408          93 :     else if (legacyCreationOp_s == "CF_1.8")
    9409             :     {
    9410          76 :         legacyCreateMode = false;
    9411             :     }
    9412             : 
    9413          17 :     else if (legacyCreationOp_s == "WKT")
    9414             :     {
    9415          17 :         legacyCreateMode = true;
    9416             :     }
    9417             : 
    9418             :     else
    9419             :     {
    9420           0 :         CPLError(
    9421             :             CE_Failure, CPLE_NotSupported,
    9422             :             "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
    9423             :             legacyCreationOp_s.c_str());
    9424           0 :         return nullptr;
    9425             :     }
    9426             : 
    9427         298 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9428         284 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9429         135 :         (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
    9430             :          eType == GDT_Int64))
    9431             :     {
    9432          10 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9433          10 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9434             :     }
    9435             : 
    9436         298 :     CPLStringList aosBandNames;
    9437         149 :     if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
    9438             :     {
    9439             :         aosBandNames =
    9440           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9441             : 
    9442           2 :         if (aosBandNames.Count() != nBandsIn)
    9443             :         {
    9444           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9445             :                      "Attempted to create netCDF with %d bands but %d names "
    9446             :                      "provided in BAND_NAMES.",
    9447             :                      nBandsIn, aosBandNames.Count());
    9448             : 
    9449           1 :             return nullptr;
    9450             :         }
    9451             :     }
    9452             : 
    9453         296 :     CPLMutexHolderD(&hNCMutex);
    9454             : 
    9455         148 :     auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
    9456         148 :                                         aosOptions.List());
    9457             : 
    9458         148 :     if (!poDS)
    9459          42 :         return nullptr;
    9460             : 
    9461         106 :     if (!legacyCreateMode)
    9462             :     {
    9463          36 :         poDS->bSGSupport = true;
    9464          36 :         poDS->vcdf.enableFullVirtualMode();
    9465             :     }
    9466             : 
    9467             :     else
    9468             :     {
    9469          70 :         poDS->bSGSupport = false;
    9470             :     }
    9471             : 
    9472             :     // Should we write signed or unsigned byte?
    9473             :     // TODO should this only be done in Create()
    9474         106 :     poDS->bSignedData = true;
    9475         106 :     const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
    9476         106 :     if (eType == GDT_UInt8 && !EQUAL(pszValue, "SIGNEDBYTE"))
    9477          15 :         poDS->bSignedData = false;
    9478             : 
    9479             :     // Add Conventions, GDAL info and history.
    9480         106 :     if (poDS->cdfid >= 0)
    9481             :     {
    9482             :         const char *CF_Vector_Conv =
    9483         172 :             poDS->bSGSupport ||
    9484             :                     // Use of variable length strings require CF-1.8
    9485          68 :                     EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
    9486             :                 ? NCDF_CONVENTIONS_CF_V1_8
    9487         172 :                 : NCDF_CONVENTIONS_CF_V1_6;
    9488         104 :         poDS->bWriteGDALVersion = CPLTestBool(
    9489             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9490         104 :         poDS->bWriteGDALHistory = CPLTestBool(
    9491             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9492         104 :         NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
    9493         104 :                            poDS->bWriteGDALHistory, "", "Create",
    9494             :                            (nBandsIn == 0) ? CF_Vector_Conv
    9495             :                                            : GDAL_DEFAULT_NCDF_CONVENTIONS);
    9496             :     }
    9497             : 
    9498             :     // Define bands.
    9499         197 :     for (int iBand = 1; iBand <= nBandsIn; iBand++)
    9500             :     {
    9501             :         const char *pszBandName =
    9502          91 :             aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
    9503             : 
    9504          91 :         poDS->SetBand(iBand, new netCDFRasterBand(
    9505          91 :                                  netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
    9506          91 :                                  eType, iBand, poDS->bSignedData, pszBandName));
    9507             :     }
    9508             : 
    9509         106 :     CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
    9510             :     // Return same dataset.
    9511         106 :     return poDS;
    9512             : }
    9513             : 
    9514             : template <class T>
    9515         103 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
    9516             :                            int nXSize, int nYSize, GDALProgressFunc pfnProgress,
    9517             :                            void *pProgressData)
    9518             : {
    9519         103 :     const GDALDataType eDT = poSrcBand->GetRasterDataType();
    9520         103 :     T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
    9521         103 :     CPLErr eErr = patScanline ? CE_None : CE_Failure;
    9522             : 
    9523        6540 :     for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
    9524             :     {
    9525        6437 :         eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
    9526             :                                    nXSize, 1, eDT, 0, 0, nullptr);
    9527        6437 :         if (eErr != CE_None)
    9528             :         {
    9529           0 :             CPLDebug(
    9530             :                 "GDAL_netCDF",
    9531             :                 "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
    9532             :                 eErr);
    9533             :         }
    9534             :         else
    9535             :         {
    9536        6437 :             eErr =
    9537             :                 poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
    9538             :                                     nXSize, 1, eDT, 0, 0, nullptr);
    9539        6437 :             if (eErr != CE_None)
    9540           0 :                 CPLDebug("GDAL_netCDF",
    9541             :                          "NCDFCopyBand(), poDstBand->RasterIO() returned error "
    9542             :                          "code %d",
    9543             :                          eErr);
    9544             :         }
    9545             : 
    9546        6437 :         if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
    9547             :         {
    9548         377 :             if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
    9549             :             {
    9550           0 :                 eErr = CE_Failure;
    9551           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt,
    9552             :                          "User terminated CreateCopy()");
    9553             :             }
    9554             :         }
    9555             :     }
    9556             : 
    9557         103 :     CPLFree(patScanline);
    9558             : 
    9559         103 :     pfnProgress(1.0, nullptr, pProgressData);
    9560             : 
    9561         103 :     return eErr;
    9562             : }
    9563             : 
    9564             : /************************************************************************/
    9565             : /*                             CreateCopy()                             */
    9566             : /************************************************************************/
    9567             : 
    9568             : GDALDataset *
    9569         100 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
    9570             :                           CPL_UNUSED int bStrict, CSLConstList papszOptions,
    9571             :                           GDALProgressFunc pfnProgress, void *pProgressData)
    9572             : {
    9573         200 :     CPLMutexHolderD(&hNCMutex);
    9574             : 
    9575         100 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
    9576             :              pszFilename);
    9577             : 
    9578         100 :     if (poSrcDS->GetRootGroup())
    9579             :     {
    9580          12 :         auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
    9581          12 :         if (poDrv)
    9582             :         {
    9583          12 :             return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    9584             :                                             papszOptions, pfnProgress,
    9585          12 :                                             pProgressData);
    9586             :         }
    9587             :     }
    9588             : 
    9589          88 :     const int nBands = poSrcDS->GetRasterCount();
    9590          88 :     const int nXSize = poSrcDS->GetRasterXSize();
    9591          88 :     const int nYSize = poSrcDS->GetRasterYSize();
    9592          88 :     const char *pszWKT = poSrcDS->GetProjectionRef();
    9593             : 
    9594             :     // Check input bands for errors.
    9595          88 :     if (nBands == 0)
    9596             :     {
    9597           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    9598             :                  "NetCDF driver does not support "
    9599             :                  "source datasets with zero bands.");
    9600           1 :         return nullptr;
    9601             :     }
    9602             : 
    9603          87 :     GDALDataType eDT = GDT_Unknown;
    9604          87 :     GDALRasterBand *poSrcBand = nullptr;
    9605         203 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9606             :     {
    9607         120 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9608         120 :         eDT = poSrcBand->GetRasterDataType();
    9609         120 :         if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
    9610             :         {
    9611           4 :             CPLError(CE_Failure, CPLE_NotSupported,
    9612             :                      "NetCDF driver does not support source dataset with band "
    9613             :                      "of complex type.");
    9614           4 :             return nullptr;
    9615             :         }
    9616             :     }
    9617             : 
    9618         166 :     CPLStringList aosBandNames;
    9619          83 :     if (const char *pszBandNames =
    9620          83 :             CSLFetchNameValue(papszOptions, "BAND_NAMES"))
    9621             :     {
    9622             :         aosBandNames =
    9623           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9624             : 
    9625           2 :         if (aosBandNames.Count() != nBands)
    9626             :         {
    9627           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9628             :                      "Attempted to create netCDF with %d bands but %d names "
    9629             :                      "provided in BAND_NAMES.",
    9630             :                      nBands, aosBandNames.Count());
    9631             : 
    9632           1 :             return nullptr;
    9633             :         }
    9634             :     }
    9635             : 
    9636          82 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    9637           0 :         return nullptr;
    9638             : 
    9639             :     // Check for extra dimensions.
    9640          82 :     int nDim = 2;
    9641             :     CPLStringList aosExtraDimNames =
    9642         164 :         NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9643             : 
    9644             :     // Same as in Create().
    9645         164 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9646          82 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr)
    9647             :     {
    9648          74 :         if (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
    9649             :             eDT == GDT_Int64)
    9650             :         {
    9651           6 :             CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9652           6 :             aosOptions.SetNameValue("FORMAT", "NC4");
    9653             :         }
    9654          68 :         else if (!aosExtraDimNames.empty())
    9655             :         {
    9656          14 :             for (const auto &pszDimName : aosExtraDimNames)
    9657             :             {
    9658           9 :                 const auto [nDimSize, nDimType] =
    9659           9 :                     ReadExtraDimDef(poSrcDS, pszDimName);
    9660             :                 {
    9661           9 :                     if (nDimType > NC_DOUBLE)
    9662             :                     {
    9663           1 :                         CPLDebug("netCDF",
    9664             :                                  "Selecting FORMAT=NC4 due to data type of "
    9665             :                                  "extra dimension '%s'",
    9666             :                                  pszDimName);
    9667           1 :                         aosOptions.SetNameValue("FORMAT", "NC4");
    9668           1 :                         break;
    9669             :                     }
    9670             :                 }
    9671             :             }
    9672             :         }
    9673             :     }
    9674             : 
    9675          82 :     netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
    9676          82 :                                                   nBands, aosOptions.List());
    9677          82 :     if (!poDS)
    9678          12 :         return nullptr;
    9679             : 
    9680             :     // Copy global metadata.
    9681             :     // Add Conventions, GDAL info and history.
    9682          70 :     CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
    9683          70 :     const bool bWriteGDALVersion = CPLTestBool(
    9684             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9685          70 :     const bool bWriteGDALHistory = CPLTestBool(
    9686             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9687          70 :     NCDFAddGDALHistory(
    9688             :         poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
    9689          70 :         poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
    9690          70 :         poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
    9691             : 
    9692          70 :     pfnProgress(0.1, nullptr, pProgressData);
    9693             : 
    9694          70 :     if (!aosExtraDimNames.empty())
    9695             :     {
    9696           6 :         size_t nDimSizeTot = 1;
    9697             :         // first make sure dimensions lengths compatible with band count
    9698             :         // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
    9699          15 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9700             :         {
    9701           9 :             const auto [nDimSize, _] =
    9702           9 :                 ReadExtraDimDef(poSrcDS, aosExtraDimNames[i]);
    9703           9 :             nDimSizeTot *= nDimSize;
    9704             :         }
    9705           6 :         if (nDimSizeTot == (size_t)nBands)
    9706             :         {
    9707           6 :             nDim = 2 + aosExtraDimNames.size();
    9708             :         }
    9709             :         else
    9710             :         {
    9711             :             // if nBands != #bands computed raise a warning
    9712             :             // just issue a debug message, because it was probably intentional
    9713           0 :             CPLDebug("GDAL_netCDF",
    9714             :                      "Warning: Number of bands (%d) is not compatible with "
    9715             :                      "dimensions "
    9716             :                      "(total=%ld names=%s)",
    9717             :                      nBands, (long)nDimSizeTot,
    9718           0 :                      poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9719           0 :             aosExtraDimNames.clear();
    9720             :         }
    9721             :     }
    9722             : 
    9723          70 :     int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9724          70 :     int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9725             : 
    9726             :     nc_type nVarType;
    9727          70 :     int *panBandZLev = nullptr;
    9728          70 :     int *panDimVarIds = nullptr;
    9729             : 
    9730          70 :     if (nDim > 2)
    9731             :     {
    9732           6 :         panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9733           6 :         panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9734             : 
    9735             :         // Define all dims.
    9736          15 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9737             :         {
    9738           9 :             poDS->papszDimName.AddString(aosExtraDimNames[i]);
    9739             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9740           9 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9741             :                      aosExtraDimNames[i]);
    9742             :             const CPLStringList aosExtraDimValues =
    9743          18 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9744             :             const int nDimSize =
    9745           9 :                 aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
    9746             :             // nc_type is an enum in netcdf-3, needs casting.
    9747           0 :             nVarType = static_cast<nc_type>(
    9748           9 :                 aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
    9749           9 :             panBandZLev[i] = nDimSize;
    9750           9 :             panBandDimPos[i + 2] = i;  // Save Position of ZDim.
    9751             : 
    9752             :             // Define dim.
    9753           9 :             int status = nc_def_dim(poDS->cdfid, aosExtraDimNames[i], nDimSize,
    9754           9 :                                     &(panDimIds[i]));
    9755           9 :             NCDF_ERR(status);
    9756             : 
    9757             :             // Define dim var.
    9758           9 :             int anDim[1] = {panDimIds[i]};
    9759           9 :             status = nc_def_var(poDS->cdfid, aosExtraDimNames[i], nVarType, 1,
    9760           9 :                                 anDim, &(panDimVarIds[i]));
    9761           9 :             NCDF_ERR(status);
    9762             : 
    9763             :             // Add dim metadata, using global var# items.
    9764           9 :             snprintf(szTemp, sizeof(szTemp), "%s#", aosExtraDimNames[i]);
    9765           9 :             CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
    9766           9 :                          panDimVarIds[i], szTemp);
    9767             :         }
    9768             :     }
    9769             : 
    9770             :     // Copy GeoTransform and Projection.
    9771             : 
    9772             :     // Copy geolocation info.
    9773             :     CSLConstList papszGeolocationInfo =
    9774          70 :         poSrcDS->GetMetadata(GDAL_MDD_GEOLOCATION);
    9775          70 :     if (papszGeolocationInfo != nullptr)
    9776           6 :         poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo,
    9777             :                                           GDAL_MDD_GEOLOCATION);
    9778             : 
    9779             :     // Copy geotransform.
    9780          70 :     bool bGotGeoTransform = false;
    9781          70 :     GDALGeoTransform gt;
    9782          70 :     CPLErr eErr = poSrcDS->GetGeoTransform(gt);
    9783          70 :     if (eErr == CE_None)
    9784             :     {
    9785          52 :         poDS->SetGeoTransform(gt);
    9786             :         // Disable AddProjectionVars() from being called.
    9787          52 :         bGotGeoTransform = true;
    9788          52 :         poDS->m_bHasGeoTransform = false;
    9789             :     }
    9790             : 
    9791             :     // Copy projection.
    9792          70 :     void *pScaledProgress = nullptr;
    9793          70 :     if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
    9794             :     {
    9795          53 :         poDS->SetProjection(pszWKT ? pszWKT : "");
    9796             : 
    9797             :         // Now we can call AddProjectionVars() directly.
    9798          53 :         poDS->m_bHasGeoTransform = bGotGeoTransform;
    9799          53 :         poDS->AddProjectionVars(true, nullptr, nullptr);
    9800             :         pScaledProgress =
    9801          53 :             GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
    9802          53 :         poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
    9803          53 :         GDALDestroyScaledProgress(pScaledProgress);
    9804             :     }
    9805             :     else
    9806             :     {
    9807          17 :         poDS->bBottomUp =
    9808          17 :             CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
    9809          17 :         if (papszGeolocationInfo)
    9810             :         {
    9811           4 :             poDS->AddProjectionVars(true, nullptr, nullptr);
    9812           4 :             poDS->AddProjectionVars(false, nullptr, nullptr);
    9813             :         }
    9814             :     }
    9815             : 
    9816             :     // Save X,Y dim positions.
    9817          70 :     panDimIds[nDim - 1] = poDS->nXDimID;
    9818          70 :     panBandDimPos[0] = nDim - 1;
    9819          70 :     panDimIds[nDim - 2] = poDS->nYDimID;
    9820          70 :     panBandDimPos[1] = nDim - 2;
    9821             : 
    9822             :     // Write extra dim values - after projection for optimization.
    9823          70 :     if (nDim > 2)
    9824             :     {
    9825             :         // Make sure we are in data mode.
    9826           6 :         poDS->SetDefineMode(false);
    9827          15 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9828             :         {
    9829             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9830           9 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
    9831             :                      aosExtraDimNames[i]);
    9832           9 :             if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
    9833             :             {
    9834           9 :                 NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
    9835           9 :                              poSrcDS->GetMetadataItem(szTemp));
    9836             :             }
    9837             :         }
    9838             :     }
    9839             : 
    9840          70 :     pfnProgress(0.25, nullptr, pProgressData);
    9841             : 
    9842             :     // Define Bands.
    9843          70 :     netCDFRasterBand *poBand = nullptr;
    9844          70 :     int nBandID = -1;
    9845             : 
    9846         173 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9847             :     {
    9848         103 :         CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
    9849             :                  nBands, nDim);
    9850             : 
    9851         103 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9852         103 :         eDT = poSrcBand->GetRasterDataType();
    9853             : 
    9854             :         // Get var name from NETCDF_VARNAME.
    9855             :         const char *pszNETCDF_VARNAME =
    9856         103 :             poSrcBand->GetMetadataItem("NETCDF_VARNAME");
    9857             :         char szBandName[NC_MAX_NAME + 1];
    9858         103 :         if (!aosBandNames.empty())
    9859             :         {
    9860           2 :             snprintf(szBandName, sizeof(szBandName), "%s",
    9861             :                      aosBandNames[iBand - 1]);
    9862             :         }
    9863         101 :         else if (pszNETCDF_VARNAME)
    9864             :         {
    9865          38 :             if (nBands > 1 && aosExtraDimNames.empty())
    9866           0 :                 snprintf(szBandName, sizeof(szBandName), "%s%d",
    9867             :                          pszNETCDF_VARNAME, iBand);
    9868             :             else
    9869          38 :                 snprintf(szBandName, sizeof(szBandName), "%s",
    9870             :                          pszNETCDF_VARNAME);
    9871             :         }
    9872             :         else
    9873             :         {
    9874          63 :             szBandName[0] = '\0';
    9875             :         }
    9876             : 
    9877             :         // Get long_name from <var>#long_name.
    9878         103 :         const char *pszLongName = "";
    9879         103 :         if (pszNETCDF_VARNAME)
    9880             :         {
    9881             :             pszLongName =
    9882          76 :                 poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
    9883          38 :                                              .append("#")
    9884          38 :                                              .append(CF_LNG_NAME)
    9885          38 :                                              .c_str());
    9886          38 :             if (!pszLongName)
    9887          25 :                 pszLongName = "";
    9888             :         }
    9889             : 
    9890         103 :         constexpr bool bSignedData = false;
    9891             : 
    9892         103 :         if (nDim > 2)
    9893          28 :             poBand = new netCDFRasterBand(
    9894          28 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9895             :                 bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
    9896          28 :                 panBandZLev, panBandDimPos, panDimIds);
    9897             :         else
    9898          75 :             poBand = new netCDFRasterBand(
    9899          75 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9900          75 :                 bSignedData, szBandName, pszLongName);
    9901             : 
    9902         103 :         poDS->SetBand(iBand, poBand);
    9903             : 
    9904             :         // Set nodata value, if any.
    9905         103 :         GDALCopyNoDataValue(poBand, poSrcBand);
    9906             : 
    9907             :         // Copy Metadata for band.
    9908         103 :         CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
    9909             :                      poDS->cdfid, poBand->nZId);
    9910             : 
    9911             :         // If more than 2D pass the first band's netcdf var ID to subsequent
    9912             :         // bands.
    9913         103 :         if (nDim > 2)
    9914          28 :             nBandID = poBand->nZId;
    9915             :     }
    9916             : 
    9917             :     // Write projection variable to band variable.
    9918          70 :     poDS->AddGridMappingRef();
    9919             : 
    9920          70 :     pfnProgress(0.5, nullptr, pProgressData);
    9921             : 
    9922             :     // Write bands.
    9923             : 
    9924             :     // Make sure we are in data mode.
    9925          70 :     poDS->SetDefineMode(false);
    9926             : 
    9927          70 :     double dfTemp = 0.5;
    9928             : 
    9929          70 :     eErr = CE_None;
    9930             : 
    9931         173 :     for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
    9932             :     {
    9933         103 :         const double dfTemp2 = dfTemp + 0.4 / nBands;
    9934         103 :         pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
    9935             :                                                    pProgressData);
    9936         103 :         dfTemp = dfTemp2;
    9937             : 
    9938         103 :         CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
    9939             : 
    9940         103 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9941         103 :         eDT = poSrcBand->GetRasterDataType();
    9942             : 
    9943         103 :         GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
    9944             : 
    9945             :         // Copy band data.
    9946         103 :         if (eDT == GDT_UInt8)
    9947             :         {
    9948          61 :             CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
    9949          61 :             eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
    9950             :                                        GDALScaledProgress, pScaledProgress);
    9951             :         }
    9952          42 :         else if (eDT == GDT_Int8)
    9953             :         {
    9954           1 :             CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
    9955           1 :             eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
    9956             :                                        GDALScaledProgress, pScaledProgress);
    9957             :         }
    9958          41 :         else if (eDT == GDT_UInt16)
    9959             :         {
    9960           2 :             CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
    9961           2 :             eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9962             :                                         GDALScaledProgress, pScaledProgress);
    9963             :         }
    9964          39 :         else if (eDT == GDT_Int16)
    9965             :         {
    9966           5 :             CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
    9967           5 :             eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9968             :                                          GDALScaledProgress, pScaledProgress);
    9969             :         }
    9970          34 :         else if (eDT == GDT_UInt32)
    9971             :         {
    9972           2 :             CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
    9973           2 :             eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9974             :                                          GDALScaledProgress, pScaledProgress);
    9975             :         }
    9976          32 :         else if (eDT == GDT_Int32)
    9977             :         {
    9978          18 :             CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
    9979          18 :             eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9980             :                                         GDALScaledProgress, pScaledProgress);
    9981             :         }
    9982          14 :         else if (eDT == GDT_UInt64)
    9983             :         {
    9984           2 :             CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
    9985           2 :             eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
    9986             :                                                nYSize, GDALScaledProgress,
    9987             :                                                pScaledProgress);
    9988             :         }
    9989          12 :         else if (eDT == GDT_Int64)
    9990             :         {
    9991           2 :             CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
    9992             :             eErr =
    9993           2 :                 NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
    9994             :                                            GDALScaledProgress, pScaledProgress);
    9995             :         }
    9996          10 :         else if (eDT == GDT_Float32)
    9997             :         {
    9998           8 :             CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
    9999           8 :             eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
   10000             :                                        GDALScaledProgress, pScaledProgress);
   10001             :         }
   10002           2 :         else if (eDT == GDT_Float64)
   10003             :         {
   10004           2 :             CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
   10005           2 :             eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
   10006             :                                         GDALScaledProgress, pScaledProgress);
   10007             :         }
   10008             :         else
   10009             :         {
   10010           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10011             :                      "The NetCDF driver does not support GDAL data type %d",
   10012             :                      eDT);
   10013             :         }
   10014             : 
   10015         103 :         GDALDestroyScaledProgress(pScaledProgress);
   10016             :     }
   10017             : 
   10018          70 :     delete (poDS);
   10019             : 
   10020          70 :     CPLFree(panDimIds);
   10021          70 :     CPLFree(panBandDimPos);
   10022          70 :     CPLFree(panBandZLev);
   10023          70 :     CPLFree(panDimVarIds);
   10024             : 
   10025          70 :     if (eErr != CE_None)
   10026           0 :         return nullptr;
   10027             : 
   10028          70 :     pfnProgress(0.95, nullptr, pProgressData);
   10029             : 
   10030             :     // Re-open dataset so we can return it.
   10031         140 :     CPLStringList aosOpenOptions;
   10032          70 :     aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
   10033          70 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
   10034          70 :     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
   10035          70 :     oOpenInfo.papszOpenOptions = aosOpenOptions.List();
   10036          70 :     auto poRetDS = Open(&oOpenInfo);
   10037             : 
   10038             :     // PAM cloning is disabled. See bug #4244.
   10039             :     // if( poDS )
   10040             :     //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
   10041             : 
   10042          70 :     pfnProgress(1.0, nullptr, pProgressData);
   10043             : 
   10044          70 :     return poRetDS;
   10045             : }
   10046             : 
   10047             : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
   10048             : // May not be known when Create() is called, see AddProjectionVars().
   10049         345 : bool netCDFDataset::ProcessCreationOptions()
   10050             : {
   10051         345 :     const char *pszConfig = aosCreationOptions.FetchNameValue("CONFIG_FILE");
   10052         345 :     if (pszConfig != nullptr)
   10053             :     {
   10054          26 :         if (oWriterConfig.Parse(pszConfig))
   10055             :         {
   10056             :             // Override dataset creation options from the config file
   10057           2 :             for (const auto &[osName, osValue] :
   10058           5 :                  oWriterConfig.m_oDatasetCreationOptions)
   10059             :             {
   10060           1 :                 aosCreationOptions.SetNameValue(osName, osValue);
   10061             :             }
   10062             :         }
   10063             :         else
   10064             :         {
   10065          23 :             return false;
   10066             :         }
   10067             :     }
   10068             : 
   10069             :     // File format.
   10070         322 :     eFormat = NCDF_FORMAT_NC;
   10071         322 :     const char *pszValue = aosCreationOptions.FetchNameValue("FORMAT");
   10072         322 :     if (pszValue != nullptr)
   10073             :     {
   10074         151 :         if (EQUAL(pszValue, "NC"))
   10075             :         {
   10076           3 :             eFormat = NCDF_FORMAT_NC;
   10077             :         }
   10078             : #ifdef NETCDF_HAS_NC2
   10079         148 :         else if (EQUAL(pszValue, "NC2"))
   10080             :         {
   10081           1 :             eFormat = NCDF_FORMAT_NC2;
   10082             :         }
   10083             : #endif
   10084         147 :         else if (EQUAL(pszValue, "NC4"))
   10085             :         {
   10086         143 :             eFormat = NCDF_FORMAT_NC4;
   10087             :         }
   10088           4 :         else if (EQUAL(pszValue, "NC4C"))
   10089             :         {
   10090           4 :             eFormat = NCDF_FORMAT_NC4C;
   10091             :         }
   10092             :         else
   10093             :         {
   10094           0 :             CPLError(CE_Warning, CPLE_NotSupported,
   10095             :                      "FORMAT=%s in not supported, using the default NC format.",
   10096             :                      pszValue);
   10097             :         }
   10098             :     }
   10099             : 
   10100             :     // COMPRESS option.
   10101         322 :     pszValue = aosCreationOptions.FetchNameValue("COMPRESS");
   10102         322 :     if (pszValue != nullptr)
   10103             :     {
   10104           3 :         if (EQUAL(pszValue, "NONE"))
   10105             :         {
   10106           1 :             eCompress = NCDF_COMPRESS_NONE;
   10107             :         }
   10108           2 :         else if (EQUAL(pszValue, "DEFLATE"))
   10109             :         {
   10110           2 :             eCompress = NCDF_COMPRESS_DEFLATE;
   10111           2 :             if (!((eFormat == NCDF_FORMAT_NC4) ||
   10112           2 :                   (eFormat == NCDF_FORMAT_NC4C)))
   10113             :             {
   10114           1 :                 CPLError(CE_Warning, CPLE_IllegalArg,
   10115             :                          "NOTICE: Format set to NC4C because compression is "
   10116             :                          "set to DEFLATE.");
   10117           1 :                 eFormat = NCDF_FORMAT_NC4C;
   10118             :             }
   10119             :         }
   10120             :         else
   10121             :         {
   10122           0 :             CPLError(CE_Warning, CPLE_NotSupported,
   10123             :                      "COMPRESS=%s is not supported.", pszValue);
   10124             :         }
   10125             :     }
   10126             : 
   10127             :     // ZLEVEL option.
   10128         322 :     pszValue = aosCreationOptions.FetchNameValue("ZLEVEL");
   10129         322 :     if (pszValue != nullptr)
   10130             :     {
   10131           1 :         nZLevel = atoi(pszValue);
   10132           1 :         if (!(nZLevel >= 1 && nZLevel <= 9))
   10133             :         {
   10134           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10135             :                      "ZLEVEL=%s value not recognised, ignoring.", pszValue);
   10136           0 :             nZLevel = NCDF_DEFLATE_LEVEL;
   10137             :         }
   10138             :     }
   10139             : 
   10140             :     // CHUNKING option.
   10141         322 :     bChunking = aosCreationOptions.FetchBool("CHUNKING", true);
   10142             : 
   10143             :     // MULTIPLE_LAYERS option.
   10144             :     const char *pszMultipleLayerBehavior =
   10145         322 :         aosCreationOptions.FetchNameValueDef("MULTIPLE_LAYERS", "NO");
   10146             :     const char *pszGeometryEnc =
   10147         322 :         aosCreationOptions.FetchNameValueDef("GEOMETRY_ENCODING", "CF_1.8");
   10148         322 :     if (EQUAL(pszMultipleLayerBehavior, "NO") ||
   10149           4 :         EQUAL(pszGeometryEnc, "CF_1.8"))
   10150             :     {
   10151         318 :         eMultipleLayerBehavior = SINGLE_LAYER;
   10152             :     }
   10153           4 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
   10154             :     {
   10155           3 :         eMultipleLayerBehavior = SEPARATE_FILES;
   10156             :     }
   10157           1 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
   10158             :     {
   10159           1 :         if (eFormat == NCDF_FORMAT_NC4)
   10160             :         {
   10161           1 :             eMultipleLayerBehavior = SEPARATE_GROUPS;
   10162             :         }
   10163             :         else
   10164             :         {
   10165           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10166             :                      "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
   10167             :                      pszMultipleLayerBehavior);
   10168             :         }
   10169             :     }
   10170             :     else
   10171             :     {
   10172           0 :         CPLError(CE_Warning, CPLE_IllegalArg,
   10173             :                  "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
   10174             :     }
   10175             : 
   10176             :     // Set nCreateMode based on eFormat.
   10177         322 :     switch (eFormat)
   10178             :     {
   10179             : #ifdef NETCDF_HAS_NC2
   10180           1 :         case NCDF_FORMAT_NC2:
   10181           1 :             nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
   10182           1 :             break;
   10183             : #endif
   10184         143 :         case NCDF_FORMAT_NC4:
   10185         143 :             nCreateMode = NC_CLOBBER | NC_NETCDF4;
   10186         143 :             break;
   10187           5 :         case NCDF_FORMAT_NC4C:
   10188           5 :             nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
   10189           5 :             break;
   10190         173 :         case NCDF_FORMAT_NC:
   10191             :         default:
   10192         173 :             nCreateMode = NC_CLOBBER;
   10193         173 :             break;
   10194             :     }
   10195             : 
   10196         322 :     CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
   10197         322 :              eFormat, eCompress, nZLevel);
   10198             : 
   10199         322 :     return true;
   10200             : }
   10201             : 
   10202         296 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) const
   10203             : {
   10204         296 :     if (eCompress == NCDF_COMPRESS_DEFLATE)
   10205             :     {
   10206             :         // Must set chunk size to avoid huge performance hit (set
   10207             :         // bChunkingArg=TRUE)
   10208             :         // perhaps another solution it to change the chunk cache?
   10209             :         // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
   10210             :         // TODO: make sure this is okay.
   10211           2 :         CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
   10212           2 :                  static_cast<int>(bChunkingArg), nZLevel);
   10213             : 
   10214           2 :         int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
   10215           2 :         NCDF_ERR(status);
   10216             : 
   10217           2 :         if (status == NC_NOERR && bChunkingArg && bChunking)
   10218             :         {
   10219             :             // set chunking to be 1 for all dims, except X dim
   10220             :             // size_t chunksize[] = { 1, (size_t)nRasterXSize };
   10221             :             size_t chunksize[MAX_NC_DIMS];
   10222             :             int nd;
   10223           2 :             nc_inq_varndims(cdfid, nVarId, &nd);
   10224           2 :             chunksize[0] = (size_t)1;
   10225           2 :             chunksize[1] = (size_t)1;
   10226           2 :             for (int i = 2; i < nd; i++)
   10227           0 :                 chunksize[i] = (size_t)1;
   10228           2 :             chunksize[nd - 1] = (size_t)nRasterXSize;
   10229             : 
   10230             :             // Config options just for testing purposes
   10231             :             const char *pszBlockXSize =
   10232           2 :                 CPLGetConfigOption("BLOCKXSIZE", nullptr);
   10233           2 :             if (pszBlockXSize)
   10234           0 :                 chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
   10235             : 
   10236             :             const char *pszBlockYSize =
   10237           2 :                 CPLGetConfigOption("BLOCKYSIZE", nullptr);
   10238           2 :             if (nd >= 2 && pszBlockYSize)
   10239           0 :                 chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
   10240             : 
   10241           2 :             CPLDebug("GDAL_netCDF",
   10242             :                      "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
   10243           2 :                      (long)chunksize[0], (long)chunksize[1],
   10244           2 :                      (long)chunksize[nd - 1], nd);
   10245             : #ifdef NCDF_DEBUG
   10246             :             for (int i = 0; i < nd; i++)
   10247             :                 CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
   10248             :                          chunksize[i]);
   10249             : #endif
   10250             : 
   10251           2 :             status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
   10252           2 :             NCDF_ERR(status);
   10253             :         }
   10254             :         else
   10255             :         {
   10256           0 :             CPLDebug("GDAL_netCDF", "chunksize not set");
   10257             :         }
   10258           2 :         return status;
   10259             :     }
   10260         294 :     return NC_NOERR;
   10261             : }
   10262             : 
   10263             : /************************************************************************/
   10264             : /*                          NCDFUnloadDriver()                          */
   10265             : /************************************************************************/
   10266             : 
   10267          10 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
   10268             : {
   10269          10 :     if (hNCMutex != nullptr)
   10270           6 :         CPLDestroyMutex(hNCMutex);
   10271          10 :     hNCMutex = nullptr;
   10272          10 : }
   10273             : 
   10274             : /************************************************************************/
   10275             : /*                        GDALRegister_netCDF()                         */
   10276             : /************************************************************************/
   10277             : 
   10278             : class GDALnetCDFDriver final : public GDALDriver
   10279             : {
   10280             :   public:
   10281          20 :     GDALnetCDFDriver() = default;
   10282             : 
   10283             :     const char *GetMetadataItem(const char *pszName,
   10284             :                                 const char *pszDomain) override;
   10285             : 
   10286         122 :     CSLConstList GetMetadata(const char *pszDomain) override
   10287             :     {
   10288         244 :         std::lock_guard oLock(m_oMutex);
   10289         122 :         InitializeDCAPVirtualIO();
   10290         244 :         return GDALDriver::GetMetadata(pszDomain);
   10291             :     }
   10292             : 
   10293             :   private:
   10294             :     std::recursive_mutex m_oMutex{};
   10295             :     bool m_bInitialized = false;
   10296             : 
   10297         135 :     void InitializeDCAPVirtualIO()
   10298             :     {
   10299         135 :         if (!m_bInitialized)
   10300             :         {
   10301          11 :             m_bInitialized = true;
   10302             : 
   10303             : #ifdef ENABLE_UFFD
   10304          11 :             if (CPLIsUserFaultMappingSupported())
   10305             :             {
   10306          11 :                 SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
   10307             :             }
   10308             : #endif
   10309             :         }
   10310         135 :     }
   10311             : };
   10312             : 
   10313        1454 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
   10314             :                                               const char *pszDomain)
   10315             : {
   10316        2908 :     std::lock_guard oLock(m_oMutex);
   10317        1454 :     if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
   10318             :     {
   10319          13 :         InitializeDCAPVirtualIO();
   10320             :     }
   10321        2908 :     return GDALDriver::GetMetadataItem(pszName, pszDomain);
   10322             : }
   10323             : 
   10324          20 : void GDALRegister_netCDF()
   10325             : 
   10326             : {
   10327          20 :     if (!GDAL_CHECK_VERSION("netCDF driver"))
   10328           0 :         return;
   10329             : 
   10330          20 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
   10331           0 :         return;
   10332             : 
   10333          20 :     GDALDriver *poDriver = new GDALnetCDFDriver();
   10334          20 :     netCDFDriverSetCommonMetadata(poDriver);
   10335             : 
   10336          20 :     poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
   10337          20 :                               GDAL_DEFAULT_NCDF_CONVENTIONS);
   10338          20 :     poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
   10339             : 
   10340             :     // Set pfns and register driver.
   10341          20 :     poDriver->pfnOpen = netCDFDataset::Open;
   10342          20 :     poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
   10343          20 :     poDriver->pfnCreate = netCDFDataset::Create;
   10344          20 :     poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
   10345          20 :     poDriver->pfnUnloadDriver = NCDFUnloadDriver;
   10346             : 
   10347          20 :     GetGDALDriverManager()->RegisterDriver(poDriver);
   10348             : }
   10349             : 
   10350             : /************************************************************************/
   10351             : /*                            New functions                             */
   10352             : /************************************************************************/
   10353             : 
   10354             : /* Test for GDAL version string >= target */
   10355         294 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
   10356             : {
   10357             : 
   10358             :     // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
   10359         294 :     if (pszVersion == nullptr || EQUAL(pszVersion, ""))
   10360           0 :         return false;
   10361         294 :     else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
   10362           0 :         return false;
   10363             :     // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
   10364         294 :     else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
   10365           0 :         return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
   10366         294 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
   10367           2 :         return nTarget <= 1900;
   10368         292 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
   10369           0 :         return nTarget <= 1800;
   10370             : 
   10371         292 :     const CPLStringList aosTokens(CSLTokenizeString2(pszVersion + 5, ".", 0));
   10372             : 
   10373         292 :     int nVersions[] = {0, 0, 0, 0};
   10374        1168 :     for (int iToken = 0; iToken < std::min(4, aosTokens.size()); iToken++)
   10375             :     {
   10376         876 :         nVersions[iToken] = atoi(aosTokens[iToken]);
   10377         876 :         if (nVersions[iToken] < 0)
   10378           0 :             nVersions[iToken] = 0;
   10379         876 :         else if (nVersions[iToken] > 99)
   10380           0 :             nVersions[iToken] = 99;
   10381             :     }
   10382             : 
   10383         292 :     int nVersion = 0;
   10384         292 :     if (nVersions[0] > 1 || nVersions[1] >= 10)
   10385         292 :         nVersion =
   10386         292 :             GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
   10387             :     else
   10388           0 :         nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
   10389           0 :                    nVersions[2] * 10 + nVersions[3];
   10390             : 
   10391         292 :     return nTarget <= nVersion;
   10392             : }
   10393             : 
   10394             : // Add Conventions, GDAL version and history.
   10395         178 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
   10396             :                                bool bWriteGDALVersion, bool bWriteGDALHistory,
   10397             :                                const char *pszOldHist,
   10398             :                                const char *pszFunctionName,
   10399             :                                const char *pszCFVersion)
   10400             : {
   10401         178 :     if (pszCFVersion == nullptr)
   10402             :     {
   10403          48 :         pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
   10404             :     }
   10405         178 :     int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
   10406             :                                  strlen(pszCFVersion), pszCFVersion);
   10407         178 :     NCDF_ERR(status);
   10408             : 
   10409         178 :     if (bWriteGDALVersion)
   10410             :     {
   10411         176 :         const char *pszNCDF_GDAL = GDALVersionInfo("--version");
   10412         176 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
   10413             :                                  strlen(pszNCDF_GDAL), pszNCDF_GDAL);
   10414         176 :         NCDF_ERR(status);
   10415             :     }
   10416             : 
   10417         178 :     if (bWriteGDALHistory)
   10418             :     {
   10419             :         // Add history.
   10420         352 :         CPLString osTmp;
   10421             : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
   10422             :         if (!EQUAL(GDALGetCmdLine(), ""))
   10423             :             osTmp = GDALGetCmdLine();
   10424             :         else
   10425             :             osTmp =
   10426             :                 CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10427             : #else
   10428         176 :         osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10429             : #endif
   10430             : 
   10431         176 :         NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
   10432             :     }
   10433           2 :     else if (pszOldHist != nullptr)
   10434             :     {
   10435           0 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10436             :                                  strlen(pszOldHist), pszOldHist);
   10437           0 :         NCDF_ERR(status);
   10438             :     }
   10439         178 : }
   10440             : 
   10441             : // Code taken from cdo and libcdi, used for writing the history attribute.
   10442             : 
   10443             : // void cdoDefHistory(int fileID, char *histstring)
   10444         176 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
   10445             :                            const char *pszOldHist)
   10446             : {
   10447             :     // Check pszOldHist - as if there was no previous history, it will be
   10448             :     // a null pointer - if so set as empty.
   10449         176 :     if (nullptr == pszOldHist)
   10450             :     {
   10451          59 :         pszOldHist = "";
   10452             :     }
   10453             : 
   10454             :     char strtime[32];
   10455         176 :     strtime[0] = '\0';
   10456             : 
   10457         176 :     time_t tp = time(nullptr);
   10458         176 :     if (tp != -1)
   10459             :     {
   10460             :         struct tm ltime;
   10461         176 :         VSILocalTime(&tp, &ltime);
   10462         176 :         (void)strftime(strtime, sizeof(strtime),
   10463             :                        "%a %b %d %H:%M:%S %Y: ", &ltime);
   10464             :     }
   10465             : 
   10466             :     // status = nc_get_att_text(fpImage, NC_GLOBAL,
   10467             :     //                           "history", pszOldHist);
   10468             :     // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
   10469             : 
   10470         176 :     size_t nNewHistSize =
   10471         176 :         strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
   10472             :     char *pszNewHist =
   10473         176 :         static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
   10474             : 
   10475         176 :     strcpy(pszNewHist, strtime);
   10476         176 :     strcat(pszNewHist, pszAddHist);
   10477             : 
   10478             :     // int disableHistory = FALSE;
   10479             :     // if( !disableHistory )
   10480             :     {
   10481         176 :         if (!EQUAL(pszOldHist, ""))
   10482           9 :             strcat(pszNewHist, "\n");
   10483         176 :         strcat(pszNewHist, pszOldHist);
   10484             :     }
   10485             : 
   10486         176 :     const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10487             :                                        strlen(pszNewHist), pszNewHist);
   10488         176 :     NCDF_ERR(status);
   10489             : 
   10490         176 :     CPLFree(pszNewHist);
   10491         176 : }
   10492             : 
   10493        7401 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
   10494             :                              size_t *nDestSize)
   10495             : {
   10496             :     /* Reallocate the data string until the content fits */
   10497        7401 :     while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
   10498             :     {
   10499         495 :         (*nDestSize) *= 2;
   10500         495 :         *ppszDest = static_cast<char *>(
   10501         495 :             CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
   10502             : #ifdef NCDF_DEBUG
   10503             :         CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
   10504             :                  (*nDestSize) / 2, *nDestSize);
   10505             : #endif
   10506             :     }
   10507        6906 :     strcat(*ppszDest, pszSrc);
   10508             : 
   10509        6906 :     return CE_None;
   10510             : }
   10511             : 
   10512             : /* helper function for NCDFGetAttr() */
   10513             : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
   10514             : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
   10515             : /* *ppszValue is the responsibility of the caller and must be freed */
   10516       79168 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
   10517             :                            double *pdfValue, char **ppszValue)
   10518             : {
   10519       79168 :     nc_type nAttrType = NC_NAT;
   10520       79168 :     size_t nAttrLen = 0;
   10521             : 
   10522       79168 :     if (ppszValue)
   10523       77811 :         *ppszValue = nullptr;
   10524             : 
   10525       79168 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10526       79168 :     if (status != NC_NOERR)
   10527       42147 :         return CE_Failure;
   10528             : 
   10529             : #ifdef NCDF_DEBUG
   10530             :     CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
   10531             :              nAttrLen, nAttrType);
   10532             : #endif
   10533       37021 :     if (nAttrLen == 0 && nAttrType != NC_CHAR)
   10534           1 :         return CE_Failure;
   10535             : 
   10536             :     /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
   10537       37020 :     size_t nAttrValueSize = nAttrLen + 1;
   10538       37020 :     if (nAttrType != NC_CHAR && nAttrValueSize < 10)
   10539        4119 :         nAttrValueSize = 10;
   10540       37020 :     if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
   10541        1981 :         nAttrValueSize = 20;
   10542       37020 :     if (nAttrType == NC_INT64 && nAttrValueSize < 20)
   10543          49 :         nAttrValueSize = 22;
   10544             :     char *pszAttrValue =
   10545       37020 :         static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
   10546       37020 :     *pszAttrValue = '\0';
   10547             : 
   10548       37020 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10549         680 :         NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
   10550             : 
   10551       37020 :     double dfValue = 0.0;
   10552       37020 :     size_t m = 0;
   10553             :     char szTemp[256];
   10554       37020 :     bool bSetDoubleFromStr = false;
   10555             : 
   10556       37020 :     switch (nAttrType)
   10557             :     {
   10558       32899 :         case NC_CHAR:
   10559       32899 :             CPL_IGNORE_RET_VAL(
   10560       32899 :                 nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
   10561       32899 :             pszAttrValue[nAttrLen] = '\0';
   10562       32899 :             bSetDoubleFromStr = true;
   10563       32899 :             dfValue = 0.0;
   10564       32899 :             break;
   10565          94 :         case NC_BYTE:
   10566             :         {
   10567             :             signed char *pscTemp = static_cast<signed char *>(
   10568          94 :                 CPLCalloc(nAttrLen, sizeof(signed char)));
   10569          94 :             nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
   10570          94 :             dfValue = static_cast<double>(pscTemp[0]);
   10571          94 :             if (nAttrLen > 1)
   10572             :             {
   10573          24 :                 for (m = 0; m < nAttrLen - 1; m++)
   10574             :                 {
   10575          13 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10576          13 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10577             :                 }
   10578             :             }
   10579          94 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10580          94 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10581          94 :             CPLFree(pscTemp);
   10582          94 :             break;
   10583             :         }
   10584         571 :         case NC_SHORT:
   10585             :         {
   10586             :             short *psTemp =
   10587         571 :                 static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
   10588         571 :             nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
   10589         571 :             dfValue = static_cast<double>(psTemp[0]);
   10590         571 :             if (nAttrLen > 1)
   10591             :             {
   10592         922 :                 for (m = 0; m < nAttrLen - 1; m++)
   10593             :                 {
   10594         461 :                     snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10595         461 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10596             :                 }
   10597             :             }
   10598         571 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10599         571 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10600         571 :             CPLFree(psTemp);
   10601         571 :             break;
   10602             :         }
   10603         620 :         case NC_INT:
   10604             :         {
   10605         620 :             int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10606         620 :             nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
   10607         620 :             dfValue = static_cast<double>(pnTemp[0]);
   10608         620 :             if (nAttrLen > 1)
   10609             :             {
   10610         218 :                 for (m = 0; m < nAttrLen - 1; m++)
   10611             :                 {
   10612         139 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10613         139 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10614             :                 }
   10615             :             }
   10616         620 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10617         620 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10618         620 :             CPLFree(pnTemp);
   10619         620 :             break;
   10620             :         }
   10621         419 :         case NC_FLOAT:
   10622             :         {
   10623             :             float *pfTemp =
   10624         419 :                 static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10625         419 :             nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
   10626         419 :             dfValue = static_cast<double>(pfTemp[0]);
   10627         419 :             if (nAttrLen > 1)
   10628             :             {
   10629          60 :                 for (m = 0; m < nAttrLen - 1; m++)
   10630             :                 {
   10631          30 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10632          30 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10633             :                 }
   10634             :             }
   10635         419 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10636         419 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10637         419 :             CPLFree(pfTemp);
   10638         419 :             break;
   10639             :         }
   10640        1981 :         case NC_DOUBLE:
   10641             :         {
   10642             :             double *pdfTemp =
   10643        1981 :                 static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10644        1981 :             nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
   10645        1981 :             dfValue = pdfTemp[0];
   10646        1981 :             if (nAttrLen > 1)
   10647             :             {
   10648         168 :                 for (m = 0; m < nAttrLen - 1; m++)
   10649             :                 {
   10650          91 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10651          91 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10652             :                 }
   10653             :             }
   10654        1981 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10655        1981 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10656        1981 :             CPLFree(pdfTemp);
   10657        1981 :             break;
   10658             :         }
   10659         264 :         case NC_STRING:
   10660             :         {
   10661             :             char **ppszTemp =
   10662         264 :                 static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
   10663         264 :             nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
   10664         264 :             bSetDoubleFromStr = true;
   10665         264 :             dfValue = 0.0;
   10666         264 :             if (nAttrLen > 1)
   10667             :             {
   10668          19 :                 for (m = 0; m < nAttrLen - 1; m++)
   10669             :                 {
   10670          12 :                     NCDFSafeStrcat(&pszAttrValue,
   10671          12 :                                    ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10672             :                                    &nAttrValueSize);
   10673          12 :                     NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
   10674             :                 }
   10675             :             }
   10676         264 :             NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10677             :                            &nAttrValueSize);
   10678         264 :             nc_free_string(nAttrLen, ppszTemp);
   10679         264 :             CPLFree(ppszTemp);
   10680         264 :             break;
   10681             :         }
   10682          28 :         case NC_UBYTE:
   10683             :         {
   10684             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   10685          28 :                 CPLCalloc(nAttrLen, sizeof(unsigned char)));
   10686          28 :             nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
   10687          28 :             dfValue = static_cast<double>(pucTemp[0]);
   10688          28 :             if (nAttrLen > 1)
   10689             :             {
   10690           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10691             :                 {
   10692           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10693           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10694             :                 }
   10695             :             }
   10696          28 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10697          28 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10698          28 :             CPLFree(pucTemp);
   10699          28 :             break;
   10700             :         }
   10701          26 :         case NC_USHORT:
   10702             :         {
   10703             :             unsigned short *pusTemp = static_cast<unsigned short *>(
   10704          26 :                 CPLCalloc(nAttrLen, sizeof(unsigned short)));
   10705          26 :             nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
   10706          26 :             dfValue = static_cast<double>(pusTemp[0]);
   10707          26 :             if (nAttrLen > 1)
   10708             :             {
   10709          10 :                 for (m = 0; m < nAttrLen - 1; m++)
   10710             :                 {
   10711           5 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10712           5 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10713             :                 }
   10714             :             }
   10715          26 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10716          26 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10717          26 :             CPLFree(pusTemp);
   10718          26 :             break;
   10719             :         }
   10720          21 :         case NC_UINT:
   10721             :         {
   10722             :             unsigned int *punTemp =
   10723          21 :                 static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10724          21 :             nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
   10725          21 :             dfValue = static_cast<double>(punTemp[0]);
   10726          21 :             if (nAttrLen > 1)
   10727             :             {
   10728           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10729             :                 {
   10730           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   10731           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10732             :                 }
   10733             :             }
   10734          21 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   10735          21 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10736          21 :             CPLFree(punTemp);
   10737          21 :             break;
   10738             :         }
   10739          49 :         case NC_INT64:
   10740             :         {
   10741             :             GIntBig *panTemp =
   10742          49 :                 static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
   10743          49 :             nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
   10744          49 :             dfValue = static_cast<double>(panTemp[0]);
   10745          49 :             if (nAttrLen > 1)
   10746             :             {
   10747           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10748             :                 {
   10749           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
   10750           0 :                                 panTemp[m]);
   10751           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10752             :                 }
   10753             :             }
   10754          49 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
   10755          49 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10756          49 :             CPLFree(panTemp);
   10757          49 :             break;
   10758             :         }
   10759          22 :         case NC_UINT64:
   10760             :         {
   10761             :             GUIntBig *panTemp =
   10762          22 :                 static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
   10763          22 :             nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
   10764          22 :             dfValue = static_cast<double>(panTemp[0]);
   10765          22 :             if (nAttrLen > 1)
   10766             :             {
   10767           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10768             :                 {
   10769           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
   10770           0 :                                 panTemp[m]);
   10771           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10772             :                 }
   10773             :             }
   10774          22 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
   10775          22 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10776          22 :             CPLFree(panTemp);
   10777          22 :             break;
   10778             :         }
   10779          26 :         default:
   10780          26 :             CPLDebug("GDAL_netCDF",
   10781             :                      "NCDFGetAttr unsupported type %d for attribute %s",
   10782             :                      nAttrType, pszAttrName);
   10783          26 :             break;
   10784             :     }
   10785             : 
   10786       37020 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10787         680 :         NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
   10788             : 
   10789       37020 :     if (bSetDoubleFromStr)
   10790             :     {
   10791       33163 :         if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
   10792             :         {
   10793       32975 :             if (ppszValue == nullptr && pdfValue != nullptr)
   10794             :             {
   10795           1 :                 CPLFree(pszAttrValue);
   10796           1 :                 return CE_Failure;
   10797             :             }
   10798             :         }
   10799       33162 :         dfValue = CPLAtof(pszAttrValue);
   10800             :     }
   10801             : 
   10802             :     /* set return values */
   10803       37019 :     if (ppszValue)
   10804       36695 :         *ppszValue = pszAttrValue;
   10805             :     else
   10806         324 :         CPLFree(pszAttrValue);
   10807             : 
   10808       37019 :     if (pdfValue)
   10809         324 :         *pdfValue = dfValue;
   10810             : 
   10811       37019 :     return CE_None;
   10812             : }
   10813             : 
   10814             : /* sets pdfValue to first value found */
   10815        1357 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10816             :                    double *pdfValue)
   10817             : {
   10818        1357 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
   10819             : }
   10820             : 
   10821             : /* pszValue is the responsibility of the caller and must be freed */
   10822       77811 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10823             :                    char **pszValue)
   10824             : {
   10825       77811 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
   10826             : }
   10827             : 
   10828        3172 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10829             :                    std::string &osValue)
   10830             : {
   10831        3172 :     nc_type nAttrType = NC_NAT;
   10832        3172 :     size_t nAttrLen = 0;
   10833             : 
   10834        3172 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10835        3172 :     if (status != NC_NOERR)
   10836        1391 :         return CE_Failure;
   10837             : 
   10838        1781 :     if (nAttrType != NC_CHAR)
   10839           2 :         return CE_Failure;
   10840             : 
   10841             :     try
   10842             :     {
   10843        1779 :         osValue.resize(nAttrLen, 0);
   10844             :     }
   10845           0 :     catch (const std::exception &)
   10846             :     {
   10847           0 :         return CE_Failure;
   10848             :     }
   10849             : 
   10850        1779 :     const auto nErr = nc_get_att_text(nCdfId, nVarId, pszAttrName,
   10851        1779 :                                       osValue.data()) != NC_NOERR;
   10852        1779 :     NCDF_ERR_RET(nErr);
   10853             : 
   10854        1779 :     return CE_None;
   10855             : }
   10856             : 
   10857             : /* By default write NC_CHAR, but detect for int/float/double and */
   10858             : /* NC4 string arrays */
   10859         184 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10860             :                           const char *pszValue)
   10861             : {
   10862         184 :     int status = 0;
   10863         184 :     char *pszTemp = nullptr;
   10864             : 
   10865             :     /* get the attribute values as tokens */
   10866         368 :     CPLStringList aosValues = NCDFTokenizeArray(pszValue);
   10867         184 :     if (aosValues.empty())
   10868           0 :         return CE_Failure;
   10869             : 
   10870         184 :     size_t nAttrLen = aosValues.size();
   10871             : 
   10872             :     /* first detect type */
   10873         184 :     nc_type nAttrType = NC_CHAR;
   10874         184 :     nc_type nTmpAttrType = NC_CHAR;
   10875         381 :     for (size_t i = 0; i < nAttrLen; i++)
   10876             :     {
   10877         197 :         nTmpAttrType = NC_CHAR;
   10878         197 :         bool bFoundType = false;
   10879         197 :         errno = 0;
   10880         197 :         int nValue = static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   10881             :         /* test for int */
   10882             :         /* TODO test for Byte and short - can this be done safely? */
   10883         197 :         if (errno == 0 && aosValues[i] != pszTemp && *pszTemp == 0)
   10884             :         {
   10885             :             char szTemp[256];
   10886          35 :             CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
   10887          35 :             if (EQUAL(szTemp, aosValues[i]))
   10888             :             {
   10889          35 :                 bFoundType = true;
   10890          35 :                 nTmpAttrType = NC_INT;
   10891             :             }
   10892             :             else
   10893             :             {
   10894             :                 unsigned int unValue = static_cast<unsigned int>(
   10895           0 :                     strtoul(aosValues[i], &pszTemp, 10));
   10896           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
   10897           0 :                 if (EQUAL(szTemp, aosValues[i]))
   10898             :                 {
   10899           0 :                     bFoundType = true;
   10900           0 :                     nTmpAttrType = NC_UINT;
   10901             :                 }
   10902             :             }
   10903             :         }
   10904         197 :         if (!bFoundType)
   10905             :         {
   10906             :             /* test for double */
   10907         162 :             errno = 0;
   10908         162 :             double dfValue = CPLStrtod(aosValues[i], &pszTemp);
   10909         162 :             if ((errno == 0) && (aosValues[i] != pszTemp) && (*pszTemp == 0))
   10910             :             {
   10911             :                 // Test for float instead of double.
   10912             :                 // strtof() is C89, which is not available in MSVC.
   10913             :                 // See if we lose precision if we cast to float and write to
   10914             :                 // char*.
   10915          18 :                 float fValue = float(dfValue);
   10916             :                 char szTemp[256];
   10917          18 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
   10918          18 :                 if (EQUAL(szTemp, aosValues[i]))
   10919          11 :                     nTmpAttrType = NC_FLOAT;
   10920             :                 else
   10921           7 :                     nTmpAttrType = NC_DOUBLE;
   10922             :             }
   10923             :         }
   10924         197 :         if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
   10925         157 :              nTmpAttrType > nAttrType) ||
   10926         157 :             (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
   10927           5 :             (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
   10928          40 :             nAttrType = nTmpAttrType;
   10929             :     }
   10930             : 
   10931             : #ifdef DEBUG
   10932         184 :     if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
   10933             :     {
   10934           0 :         nAttrType = NC_DOUBLE;
   10935           0 :         nAttrLen = 0;
   10936             :     }
   10937             : #endif
   10938             : 
   10939             :     /* now write the data */
   10940         184 :     if (nAttrType == NC_CHAR)
   10941             :     {
   10942         144 :         int nTmpFormat = 0;
   10943         144 :         if (nAttrLen > 1)
   10944             :         {
   10945           0 :             status = nc_inq_format(nCdfId, &nTmpFormat);
   10946           0 :             NCDF_ERR(status);
   10947             :         }
   10948         144 :         if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
   10949           0 :             status =
   10950           0 :                 nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
   10951           0 :                                   const_cast<const char **>(aosValues.List()));
   10952             :         else
   10953         144 :             status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
   10954             :                                      strlen(pszValue), pszValue);
   10955         144 :         NCDF_ERR(status);
   10956             :     }
   10957             :     else
   10958             :     {
   10959          40 :         switch (nAttrType)
   10960             :         {
   10961          27 :             case NC_INT:
   10962             :             {
   10963             :                 int *pnTemp =
   10964          27 :                     static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10965          62 :                 for (size_t i = 0; i < nAttrLen; i++)
   10966             :                 {
   10967          35 :                     pnTemp[i] =
   10968          35 :                         static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   10969             :                 }
   10970          27 :                 status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
   10971             :                                         nAttrLen, pnTemp);
   10972          27 :                 NCDF_ERR(status);
   10973          27 :                 CPLFree(pnTemp);
   10974          27 :                 break;
   10975             :             }
   10976           0 :             case NC_UINT:
   10977             :             {
   10978             :                 unsigned int *punTemp = static_cast<unsigned int *>(
   10979           0 :                     CPLCalloc(nAttrLen, sizeof(unsigned int)));
   10980           0 :                 for (size_t i = 0; i < nAttrLen; i++)
   10981             :                 {
   10982           0 :                     punTemp[i] = static_cast<unsigned int>(
   10983           0 :                         strtol(aosValues[i], &pszTemp, 10));
   10984             :                 }
   10985           0 :                 status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
   10986             :                                          nAttrLen, punTemp);
   10987           0 :                 NCDF_ERR(status);
   10988           0 :                 CPLFree(punTemp);
   10989           0 :                 break;
   10990             :             }
   10991           9 :             case NC_FLOAT:
   10992             :             {
   10993             :                 float *pfTemp =
   10994           9 :                     static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10995          20 :                 for (size_t i = 0; i < nAttrLen; i++)
   10996             :                 {
   10997          11 :                     pfTemp[i] =
   10998          11 :                         static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
   10999             :                 }
   11000           9 :                 status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
   11001             :                                           nAttrLen, pfTemp);
   11002           9 :                 NCDF_ERR(status);
   11003           9 :                 CPLFree(pfTemp);
   11004           9 :                 break;
   11005             :             }
   11006           4 :             case NC_DOUBLE:
   11007             :             {
   11008             :                 double *pdfTemp =
   11009           4 :                     static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   11010          11 :                 for (size_t i = 0; i < nAttrLen; i++)
   11011             :                 {
   11012           7 :                     pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
   11013             :                 }
   11014           4 :                 status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
   11015             :                                            NC_DOUBLE, nAttrLen, pdfTemp);
   11016           4 :                 NCDF_ERR(status);
   11017           4 :                 CPLFree(pdfTemp);
   11018           4 :                 break;
   11019             :             }
   11020           0 :             default:
   11021           0 :                 return CE_Failure;
   11022             :         }
   11023             :     }
   11024             : 
   11025         184 :     return CE_None;
   11026             : }
   11027             : 
   11028          82 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
   11029             : {
   11030             :     /* get var information */
   11031          82 :     int nVarDimId = -1;
   11032          82 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   11033          82 :     if (status != NC_NOERR || nVarDimId != 1)
   11034           0 :         return CE_Failure;
   11035             : 
   11036          82 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11037          82 :     if (status != NC_NOERR)
   11038           0 :         return CE_Failure;
   11039             : 
   11040          82 :     nc_type nVarType = NC_NAT;
   11041          82 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11042          82 :     if (status != NC_NOERR)
   11043           0 :         return CE_Failure;
   11044             : 
   11045          82 :     size_t nVarLen = 0;
   11046          82 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11047          82 :     if (status != NC_NOERR)
   11048           0 :         return CE_Failure;
   11049             : 
   11050          82 :     size_t start[1] = {0};
   11051          82 :     size_t count[1] = {nVarLen};
   11052             : 
   11053             :     /* Allocate guaranteed minimum size */
   11054          82 :     size_t nVarValueSize = NCDF_MAX_STR_LEN;
   11055             :     char *pszVarValue =
   11056          82 :         static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
   11057          82 :     *pszVarValue = '\0';
   11058             : 
   11059          82 :     if (nVarLen == 0)
   11060             :     {
   11061             :         /* set return values */
   11062           1 :         *pszValue = pszVarValue;
   11063             : 
   11064           1 :         return CE_None;
   11065             :     }
   11066             : 
   11067          81 :     if (nVarLen > 1 && nVarType != NC_CHAR)
   11068          43 :         NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
   11069             : 
   11070          81 :     switch (nVarType)
   11071             :     {
   11072           0 :         case NC_CHAR:
   11073           0 :             nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
   11074           0 :             pszVarValue[nVarLen] = '\0';
   11075           0 :             break;
   11076           0 :         case NC_BYTE:
   11077             :         {
   11078             :             signed char *pscTemp = static_cast<signed char *>(
   11079           0 :                 CPLCalloc(nVarLen, sizeof(signed char)));
   11080           0 :             nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11081             :             char szTemp[256];
   11082           0 :             size_t m = 0;
   11083           0 :             for (; m < nVarLen - 1; m++)
   11084             :             {
   11085           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   11086           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11087             :             }
   11088           0 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   11089           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11090           0 :             CPLFree(pscTemp);
   11091           0 :             break;
   11092             :         }
   11093           0 :         case NC_SHORT:
   11094             :         {
   11095             :             short *psTemp =
   11096           0 :                 static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11097           0 :             nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
   11098             :             char szTemp[256];
   11099           0 :             size_t m = 0;
   11100           0 :             for (; m < nVarLen - 1; m++)
   11101             :             {
   11102           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   11103           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11104             :             }
   11105           0 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   11106           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11107           0 :             CPLFree(psTemp);
   11108           0 :             break;
   11109             :         }
   11110          22 :         case NC_INT:
   11111             :         {
   11112          22 :             int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11113          22 :             nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11114             :             char szTemp[256];
   11115          22 :             size_t m = 0;
   11116          47 :             for (; m < nVarLen - 1; m++)
   11117             :             {
   11118          25 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   11119          25 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11120             :             }
   11121          22 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   11122          22 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11123          22 :             CPLFree(pnTemp);
   11124          22 :             break;
   11125             :         }
   11126           8 :         case NC_FLOAT:
   11127             :         {
   11128             :             float *pfTemp =
   11129           8 :                 static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11130           8 :             nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11131             :             char szTemp[256];
   11132           8 :             size_t m = 0;
   11133         325 :             for (; m < nVarLen - 1; m++)
   11134             :             {
   11135         317 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   11136         317 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11137             :             }
   11138           8 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   11139           8 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11140           8 :             CPLFree(pfTemp);
   11141           8 :             break;
   11142             :         }
   11143          48 :         case NC_DOUBLE:
   11144             :         {
   11145             :             double *pdfTemp =
   11146          48 :                 static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11147          48 :             nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11148             :             char szTemp[256];
   11149          48 :             size_t m = 0;
   11150         226 :             for (; m < nVarLen - 1; m++)
   11151             :             {
   11152         178 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   11153         178 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11154             :             }
   11155          48 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   11156          48 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11157          48 :             CPLFree(pdfTemp);
   11158          48 :             break;
   11159             :         }
   11160           0 :         case NC_STRING:
   11161             :         {
   11162             :             char **ppszTemp =
   11163           0 :                 static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
   11164           0 :             nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
   11165           0 :             size_t m = 0;
   11166           0 :             for (; m < nVarLen - 1; m++)
   11167             :             {
   11168           0 :                 NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11169           0 :                 NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
   11170             :             }
   11171           0 :             NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11172           0 :             nc_free_string(nVarLen, ppszTemp);
   11173           0 :             CPLFree(ppszTemp);
   11174           0 :             break;
   11175             :         }
   11176           0 :         case NC_UBYTE:
   11177             :         {
   11178             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   11179           0 :                 CPLCalloc(nVarLen, sizeof(unsigned char)));
   11180           0 :             nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
   11181             :             char szTemp[256];
   11182           0 :             size_t m = 0;
   11183           0 :             for (; m < nVarLen - 1; m++)
   11184             :             {
   11185           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   11186           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11187             :             }
   11188           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   11189           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11190           0 :             CPLFree(pucTemp);
   11191           0 :             break;
   11192             :         }
   11193           0 :         case NC_USHORT:
   11194             :         {
   11195             :             unsigned short *pusTemp = static_cast<unsigned short *>(
   11196           0 :                 CPLCalloc(nVarLen, sizeof(unsigned short)));
   11197           0 :             nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
   11198             :             char szTemp[256];
   11199           0 :             size_t m = 0;
   11200           0 :             for (; m < nVarLen - 1; m++)
   11201             :             {
   11202           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   11203           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11204             :             }
   11205           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   11206           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11207           0 :             CPLFree(pusTemp);
   11208           0 :             break;
   11209             :         }
   11210           0 :         case NC_UINT:
   11211             :         {
   11212             :             unsigned int *punTemp = static_cast<unsigned int *>(
   11213           0 :                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11214           0 :             nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
   11215             :             char szTemp[256];
   11216           0 :             size_t m = 0;
   11217           0 :             for (; m < nVarLen - 1; m++)
   11218             :             {
   11219           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   11220           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11221             :             }
   11222           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   11223           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11224           0 :             CPLFree(punTemp);
   11225           0 :             break;
   11226             :         }
   11227           3 :         case NC_INT64:
   11228             :         {
   11229             :             long long *pnTemp =
   11230           3 :                 static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
   11231           3 :             nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
   11232             :             char szTemp[256];
   11233           3 :             size_t m = 0;
   11234           4 :             for (; m < nVarLen - 1; m++)
   11235             :             {
   11236           1 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
   11237           1 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11238             :             }
   11239           3 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
   11240           3 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11241           3 :             CPLFree(pnTemp);
   11242           3 :             break;
   11243             :         }
   11244           0 :         case NC_UINT64:
   11245             :         {
   11246             :             unsigned long long *pnTemp = static_cast<unsigned long long *>(
   11247           0 :                 CPLCalloc(nVarLen, sizeof(unsigned long long)));
   11248           0 :             nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
   11249             :             char szTemp[256];
   11250           0 :             size_t m = 0;
   11251           0 :             for (; m < nVarLen - 1; m++)
   11252             :             {
   11253           0 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
   11254           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11255             :             }
   11256           0 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
   11257           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11258           0 :             CPLFree(pnTemp);
   11259           0 :             break;
   11260             :         }
   11261           0 :         default:
   11262           0 :             CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
   11263             :                      nVarType);
   11264           0 :             CPLFree(pszVarValue);
   11265           0 :             pszVarValue = nullptr;
   11266           0 :             break;
   11267             :     }
   11268             : 
   11269          81 :     if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
   11270          43 :         NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
   11271             : 
   11272             :     /* set return values */
   11273          81 :     *pszValue = pszVarValue;
   11274             : 
   11275          81 :     return CE_None;
   11276             : }
   11277             : 
   11278           9 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
   11279             : {
   11280           9 :     if (EQUAL(pszValue, ""))
   11281           0 :         return CE_Failure;
   11282             : 
   11283             :     /* get var information */
   11284           9 :     int nVarDimId = -1;
   11285           9 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   11286           9 :     if (status != NC_NOERR || nVarDimId != 1)
   11287           0 :         return CE_Failure;
   11288             : 
   11289           9 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11290           9 :     if (status != NC_NOERR)
   11291           0 :         return CE_Failure;
   11292             : 
   11293           9 :     nc_type nVarType = NC_CHAR;
   11294           9 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11295           9 :     if (status != NC_NOERR)
   11296           0 :         return CE_Failure;
   11297             : 
   11298           9 :     size_t nVarLen = 0;
   11299           9 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11300           9 :     if (status != NC_NOERR)
   11301           0 :         return CE_Failure;
   11302             : 
   11303           9 :     size_t start[1] = {0};
   11304           9 :     size_t count[1] = {nVarLen};
   11305             : 
   11306             :     /* get the values as tokens */
   11307          18 :     CPLStringList aosValues = NCDFTokenizeArray(pszValue);
   11308           9 :     if (aosValues.empty())
   11309           0 :         return CE_Failure;
   11310             : 
   11311           9 :     nVarLen = aosValues.size();
   11312             : 
   11313             :     /* now write the data */
   11314           9 :     if (nVarType == NC_CHAR)
   11315             :     {
   11316           0 :         status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
   11317           0 :         NCDF_ERR(status);
   11318             :     }
   11319             :     else
   11320             :     {
   11321           9 :         switch (nVarType)
   11322             :         {
   11323           0 :             case NC_BYTE:
   11324             :             {
   11325             :                 signed char *pscTemp = static_cast<signed char *>(
   11326           0 :                     CPLCalloc(nVarLen, sizeof(signed char)));
   11327           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11328             :                 {
   11329           0 :                     char *pszTemp = nullptr;
   11330           0 :                     pscTemp[i] = static_cast<signed char>(
   11331           0 :                         strtol(aosValues[i], &pszTemp, 10));
   11332             :                 }
   11333             :                 status =
   11334           0 :                     nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11335           0 :                 NCDF_ERR(status);
   11336           0 :                 CPLFree(pscTemp);
   11337           0 :                 break;
   11338             :             }
   11339           0 :             case NC_SHORT:
   11340             :             {
   11341             :                 short *psTemp =
   11342           0 :                     static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11343           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11344             :                 {
   11345           0 :                     char *pszTemp = nullptr;
   11346           0 :                     psTemp[i] =
   11347           0 :                         static_cast<short>(strtol(aosValues[i], &pszTemp, 10));
   11348             :                 }
   11349             :                 status =
   11350           0 :                     nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
   11351           0 :                 NCDF_ERR(status);
   11352           0 :                 CPLFree(psTemp);
   11353           0 :                 break;
   11354             :             }
   11355           3 :             case NC_INT:
   11356             :             {
   11357             :                 int *pnTemp =
   11358           3 :                     static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11359          11 :                 for (size_t i = 0; i < nVarLen; i++)
   11360             :                 {
   11361           8 :                     char *pszTemp = nullptr;
   11362           8 :                     pnTemp[i] =
   11363           8 :                         static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   11364             :                 }
   11365           3 :                 status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11366           3 :                 NCDF_ERR(status);
   11367           3 :                 CPLFree(pnTemp);
   11368           3 :                 break;
   11369             :             }
   11370           0 :             case NC_FLOAT:
   11371             :             {
   11372             :                 float *pfTemp =
   11373           0 :                     static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11374           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11375             :                 {
   11376           0 :                     char *pszTemp = nullptr;
   11377           0 :                     pfTemp[i] =
   11378           0 :                         static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
   11379             :                 }
   11380             :                 status =
   11381           0 :                     nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11382           0 :                 NCDF_ERR(status);
   11383           0 :                 CPLFree(pfTemp);
   11384           0 :                 break;
   11385             :             }
   11386           5 :             case NC_DOUBLE:
   11387             :             {
   11388             :                 double *pdfTemp =
   11389           5 :                     static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11390          19 :                 for (size_t i = 0; i < nVarLen; i++)
   11391             :                 {
   11392          14 :                     char *pszTemp = nullptr;
   11393          14 :                     pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
   11394             :                 }
   11395             :                 status =
   11396           5 :                     nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11397           5 :                 NCDF_ERR(status);
   11398           5 :                 CPLFree(pdfTemp);
   11399           5 :                 break;
   11400             :             }
   11401           1 :             default:
   11402             :             {
   11403           1 :                 int nTmpFormat = 0;
   11404           1 :                 status = nc_inq_format(nCdfId, &nTmpFormat);
   11405           1 :                 NCDF_ERR(status);
   11406           1 :                 if (nTmpFormat == NCDF_FORMAT_NC4)
   11407             :                 {
   11408           1 :                     switch (nVarType)
   11409             :                     {
   11410           0 :                         case NC_STRING:
   11411             :                         {
   11412           0 :                             status = nc_put_vara_string(
   11413             :                                 nCdfId, nVarId, start, count,
   11414           0 :                                 const_cast<const char **>(aosValues.List()));
   11415           0 :                             NCDF_ERR(status);
   11416           0 :                             break;
   11417             :                         }
   11418           0 :                         case NC_UBYTE:
   11419             :                         {
   11420             :                             unsigned char *pucTemp =
   11421             :                                 static_cast<unsigned char *>(
   11422           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned char)));
   11423           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11424             :                             {
   11425           0 :                                 char *pszTemp = nullptr;
   11426           0 :                                 pucTemp[i] = static_cast<unsigned char>(
   11427           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11428             :                             }
   11429           0 :                             status = nc_put_vara_uchar(nCdfId, nVarId, start,
   11430             :                                                        count, pucTemp);
   11431           0 :                             NCDF_ERR(status);
   11432           0 :                             CPLFree(pucTemp);
   11433           0 :                             break;
   11434             :                         }
   11435           0 :                         case NC_USHORT:
   11436             :                         {
   11437             :                             unsigned short *pusTemp =
   11438             :                                 static_cast<unsigned short *>(
   11439           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned short)));
   11440           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11441             :                             {
   11442           0 :                                 char *pszTemp = nullptr;
   11443           0 :                                 pusTemp[i] = static_cast<unsigned short>(
   11444           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11445             :                             }
   11446           0 :                             status = nc_put_vara_ushort(nCdfId, nVarId, start,
   11447             :                                                         count, pusTemp);
   11448           0 :                             NCDF_ERR(status);
   11449           0 :                             CPLFree(pusTemp);
   11450           0 :                             break;
   11451             :                         }
   11452           0 :                         case NC_UINT:
   11453             :                         {
   11454             :                             unsigned int *punTemp = static_cast<unsigned int *>(
   11455           0 :                                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11456           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11457             :                             {
   11458           0 :                                 char *pszTemp = nullptr;
   11459           0 :                                 punTemp[i] = static_cast<unsigned int>(
   11460           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11461             :                             }
   11462           0 :                             status = nc_put_vara_uint(nCdfId, nVarId, start,
   11463             :                                                       count, punTemp);
   11464           0 :                             NCDF_ERR(status);
   11465           0 :                             CPLFree(punTemp);
   11466           0 :                             break;
   11467             :                         }
   11468           1 :                         default:
   11469           1 :                             return CE_Failure;
   11470             :                     }
   11471             :                 }
   11472           0 :                 break;
   11473             :             }
   11474             :         }
   11475             :     }
   11476             : 
   11477           8 :     return CE_None;
   11478             : }
   11479             : 
   11480             : /************************************************************************/
   11481             : /*                       GetDefaultNoDataValue()                        */
   11482             : /************************************************************************/
   11483             : 
   11484         203 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
   11485             :                                  bool &bGotNoData)
   11486             : 
   11487             : {
   11488         203 :     int nNoFill = 0;
   11489         203 :     double dfNoData = 0.0;
   11490             : 
   11491         203 :     switch (nVarType)
   11492             :     {
   11493           0 :         case NC_CHAR:
   11494             :         case NC_BYTE:
   11495             :         case NC_UBYTE:
   11496             :             // Don't do default fill-values for bytes, too risky.
   11497             :             // This function should not be called in those cases.
   11498           0 :             CPLAssert(false);
   11499             :             break;
   11500          24 :         case NC_SHORT:
   11501             :         {
   11502          24 :             short nFillVal = 0;
   11503          24 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11504             :                 NC_NOERR)
   11505             :             {
   11506          24 :                 if (!nNoFill)
   11507             :                 {
   11508          23 :                     bGotNoData = true;
   11509          23 :                     dfNoData = nFillVal;
   11510             :                 }
   11511             :             }
   11512             :             else
   11513           0 :                 dfNoData = NC_FILL_SHORT;
   11514          24 :             break;
   11515             :         }
   11516          26 :         case NC_INT:
   11517             :         {
   11518          26 :             int nFillVal = 0;
   11519          26 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11520             :                 NC_NOERR)
   11521             :             {
   11522          26 :                 if (!nNoFill)
   11523             :                 {
   11524          25 :                     bGotNoData = true;
   11525          25 :                     dfNoData = nFillVal;
   11526             :                 }
   11527             :             }
   11528             :             else
   11529           0 :                 dfNoData = NC_FILL_INT;
   11530          26 :             break;
   11531             :         }
   11532          85 :         case NC_FLOAT:
   11533             :         {
   11534          85 :             float fFillVal = 0;
   11535          85 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
   11536             :                 NC_NOERR)
   11537             :             {
   11538          85 :                 if (!nNoFill)
   11539             :                 {
   11540          81 :                     bGotNoData = true;
   11541          81 :                     dfNoData = fFillVal;
   11542             :                 }
   11543             :             }
   11544             :             else
   11545           0 :                 dfNoData = NC_FILL_FLOAT;
   11546          85 :             break;
   11547             :         }
   11548          35 :         case NC_DOUBLE:
   11549             :         {
   11550          35 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
   11551             :                 NC_NOERR)
   11552             :             {
   11553          35 :                 if (!nNoFill)
   11554             :                 {
   11555          35 :                     bGotNoData = true;
   11556             :                 }
   11557             :             }
   11558             :             else
   11559           0 :                 dfNoData = NC_FILL_DOUBLE;
   11560          35 :             break;
   11561             :         }
   11562           7 :         case NC_USHORT:
   11563             :         {
   11564           7 :             unsigned short nFillVal = 0;
   11565           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11566             :                 NC_NOERR)
   11567             :             {
   11568           7 :                 if (!nNoFill)
   11569             :                 {
   11570           7 :                     bGotNoData = true;
   11571           7 :                     dfNoData = nFillVal;
   11572             :                 }
   11573             :             }
   11574             :             else
   11575           0 :                 dfNoData = NC_FILL_USHORT;
   11576           7 :             break;
   11577             :         }
   11578           7 :         case NC_UINT:
   11579             :         {
   11580           7 :             unsigned int nFillVal = 0;
   11581           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11582             :                 NC_NOERR)
   11583             :             {
   11584           7 :                 if (!nNoFill)
   11585             :                 {
   11586           7 :                     bGotNoData = true;
   11587           7 :                     dfNoData = nFillVal;
   11588             :                 }
   11589             :             }
   11590             :             else
   11591           0 :                 dfNoData = NC_FILL_UINT;
   11592           7 :             break;
   11593             :         }
   11594          19 :         default:
   11595          19 :             dfNoData = 0.0;
   11596          19 :             break;
   11597             :     }
   11598             : 
   11599         203 :     return dfNoData;
   11600             : }
   11601             : 
   11602             : /************************************************************************/
   11603             : /*                  NCDFGetDefaultNoDataValueAsInt64()                  */
   11604             : /************************************************************************/
   11605             : 
   11606           2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
   11607             :                                          bool &bGotNoData)
   11608             : 
   11609             : {
   11610           2 :     int nNoFill = 0;
   11611           2 :     long long nFillVal = 0;
   11612           2 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11613             :     {
   11614           2 :         if (!nNoFill)
   11615             :         {
   11616           2 :             bGotNoData = true;
   11617           2 :             return static_cast<int64_t>(nFillVal);
   11618             :         }
   11619             :     }
   11620             :     else
   11621           0 :         return static_cast<int64_t>(NC_FILL_INT64);
   11622           0 :     return 0;
   11623             : }
   11624             : 
   11625             : /************************************************************************/
   11626             : /*                 NCDFGetDefaultNoDataValueAsUInt64()                  */
   11627             : /************************************************************************/
   11628             : 
   11629           1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
   11630             :                                            bool &bGotNoData)
   11631             : 
   11632             : {
   11633           1 :     int nNoFill = 0;
   11634           1 :     unsigned long long nFillVal = 0;
   11635           1 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11636             :     {
   11637           1 :         if (!nNoFill)
   11638             :         {
   11639           1 :             bGotNoData = true;
   11640           1 :             return static_cast<uint64_t>(nFillVal);
   11641             :         }
   11642             :     }
   11643             :     else
   11644           0 :         return static_cast<uint64_t>(NC_FILL_UINT64);
   11645           0 :     return 0;
   11646             : }
   11647             : 
   11648       13503 : static int NCDFDoesVarContainAttribVal(int nCdfId,
   11649             :                                        const char *const *papszAttribNames,
   11650             :                                        const char *const *papszAttribValues,
   11651             :                                        int nVarId, const char *pszVarName,
   11652             :                                        bool bStrict = true)
   11653             : {
   11654       13503 :     if (nVarId == -1 && pszVarName != nullptr)
   11655        9183 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11656             : 
   11657       13503 :     if (nVarId == -1)
   11658        1058 :         return -1;
   11659             : 
   11660       12445 :     bool bFound = false;
   11661       61273 :     for (int i = 0; !bFound && papszAttribNames != nullptr &&
   11662       58524 :                     papszAttribNames[i] != nullptr;
   11663             :          i++)
   11664             :     {
   11665       48828 :         char *pszTemp = nullptr;
   11666       48828 :         if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
   11667       70319 :                 CE_None &&
   11668       21491 :             pszTemp != nullptr)
   11669             :         {
   11670       21491 :             if (bStrict)
   11671             :             {
   11672       21491 :                 if (EQUAL(pszTemp, papszAttribValues[i]))
   11673        2749 :                     bFound = true;
   11674             :             }
   11675             :             else
   11676             :             {
   11677           0 :                 if (EQUALN(pszTemp, papszAttribValues[i],
   11678             :                            strlen(papszAttribValues[i])))
   11679           0 :                     bFound = true;
   11680             :             }
   11681       21491 :             CPLFree(pszTemp);
   11682             :         }
   11683             :     }
   11684       12445 :     return bFound;
   11685             : }
   11686             : 
   11687        2291 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
   11688             :                                         const char *const *papszAttribValues,
   11689             :                                         int nVarId, const char *pszVarName,
   11690             :                                         int bStrict = true)
   11691             : {
   11692        2291 :     if (nVarId == -1 && pszVarName != nullptr)
   11693        1729 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11694             : 
   11695        2291 :     if (nVarId == -1)
   11696           0 :         return -1;
   11697             : 
   11698        2291 :     bool bFound = false;
   11699        2291 :     char *pszTemp = nullptr;
   11700        2698 :     if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
   11701         407 :         pszTemp == nullptr)
   11702        1884 :         return FALSE;
   11703             : 
   11704        8257 :     for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
   11705             :     {
   11706        7850 :         if (bStrict)
   11707             :         {
   11708        7820 :             if (EQUAL(pszTemp, papszAttribValues[i]))
   11709          31 :                 bFound = true;
   11710             :         }
   11711             :         else
   11712             :         {
   11713          30 :             if (EQUALN(pszTemp, papszAttribValues[i],
   11714             :                        strlen(papszAttribValues[i])))
   11715           2 :                 bFound = true;
   11716             :         }
   11717             :     }
   11718             : 
   11719         407 :     CPLFree(pszTemp);
   11720             : 
   11721         407 :     return bFound;
   11722             : }
   11723             : 
   11724        1056 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
   11725             : {
   11726        1056 :     if (papszName == nullptr || EQUAL(papszName, ""))
   11727           0 :         return false;
   11728             : 
   11729        3267 :     for (int i = 0; papszValues && papszValues[i]; ++i)
   11730             :     {
   11731        2355 :         if (EQUAL(papszName, papszValues[i]))
   11732         144 :             return true;
   11733             :     }
   11734             : 
   11735         912 :     return false;
   11736             : }
   11737             : 
   11738             : // Test that a variable is longitude/latitude coordinate,
   11739             : // following CF 4.1 and 4.2.
   11740        4515 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
   11741             : {
   11742             :     // Check for matching attributes.
   11743        4515 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
   11744             :                                            papszCFLongitudeAttribValues, nVarId,
   11745             :                                            pszVarName);
   11746             :     // If not found using attributes then check using var name
   11747             :     // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
   11748        4515 :     if (bVal == -1)
   11749             :     {
   11750         342 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11751             :                    "STRICT"))
   11752         342 :             bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
   11753             :         else
   11754           0 :             bVal = FALSE;
   11755             :     }
   11756        4173 :     else if (bVal)
   11757             :     {
   11758             :         // Check that the units is not 'm' or '1'. See #6759
   11759         852 :         char *pszTemp = nullptr;
   11760        1249 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11761         397 :             pszTemp != nullptr)
   11762             :         {
   11763         397 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11764         100 :                 bVal = false;
   11765         397 :             CPLFree(pszTemp);
   11766             :         }
   11767             :     }
   11768             : 
   11769        4515 :     return CPL_TO_BOOL(bVal);
   11770             : }
   11771             : 
   11772        2613 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
   11773             : {
   11774        2613 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
   11775             :                                            papszCFLatitudeAttribValues, nVarId,
   11776             :                                            pszVarName);
   11777        2613 :     if (bVal == -1)
   11778             :     {
   11779         191 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11780             :                    "STRICT"))
   11781         191 :             bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
   11782             :         else
   11783           0 :             bVal = FALSE;
   11784             :     }
   11785        2422 :     else if (bVal)
   11786             :     {
   11787             :         // Check that the units is not 'm' or '1'. See #6759
   11788         593 :         char *pszTemp = nullptr;
   11789         740 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11790         147 :             pszTemp != nullptr)
   11791             :         {
   11792         147 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11793          37 :                 bVal = false;
   11794         147 :             CPLFree(pszTemp);
   11795             :         }
   11796             :     }
   11797             : 
   11798        2613 :     return CPL_TO_BOOL(bVal);
   11799             : }
   11800             : 
   11801        2873 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
   11802             : {
   11803        2873 :     int bVal = NCDFDoesVarContainAttribVal(
   11804             :         nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
   11805             :         nVarId, pszVarName);
   11806        2873 :     if (bVal == -1)
   11807             :     {
   11808         336 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11809             :                    "STRICT"))
   11810         336 :             bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
   11811             :         else
   11812           0 :             bVal = FALSE;
   11813             :     }
   11814        2537 :     else if (bVal)
   11815             :     {
   11816             :         // Check that the units is not '1'
   11817         533 :         char *pszTemp = nullptr;
   11818         818 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11819         285 :             pszTemp != nullptr)
   11820             :         {
   11821         285 :             if (EQUAL(pszTemp, "1"))
   11822           5 :                 bVal = false;
   11823         285 :             CPLFree(pszTemp);
   11824             :         }
   11825             :     }
   11826             : 
   11827        2873 :     return CPL_TO_BOOL(bVal);
   11828             : }
   11829             : 
   11830        2017 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
   11831             : {
   11832        2017 :     int bVal = NCDFDoesVarContainAttribVal(
   11833             :         nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
   11834             :         nVarId, pszVarName);
   11835        2017 :     if (bVal == -1)
   11836             :     {
   11837         187 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11838             :                    "STRICT"))
   11839         187 :             bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
   11840             :         else
   11841           0 :             bVal = FALSE;
   11842             :     }
   11843        1830 :     else if (bVal)
   11844             :     {
   11845             :         // Check that the units is not '1'
   11846         525 :         char *pszTemp = nullptr;
   11847         803 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11848         278 :             pszTemp != nullptr)
   11849             :         {
   11850         278 :             if (EQUAL(pszTemp, "1"))
   11851           5 :                 bVal = false;
   11852         278 :             CPLFree(pszTemp);
   11853             :         }
   11854             :     }
   11855             : 
   11856        2017 :     return CPL_TO_BOOL(bVal);
   11857             : }
   11858             : 
   11859             : /* test that a variable is a vertical coordinate, following CF 4.3 */
   11860        1215 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
   11861             : {
   11862             :     /* check for matching attributes */
   11863        1215 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
   11864             :                                     papszCFVerticalAttribValues, nVarId,
   11865        1215 :                                     pszVarName))
   11866         130 :         return true;
   11867             :     /* check for matching units */
   11868        1085 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11869             :                                           papszCFVerticalUnitsValues, nVarId,
   11870        1085 :                                           pszVarName))
   11871          31 :         return true;
   11872             :     /* check for matching standard name */
   11873        1054 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
   11874             :                                           papszCFVerticalStandardNameValues,
   11875        1054 :                                           nVarId, pszVarName))
   11876           0 :         return true;
   11877             :     else
   11878        1054 :         return false;
   11879             : }
   11880             : 
   11881             : /* test that a variable is a time coordinate, following CF 4.4 */
   11882         270 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
   11883             : {
   11884             :     /* check for matching attributes */
   11885         270 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
   11886             :                                     papszCFTimeAttribValues, nVarId,
   11887         270 :                                     pszVarName))
   11888         118 :         return true;
   11889             :     /* check for matching units */
   11890         152 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11891             :                                           papszCFTimeUnitsValues, nVarId,
   11892         152 :                                           pszVarName, false))
   11893           2 :         return true;
   11894             :     else
   11895         150 :         return false;
   11896             : }
   11897             : 
   11898             : // Parse a string, and return as a string list.
   11899             : // If it is an array of the form {a,b}, then tokenize it.
   11900             : // Otherwise, return a copy.
   11901         302 : static CPLStringList NCDFTokenizeArray(const char *pszValue)
   11902             : {
   11903         302 :     if (pszValue == nullptr || EQUAL(pszValue, ""))
   11904          76 :         return CPLStringList();
   11905             : 
   11906         452 :     CPLStringList aosValues;
   11907         226 :     const int nLen = static_cast<int>(strlen(pszValue));
   11908             : 
   11909         226 :     if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
   11910             :     {
   11911          53 :         char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
   11912          53 :         strncpy(pszTemp, pszValue + 1, nLen - 2);
   11913          53 :         pszTemp[nLen - 2] = '\0';
   11914             :         aosValues.Assign(
   11915          53 :             CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS));
   11916          53 :         CPLFree(pszTemp);
   11917             :     }
   11918             :     else
   11919             :     {
   11920         173 :         aosValues.AddString(pszValue);
   11921             :     }
   11922             : 
   11923         226 :     return aosValues;
   11924             : }
   11925             : 
   11926             : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
   11927             : // Leading slash is optional.
   11928         462 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
   11929             :                                  int *pnGroupId, int *pnVarId)
   11930             : {
   11931         462 :     *pnGroupId = -1;
   11932         462 :     *pnVarId = -1;
   11933             : 
   11934             :     // Open group.
   11935         924 :     std::string osGroupFullName = CPLGetPathSafe(pszSubdatasetName);
   11936             :     // Add a leading slash if needed.
   11937         462 :     if (osGroupFullName.empty() || osGroupFullName[0] != '/')
   11938             :     {
   11939         441 :         osGroupFullName = "/" + osGroupFullName;
   11940             :     }
   11941             :     // Detect root group.
   11942         462 :     if (osGroupFullName == "/")
   11943             :     {
   11944         443 :         *pnGroupId = nCdfId;
   11945             :     }
   11946             :     else
   11947             :     {
   11948             :         int status =
   11949          19 :             nc_inq_grp_full_ncid(nCdfId, osGroupFullName.c_str(), pnGroupId);
   11950          19 :         NCDF_ERR_RET(status);
   11951             :     }
   11952             : 
   11953             :     // Open var.
   11954         462 :     const char *pszVarName = CPLGetFilename(pszSubdatasetName);
   11955         462 :     NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
   11956             : 
   11957         462 :     return CE_None;
   11958             : }
   11959             : 
   11960             : // Get all dimensions visible from a given NetCDF (or group) ID and any of
   11961             : // its parents.
   11962         391 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
   11963             : {
   11964         391 :     int nDims = 0;
   11965         391 :     int *panDimIds = nullptr;
   11966         391 :     NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
   11967             : 
   11968         391 :     panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
   11969             : 
   11970         391 :     int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
   11971         391 :     if (status != NC_NOERR)
   11972           0 :         CPLFree(panDimIds);
   11973         391 :     NCDF_ERR_RET(status);
   11974             : 
   11975         391 :     *pnDims = nDims;
   11976         391 :     *ppanDimIds = panDimIds;
   11977             : 
   11978         391 :     return CE_None;
   11979             : }
   11980             : 
   11981             : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
   11982             : // Consider only direct children, does not get children of children.
   11983        3526 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
   11984             :                                int **ppanSubGroupIds)
   11985             : {
   11986        3526 :     *pnSubGroups = 0;
   11987        3526 :     *ppanSubGroupIds = nullptr;
   11988             : 
   11989             :     int nSubGroups;
   11990        3526 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
   11991             :     int *panSubGroupIds =
   11992        3526 :         static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
   11993        3526 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
   11994        3526 :     *pnSubGroups = nSubGroups;
   11995        3526 :     *ppanSubGroupIds = panSubGroupIds;
   11996             : 
   11997        3526 :     return CE_None;
   11998             : }
   11999             : 
   12000             : // Get the full name of a given NetCDF (or group) ID
   12001             : // (e.g. /group1/group2/.../groupn).
   12002             : // bNC3Compat remove the leading slash for top-level variables for
   12003             : // backward compatibility (top-level variables are the ones in the root group).
   12004       21117 : static CPLErr NCDFGetGroupFullName(int nGroupId, std::string &osFullName,
   12005             :                                    bool bNC3Compat)
   12006             : {
   12007       21117 :     osFullName = "";
   12008             : 
   12009             :     size_t nFullNameLen;
   12010       21117 :     NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
   12011       21117 :     osFullName.resize(nFullNameLen);
   12012             : 
   12013             :     const int status =
   12014       21117 :         nc_inq_grpname_full(nGroupId, &nFullNameLen, osFullName.data());
   12015       21117 :     if (status != NC_NOERR)
   12016             :     {
   12017           0 :         osFullName = "";
   12018           0 :         NCDF_ERR_RET(status);
   12019             :     }
   12020             : 
   12021       21117 :     if (bNC3Compat && osFullName == "/")
   12022        9194 :         osFullName = "";
   12023             : 
   12024       21117 :     return CE_None;
   12025             : }
   12026             : 
   12027       11708 : CPLString NCDFGetGroupFullName(int nGroupId)
   12028             : {
   12029       11708 :     CPLString osFullName;
   12030       11708 :     NCDFGetGroupFullName(nGroupId, osFullName, false);
   12031             : 
   12032       11708 :     return osFullName;
   12033             : }
   12034             : 
   12035             : // Get the full name of a given NetCDF variable ID
   12036             : // (e.g. /group1/group2/.../groupn/var).
   12037             : // Handle also NC_GLOBAL as nVarId.
   12038             : // bNC3Compat remove the leading slash for top-level variables for
   12039             : // backward compatibility (top-level variables are the ones in the root group).
   12040        9309 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId,
   12041             :                                  std::string &osFullName, bool bNC3Compat)
   12042             : {
   12043        9309 :     osFullName = "";
   12044       18618 :     std::string osGroupFullName;
   12045        9309 :     ERR_RET(NCDFGetGroupFullName(nGroupId, osGroupFullName, bNC3Compat));
   12046             :     char szVarName[NC_MAX_NAME + 1];
   12047        9309 :     if (nVarId == NC_GLOBAL)
   12048             :     {
   12049        1211 :         strcpy(szVarName, "NC_GLOBAL");
   12050             :     }
   12051             :     else
   12052             :     {
   12053        8098 :         int status = nc_inq_varname(nGroupId, nVarId, szVarName);
   12054        8098 :         if (status != NC_NOERR)
   12055             :         {
   12056           0 :             NCDF_ERR_RET(status);
   12057             :         }
   12058             :     }
   12059        9309 :     const char *pszSep = "/";
   12060        9309 :     if (osGroupFullName.empty() || osGroupFullName == "/")
   12061        9096 :         pszSep = "";
   12062             :     osFullName =
   12063        9309 :         CPLOPrintf("%s%s%s", osGroupFullName.c_str(), pszSep, szVarName);
   12064             : 
   12065        9309 :     return CE_None;
   12066             : }
   12067             : 
   12068             : // Get the NetCDF root group ID of a given group ID.
   12069           2 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
   12070             : {
   12071           2 :     *pnRootGroupId = -1;
   12072             :     // Recurse on parent group.
   12073             :     int nParentGroupId;
   12074           2 :     int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
   12075           2 :     if (status == NC_NOERR)
   12076           0 :         return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
   12077           2 :     else if (status != NC_ENOGRP)
   12078           0 :         NCDF_ERR_RET(status);
   12079             :     else  // No more parent group.
   12080             :     {
   12081           2 :         *pnRootGroupId = nStartGroupId;
   12082             :     }
   12083             : 
   12084           2 :     return CE_None;
   12085             : }
   12086             : 
   12087             : // Read the size and type of an extra dimension
   12088          18 : static std::pair<int, int> ReadExtraDimDef(GDALDataset *poSrcDS,
   12089             :                                            const char *pszDimName)
   12090             : {
   12091             :     static char szTemp[NC_MAX_NAME + 32 + 1];
   12092          18 :     snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF", pszDimName);
   12093             :     const CPLStringList aosExtraDimValues =
   12094          18 :         NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
   12095             :     const int nDimSize =
   12096          18 :         aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
   12097             : 
   12098             :     // nc_type is an enum in netcdf-3, needs casting.
   12099             :     const int nVarType = static_cast<nc_type>(
   12100          18 :         aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
   12101             : 
   12102          36 :     return {nDimSize, nVarType};
   12103             : }
   12104             : 
   12105             : // Implementation of NCDFResolveVar/Att.
   12106       14884 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
   12107             :                               const char *pszAtt, int *pnGroupId, int *pnId,
   12108             :                               bool bMandatory)
   12109             : {
   12110       14884 :     if (!pszVar && !pszAtt)
   12111             :     {
   12112           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   12113             :                  "pszVar and pszAtt NCDFResolveElem() args are both null.");
   12114           0 :         return CE_Failure;
   12115             :     }
   12116             : 
   12117             :     enum
   12118             :     {
   12119             :         NCRM_PARENT,
   12120             :         NCRM_WIDTH_WISE
   12121       14884 :     } eNCResolveMode = NCRM_PARENT;
   12122             : 
   12123       29768 :     std::queue<int> aoQueueGroupIdsToVisit;
   12124       14884 :     aoQueueGroupIdsToVisit.push(nStartGroupId);
   12125             : 
   12126       16863 :     while (!aoQueueGroupIdsToVisit.empty())
   12127             :     {
   12128             :         // Get the first group of the FIFO queue.
   12129       15079 :         *pnGroupId = aoQueueGroupIdsToVisit.front();
   12130       15079 :         aoQueueGroupIdsToVisit.pop();
   12131             : 
   12132             :         // Look if this group contains the searched element.
   12133             :         int status;
   12134       15079 :         if (pszVar)
   12135       14840 :             status = nc_inq_varid(*pnGroupId, pszVar, pnId);
   12136             :         else  // pszAtt != nullptr.
   12137         239 :             status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
   12138             : 
   12139       15079 :         if (status == NC_NOERR)
   12140             :         {
   12141       13100 :             return CE_None;
   12142             :         }
   12143        1979 :         else if ((pszVar && status != NC_ENOTVAR) ||
   12144         236 :                  (pszAtt && status != NC_ENOTATT))
   12145             :         {
   12146           0 :             NCDF_ERR(status);
   12147             :         }
   12148             :         // Element not found, in NC4 case we must search in other groups
   12149             :         // following the CF logic.
   12150             : 
   12151             :         // The first resolve mode consists to search on parent groups.
   12152        1979 :         if (eNCResolveMode == NCRM_PARENT)
   12153             :         {
   12154        1859 :             int nParentGroupId = -1;
   12155        1859 :             int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
   12156        1859 :             if (status2 == NC_NOERR)
   12157          62 :                 aoQueueGroupIdsToVisit.push(nParentGroupId);
   12158        1797 :             else if (status2 != NC_ENOGRP)
   12159           0 :                 NCDF_ERR(status2);
   12160        1797 :             else if (pszVar)
   12161             :                 // When resolving a variable, if there is no more
   12162             :                 // parent group then we switch to width-wise search mode
   12163             :                 // starting from the latest found parent group.
   12164        1564 :                 eNCResolveMode = NCRM_WIDTH_WISE;
   12165             :         }
   12166             : 
   12167             :         // The second resolve mode is a width-wise search.
   12168        1979 :         if (eNCResolveMode == NCRM_WIDTH_WISE)
   12169             :         {
   12170             :             // Enqueue all direct sub-groups.
   12171        1684 :             int nSubGroups = 0;
   12172        1684 :             int *panSubGroupIds = nullptr;
   12173        1684 :             NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
   12174        1817 :             for (int i = 0; i < nSubGroups; i++)
   12175         133 :                 aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
   12176        1684 :             CPLFree(panSubGroupIds);
   12177             :         }
   12178             :     }
   12179             : 
   12180        1784 :     if (bMandatory)
   12181             :     {
   12182           0 :         std::string osStartGroupFullName;
   12183           0 :         NCDFGetGroupFullName(nStartGroupId, osStartGroupFullName);
   12184           0 :         CPLError(CE_Failure, CPLE_AppDefined,
   12185             :                  "Cannot resolve mandatory %s %s from group %s",
   12186             :                  (pszVar ? pszVar : pszAtt),
   12187             :                  (pszVar ? "variable" : "attribute"),
   12188             :                  osStartGroupFullName.c_str());
   12189             :     }
   12190             : 
   12191        1784 :     *pnGroupId = -1;
   12192        1784 :     *pnId = -1;
   12193        1784 :     return CE_Failure;
   12194             : }
   12195             : 
   12196             : // Resolve a variable name from a given starting group following the CF logic:
   12197             : // - if var name is an absolute path then directly open it
   12198             : // - first search in the starting group and its parent groups
   12199             : // - then if there is no more parent group we switch to a width-wise search
   12200             : //   mode starting from the latest found parent group.
   12201             : // The full CF logic is described here:
   12202             : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
   12203             : // If bMandatory then print an error if resolving fails.
   12204             : // TODO: implement support of relative paths.
   12205             : // TODO: to follow strictly the CF logic, when searching for a coordinate
   12206             : //       variable, we must stop the parent search mode once the corresponding
   12207             : //       dimension is found and start the width-wise search from this group.
   12208             : // TODO: to follow strictly the CF logic, when searching in width-wise mode
   12209             : //       we should skip every groups already visited during the parent
   12210             : //       search mode (but revisiting them should have no impact so we could
   12211             : //       let as it is if it is simpler...)
   12212             : // TODO: CF specifies that the width-wise search order is "left-to-right" so
   12213             : //       maybe we must sort sibling groups alphabetically? but maybe not
   12214             : //       necessary if nc_inq_grps() already sort them?
   12215       14650 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
   12216             :                       int *pnVarId, bool bMandatory)
   12217             : {
   12218       14650 :     *pnGroupId = -1;
   12219       14650 :     *pnVarId = -1;
   12220       14650 :     int nGroupId = nStartGroupId, nVarId;
   12221       14650 :     if (pszVar[0] == '/')
   12222             :     {
   12223             :         // This is an absolute path: we can open the var directly.
   12224             :         int nRootGroupId;
   12225           2 :         ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
   12226           2 :         ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
   12227             :     }
   12228             :     else
   12229             :     {
   12230             :         // We have to search the variable following the CF logic.
   12231       14648 :         ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
   12232             :                                 &nVarId, bMandatory));
   12233             :     }
   12234       13099 :     *pnGroupId = nGroupId;
   12235       13099 :     *pnVarId = nVarId;
   12236       13099 :     return CE_None;
   12237             : }
   12238             : 
   12239             : // Like NCDFResolveVar but returns directly the var full name.
   12240        1567 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
   12241             :                                      std::string &osFullName, bool bMandatory)
   12242             : {
   12243             :     int nGroupId, nVarId;
   12244        1567 :     osFullName = "";
   12245        1567 :     ERR_RET(
   12246             :         NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
   12247        1537 :     return NCDFGetVarFullName(nGroupId, nVarId, osFullName);
   12248             : }
   12249             : 
   12250             : // Like NCDFResolveVar but resolves an attribute instead a variable and
   12251             : // returns its integer value.
   12252             : // Only GLOBAL attributes are supported for the moment.
   12253         236 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
   12254             :                                 const char *pszAtt, int *pnAtt, bool bMandatory)
   12255             : {
   12256         236 :     int nGroupId = nStartGroupId, nAttId = nStartVarId;
   12257         236 :     ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
   12258             :                             bMandatory));
   12259           3 :     NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
   12260           3 :     return CE_None;
   12261             : }
   12262             : 
   12263             : // Filter variables to keep only valid 2+D raster bands and vector fields in
   12264             : // a given a NetCDF (or group) ID and its sub-groups.
   12265             : // Coordinate or boundary variables are ignored.
   12266             : // It also creates corresponding vector layers.
   12267         589 : CPLErr netCDFDataset::FilterVars(
   12268             :     int nCdfId, bool bKeepRasters, bool bKeepVectors,
   12269             :     const CPLStringList &aosIgnoreVars, int *pnRasterVars, int *pnGroupId,
   12270             :     int *pnVarId, int *pnIgnoredVars,
   12271             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
   12272             :         &oMap2DDimsToGroupAndVar)
   12273             : {
   12274         589 :     int nVars = 0;
   12275         589 :     int nRasterVars = 0;
   12276         589 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12277             : 
   12278        1178 :     std::vector<int> anPotentialVectorVarID;
   12279             :     // oMapDimIdToCount[x] = number of times dim x is the first dimension of
   12280             :     // potential vector variables
   12281        1178 :     std::map<int, int> oMapDimIdToCount;
   12282         589 :     int nVarXId = -1;
   12283         589 :     int nVarYId = -1;
   12284         589 :     int nVarZId = -1;
   12285         589 :     int nVarTimeId = -1;
   12286         589 :     int nVarTimeDimId = -1;
   12287         589 :     bool bIsVectorOnly = true;
   12288         589 :     int nProfileDimId = -1;
   12289         589 :     int nParentIndexVarID = -1;
   12290             : 
   12291        3601 :     for (int v = 0; v < nVars; v++)
   12292             :     {
   12293             :         int nVarDims;
   12294        3012 :         NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
   12295             :         // Should we ignore this variable?
   12296             :         char szTemp[NC_MAX_NAME + 1];
   12297        3012 :         szTemp[0] = '\0';
   12298        3012 :         NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
   12299             : 
   12300        3012 :         if (strstr(szTemp, "_node_coordinates") ||
   12301        3012 :             strstr(szTemp, "_node_count"))
   12302             :         {
   12303             :             // Ignore CF-1.8 Simple Geometries helper variables
   12304          70 :             continue;
   12305             :         }
   12306             : 
   12307        4390 :         if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
   12308        1448 :                               NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
   12309             :         {
   12310         403 :             nVarXId = v;
   12311             :         }
   12312        3584 :         else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
   12313        1045 :                                    NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
   12314             :         {
   12315         402 :             nVarYId = v;
   12316             :         }
   12317        2137 :         else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
   12318             :         {
   12319          97 :             nVarZId = v;
   12320             :         }
   12321             :         else
   12322             :         {
   12323        2040 :             std::string osFullName;
   12324        2040 :             CPLErr eErr = NCDFGetVarFullName(nCdfId, v, osFullName);
   12325        2040 :             if (eErr != CE_None)
   12326             :             {
   12327           0 :                 continue;
   12328             :             }
   12329             :             const bool bIgnoreVar =
   12330        2040 :                 aosIgnoreVars.FindString(osFullName.c_str()) != -1;
   12331        2040 :             if (bIgnoreVar)
   12332             :             {
   12333         135 :                 if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
   12334             :                 {
   12335          16 :                     nVarTimeId = v;
   12336          16 :                     nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
   12337             :                 }
   12338         119 :                 else if (nVarDims > 1)
   12339             :                 {
   12340         105 :                     (*pnIgnoredVars)++;
   12341         105 :                     CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
   12342             :                              szTemp);
   12343             :                 }
   12344             :             }
   12345             :             // Only accept 2+D vars.
   12346        1905 :             else if (nVarDims >= 2)
   12347             :             {
   12348         783 :                 bool bRasterCandidate = true;
   12349             :                 // Identify variables that might be vector variables
   12350         783 :                 if (nVarDims == 2)
   12351             :                 {
   12352         698 :                     int anDimIds[2] = {-1, -1};
   12353         698 :                     nc_inq_vardimid(nCdfId, v, anDimIds);
   12354             : 
   12355         698 :                     nc_type vartype = NC_NAT;
   12356         698 :                     nc_inq_vartype(nCdfId, v, &vartype);
   12357             : 
   12358             :                     char szDimNameFirst[NC_MAX_NAME + 1];
   12359             :                     char szDimNameSecond[NC_MAX_NAME + 1];
   12360         698 :                     szDimNameFirst[0] = '\0';
   12361         698 :                     szDimNameSecond[0] = '\0';
   12362        1591 :                     if (vartype == NC_CHAR &&
   12363         195 :                         nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
   12364         195 :                             NC_NOERR &&
   12365         195 :                         nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
   12366         195 :                             NC_NOERR &&
   12367         195 :                         !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
   12368         195 :                         !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
   12369        1088 :                         !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
   12370         195 :                         !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
   12371             :                     {
   12372         195 :                         anPotentialVectorVarID.push_back(v);
   12373         195 :                         oMapDimIdToCount[anDimIds[0]]++;
   12374         195 :                         if (strstr(szDimNameSecond, "_max_width"))
   12375             :                         {
   12376         165 :                             bRasterCandidate = false;
   12377             :                         }
   12378             :                         else
   12379             :                         {
   12380          30 :                             std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12381          30 :                                                     vartype};
   12382          30 :                             oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12383          30 :                                 std::pair(nCdfId, v));
   12384             :                         }
   12385             :                     }
   12386             :                     else
   12387             :                     {
   12388         503 :                         std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12389         503 :                                                 vartype};
   12390         503 :                         oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12391         503 :                             std::pair(nCdfId, v));
   12392         503 :                         bIsVectorOnly = false;
   12393             :                     }
   12394             :                 }
   12395             :                 else
   12396             :                 {
   12397          85 :                     bIsVectorOnly = false;
   12398             :                 }
   12399         783 :                 if (bKeepRasters && bRasterCandidate)
   12400             :                 {
   12401         589 :                     *pnGroupId = nCdfId;
   12402         589 :                     *pnVarId = v;
   12403         589 :                     nRasterVars++;
   12404             :                 }
   12405             :             }
   12406        1122 :             else if (nVarDims == 1)
   12407             :             {
   12408         797 :                 nc_type atttype = NC_NAT;
   12409         797 :                 size_t attlen = 0;
   12410         797 :                 if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
   12411          36 :                                &attlen) == NC_NOERR &&
   12412         797 :                     atttype == NC_CHAR && attlen < NC_MAX_NAME)
   12413             :                 {
   12414          72 :                     std::string osInstanceDimension;
   12415          36 :                     if (NCDFGetAttr(nCdfId, v, "instance_dimension",
   12416          36 :                                     osInstanceDimension) == CE_None)
   12417             :                     {
   12418             :                         const int status =
   12419          36 :                             nc_inq_dimid(nCdfId, osInstanceDimension.c_str(),
   12420             :                                          &nProfileDimId);
   12421          36 :                         if (status == NC_NOERR)
   12422          36 :                             nParentIndexVarID = v;
   12423             :                         else
   12424           0 :                             nProfileDimId = -1;
   12425          36 :                         if (status == NC_EBADDIM)
   12426           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
   12427             :                                      "Attribute instance_dimension='%s' refers "
   12428             :                                      "to a non existing dimension",
   12429             :                                      osInstanceDimension.c_str());
   12430             :                         else
   12431          36 :                             NCDF_ERR(status);
   12432             :                     }
   12433             :                 }
   12434         797 :                 if (v != nParentIndexVarID)
   12435             :                 {
   12436         761 :                     anPotentialVectorVarID.push_back(v);
   12437         761 :                     int nDimId = -1;
   12438         761 :                     nc_inq_vardimid(nCdfId, v, &nDimId);
   12439         761 :                     oMapDimIdToCount[nDimId]++;
   12440             :                 }
   12441             :             }
   12442             :         }
   12443             :     }
   12444             : 
   12445             :     // If we are opened in raster-only mode and that there are only 1D or 2D
   12446             :     // variables and that the 2D variables have no X/Y dim, and all
   12447             :     // variables refer to the same main dimension (or 2 dimensions for
   12448             :     // featureType=profile), then it is a pure vector dataset
   12449             :     CPLString osFeatureType(
   12450         589 :         aosMetadata.FetchNameValueDef("NC_GLOBAL#featureType", ""));
   12451         456 :     if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
   12452        1045 :         !anPotentialVectorVarID.empty() &&
   12453           0 :         (oMapDimIdToCount.size() == 1 ||
   12454           0 :          (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
   12455           0 :           nProfileDimId >= 0)))
   12456             :     {
   12457           0 :         anPotentialVectorVarID.resize(0);
   12458             :     }
   12459             :     else
   12460             :     {
   12461         589 :         *pnRasterVars += nRasterVars;
   12462             :     }
   12463             : 
   12464         589 :     if (!anPotentialVectorVarID.empty() && bKeepVectors)
   12465             :     {
   12466             :         // Take the dimension that is referenced the most times.
   12467         135 :         if (!(oMapDimIdToCount.size() == 1 ||
   12468          70 :               (EQUAL(osFeatureType, "profile") &&
   12469          70 :                oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
   12470             :         {
   12471           0 :             CPLError(CE_Warning, CPLE_AppDefined,
   12472             :                      "The dataset has several variables that could be "
   12473             :                      "identified as vector fields, but not all share the same "
   12474             :                      "primary dimension. Consequently they will be ignored.");
   12475             :         }
   12476             :         else
   12477             :         {
   12478         101 :             if (nVarTimeId >= 0 &&
   12479         101 :                 oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
   12480             :             {
   12481           1 :                 anPotentialVectorVarID.push_back(nVarTimeId);
   12482             :             }
   12483         100 :             CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
   12484             :                                   oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
   12485             :                                   nProfileDimId, nParentIndexVarID,
   12486             :                                   bKeepRasters);
   12487             :         }
   12488             :     }
   12489             : 
   12490             :     // Recurse on sub-groups.
   12491         589 :     int nSubGroups = 0;
   12492         589 :     int *panSubGroupIds = nullptr;
   12493         589 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12494         626 :     for (int i = 0; i < nSubGroups; i++)
   12495             :     {
   12496          37 :         FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors, aosIgnoreVars,
   12497             :                    pnRasterVars, pnGroupId, pnVarId, pnIgnoredVars,
   12498             :                    oMap2DDimsToGroupAndVar);
   12499             :     }
   12500         589 :     CPLFree(panSubGroupIds);
   12501             : 
   12502         589 :     return CE_None;
   12503             : }
   12504             : 
   12505             : // Create vector layers from given potentially identified vector variables
   12506             : // resulting from the scanning of a NetCDF (or group) ID.
   12507         100 : CPLErr netCDFDataset::CreateGrpVectorLayers(
   12508             :     int nCdfId, const CPLString &osFeatureType,
   12509             :     const std::vector<int> &anPotentialVectorVarID,
   12510             :     const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
   12511             :     int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
   12512             : {
   12513         200 :     std::string osGroupName;
   12514         100 :     NCDFGetGroupFullName(nCdfId, osGroupName);
   12515         100 :     if (osGroupName.empty())
   12516             :     {
   12517          98 :         osGroupName = CPLGetBasenameSafe(osFilename);
   12518             :     }
   12519         100 :     OGRwkbGeometryType eGType = wkbUnknown;
   12520             :     CPLString osLayerName = aosMetadata.FetchNameValueDef(
   12521         200 :         "NC_GLOBAL#ogr_layer_name", osGroupName.c_str());
   12522         100 :     aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_name", nullptr);
   12523             : 
   12524         100 :     if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
   12525             :     {
   12526          54 :         aosMetadata.SetNameValue("NC_GLOBAL#featureType", nullptr);
   12527          54 :         eGType = wkbPoint;
   12528             :     }
   12529             : 
   12530             :     const char *pszLayerType =
   12531         100 :         aosMetadata.FetchNameValue("NC_GLOBAL#ogr_layer_type");
   12532         100 :     if (pszLayerType != nullptr)
   12533             :     {
   12534          10 :         eGType = OGRFromOGCGeomType(pszLayerType);
   12535          10 :         aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_type", nullptr);
   12536             :     }
   12537             : 
   12538             :     CPLString osGeometryField =
   12539         200 :         aosMetadata.FetchNameValueDef("NC_GLOBAL#ogr_geometry_field", "");
   12540         100 :     aosMetadata.SetNameValue("NC_GLOBAL#ogr_geometry_field", nullptr);
   12541             : 
   12542         100 :     int nFirstVarId = -1;
   12543         100 :     int nVectorDim = oMapDimIdToCount.rbegin()->first;
   12544         100 :     if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
   12545             :     {
   12546          35 :         if (nVectorDim == nProfileDimId)
   12547           0 :             nVectorDim = oMapDimIdToCount.begin()->first;
   12548             :     }
   12549             :     else
   12550             :     {
   12551          65 :         nProfileDimId = -1;
   12552             :     }
   12553         135 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12554             :     {
   12555         135 :         int anDimIds[2] = {-1, -1};
   12556         135 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12557         135 :         if (nVectorDim == anDimIds[0])
   12558             :         {
   12559         100 :             nFirstVarId = anPotentialVectorVarID[j];
   12560         100 :             break;
   12561             :         }
   12562             :     }
   12563             : 
   12564             :     // In case where coordinates are explicitly specified for one of the
   12565             :     // field/variable, use them in priority over the ones that might have been
   12566             :     // identified above.
   12567         100 :     char *pszCoordinates = nullptr;
   12568         100 :     if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
   12569             :         CE_None)
   12570             :     {
   12571             :         const CPLStringList aosTokens(
   12572         110 :             NCDFTokenizeCoordinatesAttribute(pszCFCoordinates));
   12573          55 :         for (int i = 0; i < aosTokens.size(); i++)
   12574             :         {
   12575           0 :             if (NCDFIsVarLongitude(nCdfId, -1, aosTokens[i]) ||
   12576           0 :                 NCDFIsVarProjectionX(nCdfId, -1, aosTokens[i]))
   12577             :             {
   12578           0 :                 nVarXId = -1;
   12579           0 :                 CPL_IGNORE_RET_VAL(
   12580           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarXId));
   12581             :             }
   12582           0 :             else if (NCDFIsVarLatitude(nCdfId, -1, aosTokens[i]) ||
   12583           0 :                      NCDFIsVarProjectionY(nCdfId, -1, aosTokens[i]))
   12584             :             {
   12585           0 :                 nVarYId = -1;
   12586           0 :                 CPL_IGNORE_RET_VAL(
   12587           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarYId));
   12588             :             }
   12589           0 :             else if (NCDFIsVarVerticalCoord(nCdfId, -1, aosTokens[i]))
   12590             :             {
   12591           0 :                 nVarZId = -1;
   12592           0 :                 CPL_IGNORE_RET_VAL(
   12593           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarZId));
   12594             :             }
   12595             :         }
   12596             :     }
   12597         100 :     CPLFree(pszCoordinates);
   12598             : 
   12599             :     // Check that the X,Y,Z vars share 1D and share the same dimension as
   12600             :     // attribute variables.
   12601         100 :     if (nVarXId >= 0 && nVarYId >= 0)
   12602             :     {
   12603          84 :         int nVarDimCount = -1;
   12604          84 :         int nVarDimId = -1;
   12605          84 :         if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
   12606          84 :             nVarDimCount != 1 ||
   12607          84 :             nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
   12608          84 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
   12609          56 :             nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
   12610          56 :             nVarDimCount != 1 ||
   12611         224 :             nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
   12612          56 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
   12613             :         {
   12614          28 :             nVarXId = nVarYId = -1;
   12615             :         }
   12616         111 :         else if (nVarZId >= 0 &&
   12617          55 :                  (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
   12618          55 :                   nVarDimCount != 1 ||
   12619          55 :                   nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
   12620          55 :                   nVarDimId != nVectorDim))
   12621             :         {
   12622           0 :             nVarZId = -1;
   12623             :         }
   12624             :     }
   12625             : 
   12626         100 :     if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
   12627             :     {
   12628           2 :         eGType = wkbPoint;
   12629             :     }
   12630         100 :     if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
   12631             :     {
   12632          55 :         eGType = wkbPoint25D;
   12633             :     }
   12634         100 :     if (eGType == wkbUnknown && osGeometryField.empty())
   12635             :     {
   12636          34 :         eGType = wkbNone;
   12637             :     }
   12638             : 
   12639             :     // Read projection info
   12640         200 :     CPLStringList aosMetadataBackup = aosMetadata;
   12641         100 :     ReadAttributes(nCdfId, nFirstVarId);
   12642         100 :     if (!this->bSGSupport)
   12643         100 :         SetProjectionFromVar(nCdfId, nFirstVarId, true);
   12644         100 :     const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
   12645         200 :     std::string osGridMapping = pszValue ? pszValue : "";
   12646         100 :     aosMetadata = std::move(aosMetadataBackup);
   12647             : 
   12648         100 :     OGRSpatialReference *poSRS = nullptr;
   12649         100 :     if (!m_oSRS.IsEmpty())
   12650             :     {
   12651          21 :         poSRS = m_oSRS.Clone();
   12652             :     }
   12653             :     // Reset if there's a 2D raster
   12654         100 :     m_bHasProjection = false;
   12655         100 :     m_bHasGeoTransform = false;
   12656             : 
   12657         100 :     if (!bKeepRasters)
   12658             :     {
   12659             :         // Strip out uninteresting metadata.
   12660          67 :         aosMetadata.SetNameValue("NC_GLOBAL#Conventions", nullptr);
   12661          67 :         aosMetadata.SetNameValue("NC_GLOBAL#GDAL", nullptr);
   12662          67 :         aosMetadata.SetNameValue("NC_GLOBAL#history", nullptr);
   12663             :     }
   12664             : 
   12665             :     std::shared_ptr<netCDFLayer> poLayer(
   12666         100 :         new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
   12667         100 :     if (poSRS != nullptr)
   12668          21 :         poSRS->Release();
   12669         100 :     poLayer->SetRecordDimID(nVectorDim);
   12670         100 :     if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
   12671             :     {
   12672          56 :         poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
   12673             :     }
   12674          44 :     else if (!osGeometryField.empty())
   12675             :     {
   12676          10 :         poLayer->SetWKTGeometryField(osGeometryField);
   12677             :     }
   12678         100 :     if (!osGridMapping.empty())
   12679             :     {
   12680          21 :         poLayer->SetGridMapping(osGridMapping.c_str());
   12681             :     }
   12682         100 :     poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
   12683             : 
   12684         742 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12685             :     {
   12686         642 :         int anDimIds[2] = {-1, -1};
   12687         642 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12688         642 :         if (anDimIds[0] == nVectorDim ||
   12689          68 :             (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
   12690             :         {
   12691             : #ifdef NCDF_DEBUG
   12692             :             char szTemp2[NC_MAX_NAME + 1] = {};
   12693             :             CPL_IGNORE_RET_VAL(
   12694             :                 nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
   12695             :             CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
   12696             : #endif
   12697         642 :             poLayer->AddField(anPotentialVectorVarID[j]);
   12698             :         }
   12699             :     }
   12700             : 
   12701         100 :     if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
   12702           0 :         poLayer->GetGeomType() != wkbNone)
   12703             :     {
   12704         100 :         papoLayers.push_back(poLayer);
   12705             :     }
   12706             : 
   12707         200 :     return CE_None;
   12708             : }
   12709             : 
   12710             : // Get all coordinate and boundary variables full names referenced in
   12711             : // a given a NetCDF (or group) ID and its sub-groups.
   12712             : // These variables are identified in other variable's
   12713             : // "coordinates" and "bounds" attribute.
   12714             : // Searching coordinate and boundary variables may need to explore
   12715             : // parents groups (or other groups in case of reference given in form of an
   12716             : // absolute path).
   12717             : // See CF sections 5.2, 5.6 and 7.1
   12718         590 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
   12719             :                                                CPLStringList &aosVars)
   12720             : {
   12721         590 :     int nVars = 0;
   12722         590 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12723             : 
   12724        3616 :     for (int v = 0; v < nVars; v++)
   12725             :     {
   12726        3026 :         char *pszTemp = nullptr;
   12727        6052 :         CPLStringList aosTokens;
   12728        3026 :         if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
   12729         500 :             aosTokens.Assign(NCDFTokenizeCoordinatesAttribute(pszTemp));
   12730        3026 :         CPLFree(pszTemp);
   12731        3026 :         pszTemp = nullptr;
   12732        3026 :         if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
   12733        3026 :             pszTemp != nullptr && !EQUAL(pszTemp, ""))
   12734          20 :             aosTokens.AddString(pszTemp);
   12735        3026 :         CPLFree(pszTemp);
   12736        4479 :         for (int i = 0; i < aosTokens.size(); i++)
   12737             :         {
   12738        2906 :             std::string osVarFullName;
   12739        1453 :             if (NCDFResolveVarFullName(nCdfId, aosTokens[i], osVarFullName) ==
   12740             :                 CE_None)
   12741        1423 :                 aosVars.AddString(osVarFullName);
   12742             :         }
   12743             :     }
   12744             : 
   12745             :     // Recurse on sub-groups.
   12746             :     int nSubGroups;
   12747         590 :     int *panSubGroupIds = nullptr;
   12748         590 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12749         627 :     for (int i = 0; i < nSubGroups; i++)
   12750             :     {
   12751          37 :         NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], aosVars);
   12752             :     }
   12753         590 :     CPLFree(panSubGroupIds);
   12754             : 
   12755         590 :     return CE_None;
   12756             : }
   12757             : 
   12758             : // Check if give type is user defined
   12759        2073 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
   12760             : {
   12761        2073 :     return type >= NC_FIRSTUSERTYPEID;
   12762             : }
   12763             : 
   12764         664 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
   12765             : {
   12766             :     // CF conventions use space as the separator for variable names in the
   12767             :     // coordinates attribute, but some products such as
   12768             :     // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
   12769             :     // use comma.
   12770         664 :     return CSLTokenizeString2(pszCoordinates, ", ", 0);
   12771             : }

Generated by: LCOV version 1.14