LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4927 5807 84.8 %
Date: 2026-05-29 23:25:07 Functions: 157 164 95.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  netCDF read/write Driver
       4             :  * Purpose:  GDAL bindings over netCDF library.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *           Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2004, Frank Warmerdam
      10             :  * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
      11             :  * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
      12             :  * Copyright (c) 2021, CLS
      13             :  *
      14             :  * SPDX-License-Identifier: MIT
      15             :  ****************************************************************************/
      16             : 
      17             : #include "cpl_port.h"
      18             : 
      19             : #include <array>
      20             : #include <cassert>
      21             : #include <cctype>
      22             : #include <cerrno>
      23             : #include <climits>
      24             : #include <cmath>
      25             : #include <cstdio>
      26             : #include <cstdlib>
      27             : #include <cstring>
      28             : #include <ctime>
      29             : #include <algorithm>
      30             : #include <limits>
      31             : #include <map>
      32             : #include <mutex>
      33             : #include <set>
      34             : #include <queue>
      35             : #include <string>
      36             : #include <tuple>
      37             : #include <utility>
      38             : #include <vector>
      39             : 
      40             : // Must be included after standard includes, otherwise VS2015 fails when
      41             : // including <ctime>
      42             : #include "netcdfdataset.h"
      43             : #include "netcdfdrivercore.h"
      44             : #include "netcdfsg.h"
      45             : #include "netcdfuffd.h"
      46             : 
      47             : #include "netcdf_mem.h"
      48             : 
      49             : #include "cpl_conv.h"
      50             : #include "cpl_error.h"
      51             : #include "cpl_float.h"
      52             : #include "cpl_json.h"
      53             : #include "cpl_minixml.h"
      54             : #include "cpl_multiproc.h"
      55             : #include "cpl_progress.h"
      56             : #include "cpl_time.h"
      57             : #include "gdal.h"
      58             : #include "gdal_frmts.h"
      59             : #include "gdal_priv_templates.hpp"
      60             : #include "ogr_core.h"
      61             : #include "ogr_srs_api.h"
      62             : 
      63             : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
      64             : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
      65             : // this is apparently back to expecting filenames in current codepage...
      66             : // Detect netCDF 4.8 with NC_ENCZARR
      67             : // Detect netCDF 4.9 with NC_NOATTCREORD
      68             : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
      69             : #define NETCDF_USES_UTF8
      70             : #endif
      71             : 
      72             : // Internal function declarations.
      73             : 
      74             : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
      75             : 
      76             : static void
      77             : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
      78             :                    bool bWriteGDALHistory, const char *pszOldHist,
      79             :                    const char *pszFunctionName,
      80             :                    const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
      81             : 
      82             : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
      83             :                            const char *pszOldHist);
      84             : 
      85             : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
      86             :                              size_t *nDestSize);
      87             : 
      88             : // Var / attribute helper functions.
      89             : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
      90             :                           const char *pszValue);
      91             : 
      92             : // Replace this where used.
      93             : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
      94             : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
      95             : 
      96             : // Replace this where used.
      97             : static 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 CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
     118             :                                      std::string &osFullName,
     119             :                                      bool bMandatory = false);
     120             : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
     121             :                                 const char *pszAtt, int *pnAtt,
     122             :                                 bool bMandatory = false);
     123             : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
     124             :                                                CPLStringList &aosVars);
     125             : 
     126             : // Uncomment this for more debug output.
     127             : // #define NCDF_DEBUG 1
     128             : 
     129             : CPLMutex *hNCMutex = nullptr;
     130             : 
     131             : // Workaround https://github.com/OSGeo/gdal/issues/6253
     132             : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
     133             : // way. Apparently having the same handle works better (this is OK since
     134             : // we have a global mutex on the netCDF library)
     135             : static std::map<std::string, int> goMapNameToNetCDFId;
     136             : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
     137             : 
     138         787 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
     139             : {
     140        1574 :     std::string osKey(pszFilename);
     141         787 :     osKey += "#####";
     142         787 :     osKey += std::to_string(nMode);
     143         787 :     auto oIter = goMapNameToNetCDFId.find(osKey);
     144         787 :     if (oIter == goMapNameToNetCDFId.end())
     145             :     {
     146         727 :         int ret = nc_open(pszFilename, nMode, pID);
     147         727 :         if (ret != NC_NOERR)
     148           3 :             return ret;
     149         724 :         goMapNameToNetCDFId[osKey] = *pID;
     150         724 :         goMapNetCDFIdToKeyAndCount[*pID] =
     151        1448 :             std::pair<std::string, int>(osKey, 1);
     152         724 :         return ret;
     153             :     }
     154             :     else
     155             :     {
     156          60 :         *pID = oIter->second;
     157          60 :         goMapNetCDFIdToKeyAndCount[oIter->second].second++;
     158          60 :         return NC_NOERR;
     159             :     }
     160             : }
     161             : 
     162        1102 : int GDAL_nc_close(int cdfid)
     163             : {
     164        1102 :     int ret = NC_NOERR;
     165        1102 :     auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
     166        1102 :     if (oIter != goMapNetCDFIdToKeyAndCount.end())
     167             :     {
     168         784 :         if (--oIter->second.second == 0)
     169             :         {
     170         724 :             ret = nc_close(cdfid);
     171         724 :             goMapNameToNetCDFId.erase(oIter->second.first);
     172         724 :             goMapNetCDFIdToKeyAndCount.erase(oIter);
     173             :         }
     174             :     }
     175             :     else
     176             :     {
     177             :         // we can go here if file opened with nc_open_mem() or nc_create()
     178         318 :         ret = nc_close(cdfid);
     179             :     }
     180        1102 :     return ret;
     181             : }
     182             : 
     183             : /************************************************************************/
     184             : /* ==================================================================== */
     185             : /*                         netCDFRasterBand                             */
     186             : /* ==================================================================== */
     187             : /************************************************************************/
     188             : 
     189             : class netCDFRasterBand final : public GDALPamRasterBand
     190             : {
     191             :     friend class netCDFDataset;
     192             : 
     193             :     nc_type nc_datatype;
     194             :     int cdfid;
     195             :     int nZId;
     196             :     int nZDim;
     197             :     int nLevel;
     198             :     int nBandXPos;
     199             :     int nBandYPos;
     200             :     int *panBandZPos;
     201             :     int *panBandZLev;
     202             :     bool m_bNoDataSet = false;
     203             :     double m_dfNoDataValue = 0;
     204             :     bool m_bNoDataSetAsInt64 = false;
     205             :     int64_t m_nNodataValueInt64 = 0;
     206             :     bool m_bNoDataSetAsUInt64 = false;
     207             :     uint64_t m_nNodataValueUInt64 = 0;
     208             :     bool bValidRangeValid = false;
     209             :     double adfValidRange[2]{0, 0};
     210             :     bool m_bHaveScale = false;
     211             :     bool m_bHaveOffset = false;
     212             :     double m_dfScale = 1;
     213             :     double m_dfOffset = 0;
     214             :     CPLString m_osUnitType{};
     215             :     bool bSignedData;
     216             :     bool bCheckLongitude;
     217             :     bool m_bCreateMetadataFromOtherVarsDone = false;
     218             : 
     219             :     void CreateMetadataFromAttributes();
     220             :     void CreateMetadataFromOtherVars();
     221             : 
     222             :     template <class T>
     223             :     void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     224             :                    size_t nTmpBlockYSize, bool bCheckIsNan = false);
     225             :     template <class T>
     226             :     void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     227             :                       size_t nTmpBlockYSize, bool bCheckIsNan = false);
     228             :     void SetBlockSize();
     229             : 
     230             :     bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
     231             : 
     232             :     void SetNoDataValueNoUpdate(double dfNoData);
     233             :     void SetNoDataValueNoUpdate(int64_t nNoData);
     234             :     void SetNoDataValueNoUpdate(uint64_t nNoData);
     235             : 
     236             :     void SetOffsetNoUpdate(double dfVal);
     237             :     void SetScaleNoUpdate(double dfVal);
     238             :     void SetUnitTypeNoUpdate(const char *pszNewValue);
     239             : 
     240             :   protected:
     241             :     CPLXMLNode *SerializeToXML(const char *pszUnused) override;
     242             : 
     243             :   public:
     244             :     struct CONSTRUCTOR_OPEN
     245             :     {
     246             :     };
     247             : 
     248             :     struct CONSTRUCTOR_CREATE
     249             :     {
     250             :     };
     251             : 
     252             :     netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
     253             :                      int nGroupId, int nZId, int nZDim, int nLevel,
     254             :                      const int *panBandZLen, const int *panBandPos, int nBand);
     255             :     netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
     256             :                      GDALDataType eType, int nBand, bool bSigned = true,
     257             :                      const char *pszBandName = nullptr,
     258             :                      const char *pszLongName = nullptr, int nZId = -1,
     259             :                      int nZDim = 2, int nLevel = 0,
     260             :                      const int *panBandZLev = nullptr,
     261             :                      const int *panBandZPos = nullptr,
     262             :                      const int *paDimIds = nullptr);
     263             :     ~netCDFRasterBand() override;
     264             : 
     265             :     double GetNoDataValue(int *) override;
     266             :     int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
     267             :     uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
     268             :     CPLErr SetNoDataValue(double) override;
     269             :     CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
     270             :     CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
     271             :     // virtual CPLErr DeleteNoDataValue();
     272             :     double GetOffset(int *) override;
     273             :     CPLErr SetOffset(double) override;
     274             :     double GetScale(int *) override;
     275             :     CPLErr SetScale(double) override;
     276             :     const char *GetUnitType() override;
     277             :     CPLErr SetUnitType(const char *) override;
     278             :     CPLErr IReadBlock(int, int, void *) override;
     279             :     CPLErr IWriteBlock(int, int, void *) override;
     280             : 
     281             :     CSLConstList GetMetadata(const char *pszDomain = "") override;
     282             :     const char *GetMetadataItem(const char *pszName,
     283             :                                 const char *pszDomain = "") override;
     284             : 
     285             :     CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     286             :                            const char *pszDomain = "") override;
     287             :     CPLErr SetMetadata(CSLConstList papszMD,
     288             :                        const char *pszDomain = "") override;
     289             : };
     290             : 
     291             : /************************************************************************/
     292             : /*                          netCDFRasterBand()                          */
     293             : /************************************************************************/
     294             : 
     295         498 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
     296             :                                    netCDFDataset *poNCDFDS, int nGroupId,
     297             :                                    int nZIdIn, int nZDimIn, int nLevelIn,
     298             :                                    const int *panBandZLevIn,
     299         498 :                                    const int *panBandZPosIn, int nBandIn)
     300             :     : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
     301         498 :       nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
     302         498 :       nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
     303             :       panBandZLev(nullptr),
     304             :       bSignedData(true),  // Default signed, except for Byte.
     305         996 :       bCheckLongitude(false)
     306             : {
     307         498 :     poDS = poNCDFDS;
     308         498 :     nBand = nBandIn;
     309             : 
     310             :     // Take care of all other dimensions.
     311         498 :     if (nZDim > 2)
     312             :     {
     313         176 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     314         176 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     315             : 
     316         478 :         for (int i = 0; i < nZDim - 2; i++)
     317             :         {
     318         302 :             panBandZPos[i] = panBandZPosIn[i + 2];
     319         302 :             panBandZLev[i] = panBandZLevIn[i];
     320             :         }
     321             :     }
     322             : 
     323         498 :     nRasterXSize = poDS->GetRasterXSize();
     324         498 :     nRasterYSize = poDS->GetRasterYSize();
     325         498 :     nBlockXSize = poDS->GetRasterXSize();
     326         498 :     nBlockYSize = 1;
     327             : 
     328             :     // Get the type of the "z" variable, our target raster array.
     329         498 :     if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
     330         498 :                    nullptr) != NC_NOERR)
     331             :     {
     332           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
     333           0 :         return;
     334             :     }
     335             : 
     336         498 :     if (NCDFIsUserDefinedType(cdfid, nc_datatype))
     337             :     {
     338             :         // First enquire and check that the number of fields is 2
     339             :         size_t nfields, compoundsize;
     340           5 :         if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
     341           5 :                             &nfields) != NC_NOERR)
     342             :         {
     343           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     344             :                      "Error in nc_inq_compound() on 'z'.");
     345           0 :             return;
     346             :         }
     347             : 
     348           5 :         if (nfields != 2)
     349             :         {
     350           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     351             :                      "Unsupported data type encountered in nc_inq_compound() "
     352             :                      "on 'z'.");
     353           0 :             return;
     354             :         }
     355             : 
     356             :         // Now check that that two types are the same in the struct.
     357             :         nc_type field_type1, field_type2;
     358             :         int field_dims1, field_dims2;
     359           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     360             :                                   &field_type1, &field_dims1,
     361           5 :                                   nullptr) != NC_NOERR)
     362             :         {
     363           0 :             CPLError(
     364             :                 CE_Failure, CPLE_AppDefined,
     365             :                 "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
     366           0 :             return;
     367             :         }
     368             : 
     369           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     370             :                                   &field_type2, &field_dims2,
     371           5 :                                   nullptr) != NC_NOERR)
     372             :         {
     373           0 :             CPLError(
     374             :                 CE_Failure, CPLE_AppDefined,
     375             :                 "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
     376           0 :             return;
     377             :         }
     378             : 
     379           5 :         if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
     380           5 :             (field_dims1 != 0))
     381             :         {
     382           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     383             :                      "Error in interpreting compound data type on 'z'.");
     384           0 :             return;
     385             :         }
     386             : 
     387           5 :         if (field_type1 == NC_SHORT)
     388           0 :             eDataType = GDT_CInt16;
     389           5 :         else if (field_type1 == NC_INT)
     390           0 :             eDataType = GDT_CInt32;
     391           5 :         else if (field_type1 == NC_FLOAT)
     392           4 :             eDataType = GDT_CFloat32;
     393           1 :         else if (field_type1 == NC_DOUBLE)
     394           1 :             eDataType = GDT_CFloat64;
     395             :         else
     396             :         {
     397           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     398             :                      "Unsupported netCDF compound data type encountered.");
     399           0 :             return;
     400             :         }
     401             :     }
     402             :     else
     403             :     {
     404         493 :         if (nc_datatype == NC_BYTE)
     405         157 :             eDataType = GDT_UInt8;
     406         336 :         else if (nc_datatype == NC_CHAR)
     407           0 :             eDataType = GDT_UInt8;
     408         336 :         else if (nc_datatype == NC_SHORT)
     409          41 :             eDataType = GDT_Int16;
     410         295 :         else if (nc_datatype == NC_INT)
     411          89 :             eDataType = GDT_Int32;
     412         206 :         else if (nc_datatype == NC_FLOAT)
     413         127 :             eDataType = GDT_Float32;
     414          79 :         else if (nc_datatype == NC_DOUBLE)
     415          40 :             eDataType = GDT_Float64;
     416          39 :         else if (nc_datatype == NC_UBYTE)
     417          16 :             eDataType = GDT_UInt8;
     418          23 :         else if (nc_datatype == NC_USHORT)
     419           4 :             eDataType = GDT_UInt16;
     420          19 :         else if (nc_datatype == NC_UINT)
     421           4 :             eDataType = GDT_UInt32;
     422          15 :         else if (nc_datatype == NC_INT64)
     423           8 :             eDataType = GDT_Int64;
     424           7 :         else if (nc_datatype == NC_UINT64)
     425           7 :             eDataType = GDT_UInt64;
     426             :         else
     427             :         {
     428           0 :             if (nBand == 1)
     429           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     430             :                          "Unsupported netCDF datatype (%d), treat as Float32.",
     431           0 :                          static_cast<int>(nc_datatype));
     432           0 :             eDataType = GDT_Float32;
     433           0 :             nc_datatype = NC_FLOAT;
     434             :         }
     435             :     }
     436             : 
     437             :     // Find and set No Data for this variable.
     438         498 :     nc_type atttype = NC_NAT;
     439         498 :     size_t attlen = 0;
     440         498 :     const char *pszNoValueName = nullptr;
     441             : 
     442             :     // Find attribute name, either _FillValue or missing_value.
     443         498 :     int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
     444         498 :     if (status == NC_NOERR)
     445             :     {
     446         249 :         pszNoValueName = NCDF_FillValue;
     447             :     }
     448             :     else
     449             :     {
     450         249 :         status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
     451         249 :         if (status == NC_NOERR)
     452             :         {
     453          12 :             pszNoValueName = "missing_value";
     454             :         }
     455             :     }
     456             : 
     457             :     // Fetch missing value.
     458         498 :     double dfNoData = 0.0;
     459         498 :     bool bGotNoData = false;
     460         498 :     int64_t nNoDataAsInt64 = 0;
     461         498 :     bool bGotNoDataAsInt64 = false;
     462         498 :     uint64_t nNoDataAsUInt64 = 0;
     463         498 :     bool bGotNoDataAsUInt64 = false;
     464         498 :     if (status == NC_NOERR)
     465             :     {
     466         261 :         nc_type nAttrType = NC_NAT;
     467         261 :         size_t nAttrLen = 0;
     468         261 :         status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
     469         261 :         if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
     470             :         {
     471             :             long long v;
     472           7 :             nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
     473           7 :             bGotNoData = true;
     474           7 :             bGotNoDataAsInt64 = true;
     475           7 :             nNoDataAsInt64 = static_cast<int64_t>(v);
     476             :         }
     477         254 :         else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
     478             :         {
     479             :             unsigned long long v;
     480           7 :             nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
     481           7 :             bGotNoData = true;
     482           7 :             bGotNoDataAsUInt64 = true;
     483           7 :             nNoDataAsUInt64 = static_cast<uint64_t>(v);
     484             :         }
     485         247 :         else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
     486             :         {
     487         246 :             bGotNoData = true;
     488             :         }
     489             :     }
     490             : 
     491             :     // If NoData was not found, use the default value, but for non-Byte types
     492             :     // as it is not recommended:
     493             :     // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
     494         498 :     nc_type vartype = NC_NAT;
     495         498 :     if (!bGotNoData)
     496             :     {
     497         238 :         nc_inq_vartype(cdfid, nZId, &vartype);
     498         238 :         if (vartype == NC_INT64)
     499             :         {
     500             :             nNoDataAsInt64 =
     501           1 :                 NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
     502           1 :             bGotNoDataAsInt64 = bGotNoData;
     503             :         }
     504         237 :         else if (vartype == NC_UINT64)
     505             :         {
     506             :             nNoDataAsUInt64 =
     507           0 :                 NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
     508           0 :             bGotNoDataAsUInt64 = bGotNoData;
     509             :         }
     510         237 :         else if (vartype != NC_CHAR && vartype != NC_BYTE &&
     511         103 :                  vartype != NC_UBYTE)
     512             :         {
     513          93 :             dfNoData =
     514          93 :                 NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
     515          93 :             if (bGotNoData)
     516             :             {
     517          82 :                 CPLDebug("GDAL_netCDF",
     518             :                          "did not get nodata value for variable #%d, using "
     519             :                          "default %f",
     520             :                          nZId, dfNoData);
     521             :             }
     522             :         }
     523             :     }
     524             : 
     525         498 :     bool bHasUnderscoreUnsignedAttr = false;
     526         498 :     bool bUnderscoreUnsignedAttrVal = false;
     527             :     {
     528         498 :         char *pszTemp = nullptr;
     529         498 :         if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
     530             :         {
     531         149 :             if (EQUAL(pszTemp, "true"))
     532             :             {
     533         141 :                 bHasUnderscoreUnsignedAttr = true;
     534         141 :                 bUnderscoreUnsignedAttrVal = true;
     535             :             }
     536           8 :             else if (EQUAL(pszTemp, "false"))
     537             :             {
     538           8 :                 bHasUnderscoreUnsignedAttr = true;
     539           8 :                 bUnderscoreUnsignedAttrVal = false;
     540             :             }
     541         149 :             CPLFree(pszTemp);
     542             :         }
     543             :     }
     544             : 
     545             :     // Look for valid_range or valid_min/valid_max.
     546             : 
     547             :     // First look for valid_range.
     548         498 :     if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
     549             :     {
     550         496 :         char *pszValidRange = nullptr;
     551         496 :         if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
     552         142 :                 CE_None &&
     553         638 :             pszValidRange[0] == '{' &&
     554         142 :             pszValidRange[strlen(pszValidRange) - 1] == '}')
     555             :         {
     556             :             const std::string osValidRange =
     557         426 :                 std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
     558             :             const CPLStringList aosValidRange(
     559         284 :                 CSLTokenizeString2(osValidRange.c_str(), ",", 0));
     560         142 :             if (aosValidRange.size() == 2 &&
     561         284 :                 CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
     562         142 :                 CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
     563             :             {
     564         142 :                 bValidRangeValid = true;
     565         142 :                 adfValidRange[0] = CPLAtof(aosValidRange[0]);
     566         142 :                 adfValidRange[1] = CPLAtof(aosValidRange[1]);
     567             :             }
     568             :         }
     569         496 :         CPLFree(pszValidRange);
     570             : 
     571             :         // If not found look for valid_min and valid_max.
     572         496 :         if (!bValidRangeValid)
     573             :         {
     574         354 :             double dfMin = 0;
     575         354 :             double dfMax = 0;
     576         369 :             if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
     577          15 :                 NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
     578             :             {
     579           8 :                 adfValidRange[0] = dfMin;
     580           8 :                 adfValidRange[1] = dfMax;
     581           8 :                 bValidRangeValid = true;
     582             :             }
     583             :         }
     584             : 
     585         496 :         if (bValidRangeValid &&
     586         150 :             (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
     587          17 :             nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
     588             :             bUnderscoreUnsignedAttrVal)
     589             :         {
     590           2 :             if (adfValidRange[0] < 0)
     591           0 :                 adfValidRange[0] += 65536;
     592           2 :             if (adfValidRange[1] < 0)
     593           2 :                 adfValidRange[1] += 65536;
     594           2 :             if (adfValidRange[0] <= adfValidRange[1])
     595             :             {
     596             :                 // Updating metadata item
     597           2 :                 GDALPamRasterBand::SetMetadataItem(
     598             :                     "valid_range",
     599           2 :                     CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
     600           2 :                                static_cast<int>(adfValidRange[1])));
     601             :             }
     602             :         }
     603             : 
     604         496 :         if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
     605             :         {
     606           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     607             :                      "netCDFDataset::valid_range: min > max:\n"
     608             :                      "  min: %lf\n  max: %lf\n",
     609             :                      adfValidRange[0], adfValidRange[1]);
     610           0 :             bValidRangeValid = false;
     611           0 :             adfValidRange[0] = 0.0;
     612           0 :             adfValidRange[1] = 0.0;
     613             :         }
     614             :     }
     615             : 
     616             :     // Special For Byte Bands: check for signed/unsigned byte.
     617         498 :     if (nc_datatype == NC_BYTE)
     618             :     {
     619             :         // netcdf uses signed byte by default, but GDAL uses unsigned by default
     620             :         // This may cause unexpected results, but is needed for back-compat.
     621         157 :         if (poNCDFDS->bIsGdalFile)
     622         135 :             bSignedData = false;
     623             :         else
     624          22 :             bSignedData = true;
     625             : 
     626             :         // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
     627             :         // But in case a NC3 file was converted automatically and has hints
     628             :         // that it is unsigned, take them into account
     629         157 :         if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     630             :         {
     631           3 :             bSignedData = true;
     632             :         }
     633             : 
     634             :         // If we got valid_range, test for signed/unsigned range.
     635             :         // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
     636         157 :         if (bValidRangeValid)
     637             :         {
     638             :             // If we got valid_range={0,255}, treat as unsigned.
     639         138 :             if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
     640             :             {
     641         130 :                 bSignedData = false;
     642             :                 // Reset valid_range.
     643         130 :                 bValidRangeValid = false;
     644             :             }
     645             :             // If we got valid_range={-128,127}, treat as signed.
     646           8 :             else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
     647             :             {
     648           8 :                 bSignedData = true;
     649             :                 // Reset valid_range.
     650           8 :                 bValidRangeValid = false;
     651             :             }
     652             :         }
     653             :         // Else test for _Unsigned.
     654             :         // https://docs.unidata.ucar.edu/nug/current/best_practices.html
     655             :         else
     656             :         {
     657          19 :             if (bHasUnderscoreUnsignedAttr)
     658           7 :                 bSignedData = !bUnderscoreUnsignedAttrVal;
     659             :         }
     660             : 
     661         157 :         if (bSignedData)
     662             :         {
     663          20 :             eDataType = GDT_Int8;
     664             :         }
     665         137 :         else if (dfNoData < 0)
     666             :         {
     667             :             // Fix nodata value as it was stored signed.
     668           6 :             dfNoData += 256;
     669           6 :             if (pszNoValueName)
     670             :             {
     671             :                 // Updating metadata item
     672           6 :                 GDALPamRasterBand::SetMetadataItem(
     673             :                     pszNoValueName,
     674             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     675             :             }
     676             :         }
     677             :     }
     678         341 :     else if (nc_datatype == NC_SHORT)
     679             :     {
     680          41 :         if (bHasUnderscoreUnsignedAttr)
     681             :         {
     682           4 :             bSignedData = !bUnderscoreUnsignedAttrVal;
     683           4 :             if (!bSignedData)
     684           4 :                 eDataType = GDT_UInt16;
     685             :         }
     686             : 
     687             :         // Fix nodata value as it was stored signed.
     688          41 :         if (!bSignedData && dfNoData < 0)
     689             :         {
     690           4 :             dfNoData += 65536;
     691           4 :             if (pszNoValueName)
     692             :             {
     693             :                 // Updating metadata item
     694           4 :                 GDALPamRasterBand::SetMetadataItem(
     695             :                     pszNoValueName,
     696             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     697             :             }
     698             :         }
     699             :     }
     700             : 
     701         300 :     else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
     702         280 :              nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
     703             :     {
     704          31 :         bSignedData = false;
     705             :     }
     706             : 
     707         498 :     CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
     708         498 :              nc_datatype, eDataType, static_cast<int>(bSignedData));
     709             : 
     710         498 :     if (bGotNoData)
     711             :     {
     712             :         // Set nodata value.
     713         343 :         if (bGotNoDataAsInt64)
     714             :         {
     715           8 :             if (eDataType == GDT_Int64)
     716             :             {
     717           8 :                 SetNoDataValueNoUpdate(nNoDataAsInt64);
     718             :             }
     719           0 :             else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
     720             :             {
     721           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
     722             :             }
     723             :             else
     724             :             {
     725           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
     726             :             }
     727             :         }
     728         335 :         else if (bGotNoDataAsUInt64)
     729             :         {
     730           7 :             if (eDataType == GDT_UInt64)
     731             :             {
     732           7 :                 SetNoDataValueNoUpdate(nNoDataAsUInt64);
     733             :             }
     734           0 :             else if (eDataType == GDT_Int64 &&
     735             :                      nNoDataAsUInt64 <=
     736           0 :                          static_cast<uint64_t>(
     737           0 :                              std::numeric_limits<int64_t>::max()))
     738             :             {
     739           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
     740             :             }
     741             :             else
     742             :             {
     743           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
     744             :             }
     745             :         }
     746             :         else
     747             :         {
     748             : #ifdef NCDF_DEBUG
     749             :             CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
     750             : #endif
     751         328 :             if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
     752             :             {
     753           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
     754             :             }
     755         328 :             else if (eDataType == GDT_UInt64 &&
     756           0 :                      GDALIsValueExactAs<uint64_t>(dfNoData))
     757             :             {
     758           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
     759             :             }
     760             :             else
     761             :             {
     762         328 :                 SetNoDataValueNoUpdate(dfNoData);
     763             :             }
     764             :         }
     765             :     }
     766             : 
     767         498 :     CreateMetadataFromAttributes();
     768             : 
     769             :     // Attempt to fetch the scale_factor and add_offset attributes for the
     770             :     // variable and set them.  If these values are not available, set
     771             :     // offset to 0 and scale to 1.
     772         498 :     if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
     773             :     {
     774          16 :         double dfOffset = 0;
     775          16 :         status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
     776          16 :         CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
     777             :                  status);
     778          16 :         SetOffsetNoUpdate(dfOffset);
     779             :     }
     780             : 
     781         498 :     bool bHasScale = false;
     782         498 :     if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
     783             :     {
     784          20 :         bHasScale = true;
     785          20 :         double dfScale = 1;
     786          20 :         status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
     787          20 :         CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
     788             :                  status);
     789          20 :         SetScaleNoUpdate(dfScale);
     790             :     }
     791             : 
     792          12 :     if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
     793           4 :         eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
     794           4 :         (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
     795         510 :          std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
     796           1 :         CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
     797             :             nullptr)
     798             :     {
     799           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     800             :                  "validity range = %f, %f contains floating-point values, "
     801             :                  "whereas data type is integer. valid_range is thus likely "
     802             :                  "wrong%s. Ignoring it.",
     803             :                  adfValidRange[0], adfValidRange[1],
     804             :                  bHasScale ? " (likely scaled using scale_factor/add_factor "
     805             :                              "whereas it should be using the packed data type)"
     806             :                            : "");
     807           1 :         bValidRangeValid = false;
     808           1 :         adfValidRange[0] = 0.0;
     809           1 :         adfValidRange[1] = 0.0;
     810             :     }
     811             : 
     812             :     // Should we check for longitude values > 360?
     813         498 :     bCheckLongitude =
     814         996 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
     815         498 :         NCDFIsVarLongitude(cdfid, nZId, nullptr);
     816             : 
     817             :     // Attempt to fetch the units attribute for the variable and set it.
     818         498 :     SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
     819             : 
     820         498 :     SetBlockSize();
     821             : }
     822             : 
     823         686 : void netCDFRasterBand::SetBlockSize()
     824             : {
     825             :     // Check for variable chunking (netcdf-4 only).
     826             :     // GDAL block size should be set to hdf5 chunk size.
     827         686 :     int nTmpFormat = 0;
     828         686 :     int status = nc_inq_format(cdfid, &nTmpFormat);
     829         686 :     NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
     830         686 :     if ((status == NC_NOERR) &&
     831         587 :         (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
     832             :     {
     833         115 :         size_t chunksize[MAX_NC_DIMS] = {};
     834             :         // Check for chunksize and set it as the blocksize (optimizes read).
     835         115 :         status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
     836         115 :         if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
     837             :         {
     838          14 :             nBlockXSize = (int)chunksize[nZDim - 1];
     839          14 :             if (nZDim >= 2)
     840          14 :                 nBlockYSize = (int)chunksize[nZDim - 2];
     841             :             else
     842           0 :                 nBlockYSize = 1;
     843             :         }
     844             :     }
     845             : 
     846             :     // Deal with bottom-up datasets and nBlockYSize != 1.
     847         686 :     auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
     848         686 :     if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
     849             :     {
     850           6 :         if (poGDS->eAccess == GA_ReadOnly)
     851             :         {
     852             :             // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
     853             :             // width of the raster
     854           6 :             size_t nChunks =
     855           6 :                 static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
     856           6 :             if ((nRasterYSize % nBlockYSize) != 0)
     857           2 :                 nChunks *= 2;
     858             :             const size_t nChunkSize =
     859           6 :                 static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
     860           6 :                 nBlockXSize * nBlockYSize;
     861           6 :             constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
     862           6 :             nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
     863           6 :             if (nChunks)
     864             :             {
     865           6 :                 poGDS->poChunkCache.reset(
     866           6 :                     new netCDFDataset::ChunkCacheType(nChunks));
     867             :             }
     868             :         }
     869             :         else
     870             :         {
     871           0 :             nBlockYSize = 1;
     872             :         }
     873             :     }
     874         686 : }
     875             : 
     876             : // Constructor in create mode.
     877             : // If nZId and following variables are not passed, the band will have 2
     878             : // dimensions.
     879             : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
     880         188 : netCDFRasterBand::netCDFRasterBand(
     881             :     const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
     882             :     const GDALDataType eTypeIn, int nBandIn, bool bSigned,
     883             :     const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
     884             :     int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
     885         188 :     const int *paDimIds)
     886         188 :     : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
     887             :       nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
     888             :       panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
     889         188 :       bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
     890             : {
     891         188 :     poDS = poNCDFDS;
     892         188 :     nBand = nBandIn;
     893             : 
     894         188 :     nRasterXSize = poDS->GetRasterXSize();
     895         188 :     nRasterYSize = poDS->GetRasterYSize();
     896         188 :     nBlockXSize = poDS->GetRasterXSize();
     897         188 :     nBlockYSize = 1;
     898             : 
     899         188 :     if (poDS->GetAccess() != GA_Update)
     900             :     {
     901           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     902             :                  "Dataset is not in update mode, "
     903             :                  "wrong netCDFRasterBand constructor");
     904           0 :         return;
     905             :     }
     906             : 
     907             :     // Take care of all other dimensions.
     908         188 :     if (nZDim > 2 && paDimIds != nullptr)
     909             :     {
     910          27 :         nBandXPos = panBandZPosIn[0];
     911          27 :         nBandYPos = panBandZPosIn[1];
     912             : 
     913          27 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     914          27 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     915             : 
     916          76 :         for (int i = 0; i < nZDim - 2; i++)
     917             :         {
     918          49 :             panBandZPos[i] = panBandZPosIn[i + 2];
     919          49 :             panBandZLev[i] = panBandZLevIn[i];
     920             :         }
     921             :     }
     922             : 
     923             :     // Get the type of the "z" variable, our target raster array.
     924         188 :     eDataType = eTypeIn;
     925             : 
     926         188 :     switch (eDataType)
     927             :     {
     928          83 :         case GDT_UInt8:
     929          83 :             nc_datatype = NC_BYTE;
     930             :             // NC_UBYTE (unsigned byte) is only available for NC4.
     931          83 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     932           3 :                 nc_datatype = NC_UBYTE;
     933          83 :             break;
     934           7 :         case GDT_Int8:
     935           7 :             nc_datatype = NC_BYTE;
     936           7 :             break;
     937          11 :         case GDT_Int16:
     938          11 :             nc_datatype = NC_SHORT;
     939          11 :             break;
     940          24 :         case GDT_Int32:
     941          24 :             nc_datatype = NC_INT;
     942          24 :             break;
     943          13 :         case GDT_Float32:
     944          13 :             nc_datatype = NC_FLOAT;
     945          13 :             break;
     946           8 :         case GDT_Float64:
     947           8 :             nc_datatype = NC_DOUBLE;
     948           8 :             break;
     949           7 :         case GDT_Int64:
     950           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     951             :             {
     952           7 :                 nc_datatype = NC_INT64;
     953             :             }
     954             :             else
     955             :             {
     956           0 :                 if (nBand == 1)
     957           0 :                     CPLError(
     958             :                         CE_Warning, CPLE_AppDefined,
     959             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     960             :                         "Int64");
     961           0 :                 nc_datatype = NC_DOUBLE;
     962           0 :                 eDataType = GDT_Float64;
     963             :             }
     964           7 :             break;
     965           7 :         case GDT_UInt64:
     966           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     967             :             {
     968           7 :                 nc_datatype = NC_UINT64;
     969             :             }
     970             :             else
     971             :             {
     972           0 :                 if (nBand == 1)
     973           0 :                     CPLError(
     974             :                         CE_Warning, CPLE_AppDefined,
     975             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     976             :                         "UInt64");
     977           0 :                 nc_datatype = NC_DOUBLE;
     978           0 :                 eDataType = GDT_Float64;
     979             :             }
     980           7 :             break;
     981           6 :         case GDT_UInt16:
     982           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     983             :             {
     984           6 :                 nc_datatype = NC_USHORT;
     985           6 :                 break;
     986             :             }
     987             :             [[fallthrough]];
     988             :         case GDT_UInt32:
     989           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     990             :             {
     991           6 :                 nc_datatype = NC_UINT;
     992           6 :                 break;
     993             :             }
     994             :             [[fallthrough]];
     995             :         default:
     996          16 :             if (nBand == 1)
     997           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
     998             :                          "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
     999           8 :                          static_cast<int>(eDataType));
    1000          16 :             nc_datatype = NC_FLOAT;
    1001          16 :             eDataType = GDT_Float32;
    1002          16 :             break;
    1003             :     }
    1004             : 
    1005             :     // Define the variable if necessary (if nZId == -1).
    1006         188 :     bool bDefineVar = false;
    1007             : 
    1008         188 :     if (nZId == -1)
    1009             :     {
    1010         166 :         bDefineVar = true;
    1011             : 
    1012             :         // Make sure we are in define mode.
    1013         166 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1014             : 
    1015             :         char szTempPrivate[256 + 1];
    1016         166 :         const char *pszTemp = nullptr;
    1017         166 :         if (!pszBandName || EQUAL(pszBandName, ""))
    1018             :         {
    1019         144 :             snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
    1020         144 :             pszTemp = szTempPrivate;
    1021             :         }
    1022             :         else
    1023             :         {
    1024          22 :             pszTemp = pszBandName;
    1025             :         }
    1026             : 
    1027             :         int status;
    1028         166 :         if (nZDim > 2 && paDimIds != nullptr)
    1029             :         {
    1030           5 :             status =
    1031           5 :                 nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
    1032             :         }
    1033             :         else
    1034             :         {
    1035         161 :             int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
    1036             :             status =
    1037         161 :                 nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
    1038             :         }
    1039         166 :         NCDF_ERR(status);
    1040         166 :         CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
    1041             :                  nc_datatype, nZId);
    1042             : 
    1043         166 :         if (!pszLongName || EQUAL(pszLongName, ""))
    1044             :         {
    1045         159 :             snprintf(szTempPrivate, sizeof(szTempPrivate),
    1046             :                      "GDAL Band Number %d", nBand);
    1047         159 :             pszTemp = szTempPrivate;
    1048             :         }
    1049             :         else
    1050             :         {
    1051           7 :             pszTemp = pszLongName;
    1052             :         }
    1053             :         status =
    1054         166 :             nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
    1055         166 :         NCDF_ERR(status);
    1056             : 
    1057         166 :         poNCDFDS->DefVarDeflate(nZId, true);
    1058             :     }
    1059             : 
    1060             :     // For Byte data add signed/unsigned info.
    1061         188 :     if (eDataType == GDT_UInt8 || eDataType == GDT_Int8)
    1062             :     {
    1063          90 :         if (bDefineVar)
    1064             :         {
    1065             :             // Only add attributes if creating variable.
    1066             :             // For unsigned NC_BYTE (except NC4 format),
    1067             :             // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
    1068          82 :             if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
    1069             :             {
    1070          79 :                 CPLDebug("GDAL_netCDF",
    1071             :                          "adding valid_range attributes for Byte Band");
    1072          79 :                 short l_adfValidRange[2] = {0, 0};
    1073             :                 int status;
    1074          79 :                 if (bSignedData || eDataType == GDT_Int8)
    1075             :                 {
    1076           7 :                     l_adfValidRange[0] = -128;
    1077           7 :                     l_adfValidRange[1] = 127;
    1078           7 :                     status =
    1079           7 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
    1080             :                 }
    1081             :                 else
    1082             :                 {
    1083          72 :                     l_adfValidRange[0] = 0;
    1084          72 :                     l_adfValidRange[1] = 255;
    1085             :                     status =
    1086          72 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
    1087             :                 }
    1088          79 :                 NCDF_ERR(status);
    1089          79 :                 status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
    1090             :                                           2, l_adfValidRange);
    1091          79 :                 NCDF_ERR(status);
    1092             :             }
    1093             :         }
    1094             :     }
    1095             : 
    1096         188 :     if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
    1097         101 :         nc_datatype != NC_UBYTE)
    1098             :     {
    1099             :         // Set default nodata.
    1100          98 :         bool bIgnored = false;
    1101             :         double dfNoData =
    1102          98 :             NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
    1103             : #ifdef NCDF_DEBUG
    1104             :         CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
    1105             : #endif
    1106          98 :         netCDFRasterBand::SetNoDataValue(dfNoData);
    1107             :     }
    1108             : 
    1109         188 :     SetBlockSize();
    1110             : }
    1111             : 
    1112             : /************************************************************************/
    1113             : /*                         ~netCDFRasterBand()                          */
    1114             : /************************************************************************/
    1115             : 
    1116        1372 : netCDFRasterBand::~netCDFRasterBand()
    1117             : {
    1118         686 :     netCDFRasterBand::FlushCache(true);
    1119         686 :     CPLFree(panBandZPos);
    1120         686 :     CPLFree(panBandZLev);
    1121        1372 : }
    1122             : 
    1123             : /************************************************************************/
    1124             : /*                            GetMetadata()                             */
    1125             : /************************************************************************/
    1126             : 
    1127          54 : CSLConstList netCDFRasterBand::GetMetadata(const char *pszDomain)
    1128             : {
    1129          54 :     if (!m_bCreateMetadataFromOtherVarsDone)
    1130          52 :         CreateMetadataFromOtherVars();
    1131          54 :     return GDALPamRasterBand::GetMetadata(pszDomain);
    1132             : }
    1133             : 
    1134             : /************************************************************************/
    1135             : /*                          GetMetadataItem()                           */
    1136             : /************************************************************************/
    1137             : 
    1138         572 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
    1139             :                                               const char *pszDomain)
    1140             : {
    1141         572 :     if (!m_bCreateMetadataFromOtherVarsDone &&
    1142         556 :         STARTS_WITH(pszName, "NETCDF_DIM_") &&
    1143           1 :         (!pszDomain || pszDomain[0] == 0))
    1144           1 :         CreateMetadataFromOtherVars();
    1145         572 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
    1146             : }
    1147             : 
    1148             : /************************************************************************/
    1149             : /*                          SetMetadataItem()                           */
    1150             : /************************************************************************/
    1151             : 
    1152           7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
    1153             :                                          const char *pszValue,
    1154             :                                          const char *pszDomain)
    1155             : {
    1156           9 :     if (GetAccess() == GA_Update &&
    1157           9 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    1158             :     {
    1159             :         // Same logic as in CopyMetadata()
    1160             : 
    1161           2 :         const char *const papszIgnoreBand[] = {
    1162             :             CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    1163             :             NCDF_FillValue, "coordinates",   nullptr};
    1164             :         // Do not copy varname, stats, NETCDF_DIM_*, nodata
    1165             :         // and items in papszIgnoreBand.
    1166           6 :         if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
    1167           2 :             STARTS_WITH(pszName, "STATISTICS_") ||
    1168           2 :             STARTS_WITH(pszName, "NETCDF_DIM_") ||
    1169           2 :             STARTS_WITH(pszName, "missing_value") ||
    1170           6 :             STARTS_WITH(pszName, "_FillValue") ||
    1171           2 :             CSLFindString(papszIgnoreBand, pszName) != -1)
    1172             :         {
    1173             :             // do nothing
    1174             :         }
    1175             :         else
    1176             :         {
    1177           2 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1178             : 
    1179           2 :             if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
    1180           2 :                 return CE_Failure;
    1181             :         }
    1182             :     }
    1183             : 
    1184           5 :     return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    1185             : }
    1186             : 
    1187             : /************************************************************************/
    1188             : /*                            SetMetadata()                             */
    1189             : /************************************************************************/
    1190             : 
    1191           2 : CPLErr netCDFRasterBand::SetMetadata(CSLConstList papszMD,
    1192             :                                      const char *pszDomain)
    1193             : {
    1194           4 :     if (GetAccess() == GA_Update &&
    1195           2 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    1196             :     {
    1197             :         // We don't handle metadata item removal for now
    1198           4 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    1199             :              ++papszIter)
    1200             :         {
    1201           2 :             char *pszName = nullptr;
    1202           2 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    1203           2 :             if (pszName && pszValue)
    1204           2 :                 SetMetadataItem(pszName, pszValue);
    1205           2 :             CPLFree(pszName);
    1206             :         }
    1207             :     }
    1208           2 :     return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
    1209             : }
    1210             : 
    1211             : /************************************************************************/
    1212             : /*                             GetOffset()                              */
    1213             : /************************************************************************/
    1214          54 : double netCDFRasterBand::GetOffset(int *pbSuccess)
    1215             : {
    1216          54 :     if (pbSuccess != nullptr)
    1217          49 :         *pbSuccess = static_cast<int>(m_bHaveOffset);
    1218             : 
    1219          54 :     return m_dfOffset;
    1220             : }
    1221             : 
    1222             : /************************************************************************/
    1223             : /*                             SetOffset()                              */
    1224             : /************************************************************************/
    1225           1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
    1226             : {
    1227           2 :     CPLMutexHolderD(&hNCMutex);
    1228             : 
    1229             :     // Write value if in update mode.
    1230           1 :     if (poDS->GetAccess() == GA_Update)
    1231             :     {
    1232             :         // Make sure we are in define mode.
    1233           1 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1234             : 
    1235           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
    1236             :                                              NC_DOUBLE, 1, &dfNewOffset);
    1237             : 
    1238           1 :         NCDF_ERR(status);
    1239           1 :         if (status == NC_NOERR)
    1240             :         {
    1241           1 :             SetOffsetNoUpdate(dfNewOffset);
    1242           1 :             return CE_None;
    1243             :         }
    1244             : 
    1245           0 :         return CE_Failure;
    1246             :     }
    1247             : 
    1248           0 :     SetOffsetNoUpdate(dfNewOffset);
    1249           0 :     return CE_None;
    1250             : }
    1251             : 
    1252             : /************************************************************************/
    1253             : /*                         SetOffsetNoUpdate()                          */
    1254             : /************************************************************************/
    1255          17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
    1256             : {
    1257          17 :     m_dfOffset = dfVal;
    1258          17 :     m_bHaveOffset = true;
    1259          17 : }
    1260             : 
    1261             : /************************************************************************/
    1262             : /*                              GetScale()                              */
    1263             : /************************************************************************/
    1264          54 : double netCDFRasterBand::GetScale(int *pbSuccess)
    1265             : {
    1266          54 :     if (pbSuccess != nullptr)
    1267          49 :         *pbSuccess = static_cast<int>(m_bHaveScale);
    1268             : 
    1269          54 :     return m_dfScale;
    1270             : }
    1271             : 
    1272             : /************************************************************************/
    1273             : /*                              SetScale()                              */
    1274             : /************************************************************************/
    1275           1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
    1276             : {
    1277           2 :     CPLMutexHolderD(&hNCMutex);
    1278             : 
    1279             :     // Write value if in update mode.
    1280           1 :     if (poDS->GetAccess() == GA_Update)
    1281             :     {
    1282             :         // Make sure we are in define mode.
    1283           1 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1284             : 
    1285           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
    1286             :                                              NC_DOUBLE, 1, &dfNewScale);
    1287             : 
    1288           1 :         NCDF_ERR(status);
    1289           1 :         if (status == NC_NOERR)
    1290             :         {
    1291           1 :             SetScaleNoUpdate(dfNewScale);
    1292           1 :             return CE_None;
    1293             :         }
    1294             : 
    1295           0 :         return CE_Failure;
    1296             :     }
    1297             : 
    1298           0 :     SetScaleNoUpdate(dfNewScale);
    1299           0 :     return CE_None;
    1300             : }
    1301             : 
    1302             : /************************************************************************/
    1303             : /*                          SetScaleNoUpdate()                          */
    1304             : /************************************************************************/
    1305          21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
    1306             : {
    1307          21 :     m_dfScale = dfVal;
    1308          21 :     m_bHaveScale = true;
    1309          21 : }
    1310             : 
    1311             : /************************************************************************/
    1312             : /*                            GetUnitType()                             */
    1313             : /************************************************************************/
    1314             : 
    1315          26 : const char *netCDFRasterBand::GetUnitType()
    1316             : 
    1317             : {
    1318          26 :     if (!m_osUnitType.empty())
    1319           6 :         return m_osUnitType;
    1320             : 
    1321          20 :     return GDALRasterBand::GetUnitType();
    1322             : }
    1323             : 
    1324             : /************************************************************************/
    1325             : /*                            SetUnitType()                             */
    1326             : /************************************************************************/
    1327             : 
    1328           1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
    1329             : 
    1330             : {
    1331           2 :     CPLMutexHolderD(&hNCMutex);
    1332             : 
    1333           2 :     const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1334             : 
    1335           1 :     if (!osUnitType.empty())
    1336             :     {
    1337             :         // Write value if in update mode.
    1338           1 :         if (poDS->GetAccess() == GA_Update)
    1339             :         {
    1340             :             // Make sure we are in define mode.
    1341           1 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
    1342             : 
    1343           1 :             const int status = nc_put_att_text(
    1344             :                 cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
    1345             : 
    1346           1 :             NCDF_ERR(status);
    1347           1 :             if (status == NC_NOERR)
    1348             :             {
    1349           1 :                 SetUnitTypeNoUpdate(pszNewValue);
    1350           1 :                 return CE_None;
    1351             :             }
    1352             : 
    1353           0 :             return CE_Failure;
    1354             :         }
    1355             :     }
    1356             : 
    1357           0 :     SetUnitTypeNoUpdate(pszNewValue);
    1358             : 
    1359           0 :     return CE_None;
    1360             : }
    1361             : 
    1362             : /************************************************************************/
    1363             : /*                        SetUnitTypeNoUpdate()                         */
    1364             : /************************************************************************/
    1365             : 
    1366         499 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
    1367             : {
    1368         499 :     m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1369         499 : }
    1370             : 
    1371             : /************************************************************************/
    1372             : /*                           GetNoDataValue()                           */
    1373             : /************************************************************************/
    1374             : 
    1375         193 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
    1376             : 
    1377             : {
    1378         193 :     if (m_bNoDataSetAsInt64)
    1379             :     {
    1380           0 :         if (pbSuccess)
    1381           0 :             *pbSuccess = TRUE;
    1382           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
    1383             :     }
    1384             : 
    1385         193 :     if (m_bNoDataSetAsUInt64)
    1386             :     {
    1387           0 :         if (pbSuccess)
    1388           0 :             *pbSuccess = TRUE;
    1389           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
    1390             :     }
    1391             : 
    1392         193 :     if (m_bNoDataSet)
    1393             :     {
    1394         144 :         if (pbSuccess)
    1395         127 :             *pbSuccess = TRUE;
    1396         144 :         return m_dfNoDataValue;
    1397             :     }
    1398             : 
    1399          49 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
    1400             : }
    1401             : 
    1402             : /************************************************************************/
    1403             : /*                       GetNoDataValueAsInt64()                        */
    1404             : /************************************************************************/
    1405             : 
    1406           4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
    1407             : 
    1408             : {
    1409           4 :     if (m_bNoDataSetAsInt64)
    1410             :     {
    1411           4 :         if (pbSuccess)
    1412           4 :             *pbSuccess = TRUE;
    1413             : 
    1414           4 :         return m_nNodataValueInt64;
    1415             :     }
    1416             : 
    1417           0 :     return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
    1418             : }
    1419             : 
    1420             : /************************************************************************/
    1421             : /*                       GetNoDataValueAsUInt64()                       */
    1422             : /************************************************************************/
    1423             : 
    1424           4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
    1425             : 
    1426             : {
    1427           4 :     if (m_bNoDataSetAsUInt64)
    1428             :     {
    1429           4 :         if (pbSuccess)
    1430           4 :             *pbSuccess = TRUE;
    1431             : 
    1432           4 :         return m_nNodataValueUInt64;
    1433             :     }
    1434             : 
    1435           0 :     return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
    1436             : }
    1437             : 
    1438             : /************************************************************************/
    1439             : /*                           SetNoDataValue()                           */
    1440             : /************************************************************************/
    1441             : 
    1442         134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
    1443             : 
    1444             : {
    1445         268 :     CPLMutexHolderD(&hNCMutex);
    1446             : 
    1447             :     // If already set to new value, don't do anything.
    1448         134 :     if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
    1449          19 :         return CE_None;
    1450             : 
    1451             :     // Write value if in update mode.
    1452         115 :     if (poDS->GetAccess() == GA_Update)
    1453             :     {
    1454             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1455             :         // but it is ok if variable has not been written to, so only print
    1456             :         // debug. See bug #4484.
    1457         125 :         if (m_bNoDataSet &&
    1458          10 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1459             :         {
    1460           0 :             CPLDebug("GDAL_netCDF",
    1461             :                      "Setting NoDataValue to %.17g (previously set to %.17g) "
    1462             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1463             :                      dfNoData, m_dfNoDataValue, cdfid, nBand);
    1464             :         }
    1465             : #ifdef NCDF_DEBUG
    1466             :         else
    1467             :         {
    1468             :             CPLDebug("GDAL_netCDF",
    1469             :                      "Setting NoDataValue to %.17g (id #%d, band #%d)",
    1470             :                      dfNoData, cdfid, nBand);
    1471             :         }
    1472             : #endif
    1473             :         // Make sure we are in define mode.
    1474         115 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1475             : 
    1476             :         int status;
    1477         115 :         if (eDataType == GDT_UInt8)
    1478             :         {
    1479           6 :             if (bSignedData)
    1480             :             {
    1481           0 :                 signed char cNoDataValue = static_cast<signed char>(dfNoData);
    1482           0 :                 status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
    1483             :                                           nc_datatype, 1, &cNoDataValue);
    1484             :             }
    1485             :             else
    1486             :             {
    1487           6 :                 const unsigned char ucNoDataValue =
    1488           6 :                     static_cast<unsigned char>(dfNoData);
    1489           6 :                 status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
    1490             :                                           nc_datatype, 1, &ucNoDataValue);
    1491             :             }
    1492             :         }
    1493         109 :         else if (eDataType == GDT_Int16)
    1494             :         {
    1495          14 :             short nsNoDataValue = static_cast<short>(dfNoData);
    1496          14 :             status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1497             :                                       1, &nsNoDataValue);
    1498             :         }
    1499          95 :         else if (eDataType == GDT_Int32)
    1500             :         {
    1501          27 :             int nNoDataValue = static_cast<int>(dfNoData);
    1502          27 :             status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
    1503             :                                     &nNoDataValue);
    1504             :         }
    1505          68 :         else if (eDataType == GDT_Float32)
    1506             :         {
    1507          31 :             float fNoDataValue = static_cast<float>(dfNoData);
    1508          31 :             status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1509             :                                       1, &fNoDataValue);
    1510             :         }
    1511          43 :         else if (eDataType == GDT_UInt16 &&
    1512           6 :                  cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
    1513             :                      NCDF_FORMAT_NC4)
    1514             :         {
    1515           6 :             unsigned short usNoDataValue =
    1516           6 :                 static_cast<unsigned short>(dfNoData);
    1517           6 :             status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1518             :                                        1, &usNoDataValue);
    1519             :         }
    1520          38 :         else if (eDataType == GDT_UInt32 &&
    1521           7 :                  cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
    1522             :                      NCDF_FORMAT_NC4)
    1523             :         {
    1524           7 :             unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
    1525           7 :             status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1526             :                                      1, &unNoDataValue);
    1527             :         }
    1528             :         else
    1529             :         {
    1530          24 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1531             :                                        1, &dfNoData);
    1532             :         }
    1533             : 
    1534         115 :         NCDF_ERR(status);
    1535             : 
    1536             :         // Update status if write worked.
    1537         115 :         if (status == NC_NOERR)
    1538             :         {
    1539         115 :             SetNoDataValueNoUpdate(dfNoData);
    1540         115 :             return CE_None;
    1541             :         }
    1542             : 
    1543           0 :         return CE_Failure;
    1544             :     }
    1545             : 
    1546           0 :     SetNoDataValueNoUpdate(dfNoData);
    1547           0 :     return CE_None;
    1548             : }
    1549             : 
    1550             : /************************************************************************/
    1551             : /*                       SetNoDataValueNoUpdate()                       */
    1552             : /************************************************************************/
    1553             : 
    1554         443 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
    1555             : {
    1556         443 :     m_dfNoDataValue = dfNoData;
    1557         443 :     m_bNoDataSet = true;
    1558         443 :     m_bNoDataSetAsInt64 = false;
    1559         443 :     m_bNoDataSetAsUInt64 = false;
    1560         443 : }
    1561             : 
    1562             : /************************************************************************/
    1563             : /*                       SetNoDataValueAsInt64()                        */
    1564             : /************************************************************************/
    1565             : 
    1566           3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    1567             : 
    1568             : {
    1569           6 :     CPLMutexHolderD(&hNCMutex);
    1570             : 
    1571             :     // If already set to new value, don't do anything.
    1572           3 :     if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
    1573           0 :         return CE_None;
    1574             : 
    1575             :     // Write value if in update mode.
    1576           3 :     if (poDS->GetAccess() == GA_Update)
    1577             :     {
    1578             :         // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
    1579             :         // but it is ok if variable has not been written to, so only print
    1580             :         // debug. See bug #4484.
    1581           3 :         if (m_bNoDataSetAsInt64 &&
    1582           0 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1583             :         {
    1584           0 :             CPLDebug("GDAL_netCDF",
    1585             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1586             :                      " (previously set to " CPL_FRMT_GIB ") "
    1587             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1588             :                      static_cast<GIntBig>(nNoData),
    1589           0 :                      static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
    1590             :         }
    1591             : #ifdef NCDF_DEBUG
    1592             :         else
    1593             :         {
    1594             :             CPLDebug("GDAL_netCDF",
    1595             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1596             :                      " (id #%d, band #%d)",
    1597             :                      static_cast<GIntBig>(nNoData), cdfid, nBand);
    1598             :         }
    1599             : #endif
    1600             :         // Make sure we are in define mode.
    1601           3 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1602             : 
    1603             :         int status;
    1604           6 :         if (eDataType == GDT_Int64 &&
    1605           3 :             cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1606             :         {
    1607           3 :             long long tmp = static_cast<long long>(nNoData);
    1608           3 :             status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
    1609             :                                          nc_datatype, 1, &tmp);
    1610             :         }
    1611             :         else
    1612             :         {
    1613           0 :             double dfNoData = static_cast<double>(nNoData);
    1614           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1615             :                                        1, &dfNoData);
    1616             :         }
    1617             : 
    1618           3 :         NCDF_ERR(status);
    1619             : 
    1620             :         // Update status if write worked.
    1621           3 :         if (status == NC_NOERR)
    1622             :         {
    1623           3 :             SetNoDataValueNoUpdate(nNoData);
    1624           3 :             return CE_None;
    1625             :         }
    1626             : 
    1627           0 :         return CE_Failure;
    1628             :     }
    1629             : 
    1630           0 :     SetNoDataValueNoUpdate(nNoData);
    1631           0 :     return CE_None;
    1632             : }
    1633             : 
    1634             : /************************************************************************/
    1635             : /*                       SetNoDataValueNoUpdate()                       */
    1636             : /************************************************************************/
    1637             : 
    1638          11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
    1639             : {
    1640          11 :     m_nNodataValueInt64 = nNoData;
    1641          11 :     m_bNoDataSet = false;
    1642          11 :     m_bNoDataSetAsInt64 = true;
    1643          11 :     m_bNoDataSetAsUInt64 = false;
    1644          11 : }
    1645             : 
    1646             : /************************************************************************/
    1647             : /*                       SetNoDataValueAsUInt64()                       */
    1648             : /************************************************************************/
    1649             : 
    1650           3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    1651             : 
    1652             : {
    1653           6 :     CPLMutexHolderD(&hNCMutex);
    1654             : 
    1655             :     // If already set to new value, don't do anything.
    1656           3 :     if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
    1657           0 :         return CE_None;
    1658             : 
    1659             :     // Write value if in update mode.
    1660           3 :     if (poDS->GetAccess() == GA_Update)
    1661             :     {
    1662             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1663             :         // but it is ok if variable has not been written to, so only print
    1664             :         // debug. See bug #4484.
    1665           3 :         if (m_bNoDataSetAsUInt64 &&
    1666           0 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1667             :         {
    1668           0 :             CPLDebug("GDAL_netCDF",
    1669             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1670             :                      " (previously set to " CPL_FRMT_GUIB ") "
    1671             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1672             :                      static_cast<GUIntBig>(nNoData),
    1673           0 :                      static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
    1674             :         }
    1675             : #ifdef NCDF_DEBUG
    1676             :         else
    1677             :         {
    1678             :             CPLDebug("GDAL_netCDF",
    1679             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1680             :                      " (id #%d, band #%d)",
    1681             :                      static_cast<GUIntBig>(nNoData), cdfid, nBand);
    1682             :         }
    1683             : #endif
    1684             :         // Make sure we are in define mode.
    1685           3 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1686             : 
    1687             :         int status;
    1688           6 :         if (eDataType == GDT_UInt64 &&
    1689           3 :             cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1690             :         {
    1691           3 :             unsigned long long tmp = static_cast<long long>(nNoData);
    1692           3 :             status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
    1693             :                                           nc_datatype, 1, &tmp);
    1694             :         }
    1695             :         else
    1696             :         {
    1697           0 :             double dfNoData = static_cast<double>(nNoData);
    1698           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1699             :                                        1, &dfNoData);
    1700             :         }
    1701             : 
    1702           3 :         NCDF_ERR(status);
    1703             : 
    1704             :         // Update status if write worked.
    1705           3 :         if (status == NC_NOERR)
    1706             :         {
    1707           3 :             SetNoDataValueNoUpdate(nNoData);
    1708           3 :             return CE_None;
    1709             :         }
    1710             : 
    1711           0 :         return CE_Failure;
    1712             :     }
    1713             : 
    1714           0 :     SetNoDataValueNoUpdate(nNoData);
    1715           0 :     return CE_None;
    1716             : }
    1717             : 
    1718             : /************************************************************************/
    1719             : /*                       SetNoDataValueNoUpdate()                       */
    1720             : /************************************************************************/
    1721             : 
    1722          10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
    1723             : {
    1724          10 :     m_nNodataValueUInt64 = nNoData;
    1725          10 :     m_bNoDataSet = false;
    1726          10 :     m_bNoDataSetAsInt64 = false;
    1727          10 :     m_bNoDataSetAsUInt64 = true;
    1728          10 : }
    1729             : 
    1730             : /************************************************************************/
    1731             : /*                         DeleteNoDataValue()                          */
    1732             : /************************************************************************/
    1733             : 
    1734             : #ifdef notdef
    1735             : CPLErr netCDFRasterBand::DeleteNoDataValue()
    1736             : 
    1737             : {
    1738             :     CPLMutexHolderD(&hNCMutex);
    1739             : 
    1740             :     if (!bNoDataSet)
    1741             :         return CE_None;
    1742             : 
    1743             :     // Write value if in update mode.
    1744             :     if (poDS->GetAccess() == GA_Update)
    1745             :     {
    1746             :         // Make sure we are in define mode.
    1747             :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1748             : 
    1749             :         status = nc_del_att(cdfid, nZId, NCDF_FillValue);
    1750             : 
    1751             :         NCDF_ERR(status);
    1752             : 
    1753             :         // Update status if write worked.
    1754             :         if (status == NC_NOERR)
    1755             :         {
    1756             :             dfNoDataValue = 0.0;
    1757             :             bNoDataSet = false;
    1758             :             return CE_None;
    1759             :         }
    1760             : 
    1761             :         return CE_Failure;
    1762             :     }
    1763             : 
    1764             :     dfNoDataValue = 0.0;
    1765             :     bNoDataSet = false;
    1766             :     return CE_None;
    1767             : }
    1768             : #endif
    1769             : 
    1770             : /************************************************************************/
    1771             : /*                           SerializeToXML()                           */
    1772             : /************************************************************************/
    1773             : 
    1774           5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
    1775             : {
    1776             :     // Overridden from GDALPamDataset to add only band histogram
    1777             :     // and statistics. See bug #4244.
    1778           5 :     if (psPam == nullptr)
    1779           0 :         return nullptr;
    1780             : 
    1781             :     // Setup root node and attributes.
    1782             :     CPLXMLNode *psTree =
    1783           5 :         CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
    1784             : 
    1785           5 :     if (GetBand() > 0)
    1786             :     {
    1787          10 :         CPLString oFmt;
    1788           5 :         CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
    1789             :     }
    1790             : 
    1791             :     // Histograms.
    1792           5 :     if (psPam->psSavedHistograms != nullptr)
    1793           1 :         CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
    1794             : 
    1795             :     // Metadata (statistics only).
    1796           5 :     GDALMultiDomainMetadata oMDMDStats;
    1797           5 :     const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
    1798             :                                   "STATISTICS_MEAN", "STATISTICS_STDDEV",
    1799             :                                   nullptr};
    1800          25 :     for (int i = 0; i < CSLCount(papszMDStats); i++)
    1801             :     {
    1802          20 :         const char *pszMDI = GetMetadataItem(papszMDStats[i]);
    1803          20 :         if (pszMDI)
    1804           4 :             oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
    1805             :     }
    1806           5 :     CPLXMLNode *psMD = oMDMDStats.Serialize();
    1807             : 
    1808           5 :     if (psMD != nullptr)
    1809             :     {
    1810           1 :         if (psMD->psChild == nullptr)
    1811           0 :             CPLDestroyXMLNode(psMD);
    1812             :         else
    1813           1 :             CPLAddXMLChild(psTree, psMD);
    1814             :     }
    1815             : 
    1816             :     // We don't want to return anything if we had no metadata to attach.
    1817           5 :     if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
    1818             :     {
    1819           3 :         CPLDestroyXMLNode(psTree);
    1820           3 :         psTree = nullptr;
    1821             :     }
    1822             : 
    1823           5 :     return psTree;
    1824             : }
    1825             : 
    1826             : /************************************************************************/
    1827             : /*                  Get1DVariableIndexedByDimension()                   */
    1828             : /************************************************************************/
    1829             : 
    1830          81 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
    1831             :                                            const char *pszDimName,
    1832             :                                            bool bVerboseError, int *pnGroupID)
    1833             : {
    1834          81 :     *pnGroupID = -1;
    1835          81 :     int nVarID = -1;
    1836             :     // First try to find a variable whose name is identical to the dimension
    1837             :     // name, and check that it is indeed indexed by this dimension
    1838          81 :     if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
    1839             :     {
    1840          67 :         int nDimCountOfVariable = 0;
    1841          67 :         nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
    1842          67 :         if (nDimCountOfVariable == 1)
    1843             :         {
    1844          67 :             int nDimIdOfVariable = -1;
    1845          67 :             nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
    1846          67 :             if (nDimIdOfVariable == nDimId)
    1847             :             {
    1848          67 :                 return nVarID;
    1849             :             }
    1850             :         }
    1851             :     }
    1852             : 
    1853             :     // Otherwise iterate over the variables to find potential candidates
    1854             :     // TODO: should be modified to search also in other groups using the same
    1855             :     //       logic than in NCDFResolveVar(), but maybe not needed if it's a
    1856             :     //       very rare case? and I think this is not CF compliant.
    1857          14 :     int nvars = 0;
    1858          14 :     CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
    1859             : 
    1860          14 :     int nCountCandidateVars = 0;
    1861          14 :     int nCandidateVarID = -1;
    1862          65 :     for (int k = 0; k < nvars; k++)
    1863             :     {
    1864          51 :         int nDimCountOfVariable = 0;
    1865          51 :         nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
    1866          51 :         if (nDimCountOfVariable == 1)
    1867             :         {
    1868          27 :             int nDimIdOfVariable = -1;
    1869          27 :             nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
    1870          27 :             if (nDimIdOfVariable == nDimId)
    1871             :             {
    1872           7 :                 nCountCandidateVars++;
    1873           7 :                 nCandidateVarID = k;
    1874             :             }
    1875             :         }
    1876             :     }
    1877          14 :     if (nCountCandidateVars > 1)
    1878             :     {
    1879           1 :         if (bVerboseError)
    1880             :         {
    1881           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1882             :                      "Several 1D variables are indexed by dimension %s",
    1883             :                      pszDimName);
    1884             :         }
    1885           1 :         *pnGroupID = -1;
    1886           1 :         return -1;
    1887             :     }
    1888          13 :     else if (nCandidateVarID < 0)
    1889             :     {
    1890           8 :         if (bVerboseError)
    1891             :         {
    1892           8 :             CPLError(CE_Warning, CPLE_AppDefined,
    1893             :                      "No 1D variable is indexed by dimension %s", pszDimName);
    1894             :         }
    1895             :     }
    1896          13 :     *pnGroupID = cdfid;
    1897          13 :     return nCandidateVarID;
    1898             : }
    1899             : 
    1900             : /************************************************************************/
    1901             : /*                    CreateMetadataFromAttributes()                    */
    1902             : /************************************************************************/
    1903             : 
    1904         498 : void netCDFRasterBand::CreateMetadataFromAttributes()
    1905             : {
    1906         498 :     char szVarName[NC_MAX_NAME + 1] = {};
    1907         498 :     int status = nc_inq_varname(cdfid, nZId, szVarName);
    1908         498 :     NCDF_ERR(status);
    1909             : 
    1910         498 :     GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
    1911             : 
    1912             :     // Get attribute metadata.
    1913         498 :     int nAtt = 0;
    1914         498 :     NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
    1915             : 
    1916        2120 :     for (int i = 0; i < nAtt; i++)
    1917             :     {
    1918        1622 :         char szMetaName[NC_MAX_NAME + 1] = {};
    1919        1622 :         status = nc_inq_attname(cdfid, nZId, i, szMetaName);
    1920        1622 :         if (status != NC_NOERR)
    1921          12 :             continue;
    1922             : 
    1923        1622 :         if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
    1924             :         {
    1925          12 :             continue;
    1926             :         }
    1927             : 
    1928        1610 :         char *pszMetaValue = nullptr;
    1929        1610 :         if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
    1930             :         {
    1931        1610 :             GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
    1932             :         }
    1933             :         else
    1934             :         {
    1935           0 :             CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
    1936             :         }
    1937             : 
    1938        1610 :         if (pszMetaValue)
    1939             :         {
    1940        1610 :             CPLFree(pszMetaValue);
    1941        1610 :             pszMetaValue = nullptr;
    1942             :         }
    1943             :     }
    1944         498 : }
    1945             : 
    1946             : /************************************************************************/
    1947             : /*                    CreateMetadataFromOtherVars()                     */
    1948             : /************************************************************************/
    1949             : 
    1950          53 : void netCDFRasterBand::CreateMetadataFromOtherVars()
    1951             : 
    1952             : {
    1953          53 :     CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
    1954          53 :     m_bCreateMetadataFromOtherVarsDone = true;
    1955             : 
    1956          53 :     netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
    1957          53 :     const int nPamFlagsBackup = l_poDS->nPamFlags;
    1958             : 
    1959             :     // Compute all dimensions from Band number and save in Metadata.
    1960          53 :     int nd = 0;
    1961          53 :     nc_inq_varndims(cdfid, nZId, &nd);
    1962             :     // Compute multidimention band position.
    1963             :     //
    1964             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    1965             :     // if Data[2,3,4,x,y]
    1966             :     //
    1967             :     //  BandPos0 = (nBand) / (3*4)
    1968             :     //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)
    1969             :     //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)
    1970             : 
    1971          53 :     int Sum = 1;
    1972          53 :     if (nd == 3)
    1973             :     {
    1974           5 :         Sum *= panBandZLev[0];
    1975             :     }
    1976             : 
    1977             :     // Loop over non-spatial dimensions.
    1978          53 :     int Taken = 0;
    1979             : 
    1980          93 :     for (int i = 0; i < nd - 2; i++)
    1981             :     {
    1982             :         int result;
    1983          40 :         if (i != nd - 2 - 1)
    1984             :         {
    1985          18 :             Sum = 1;
    1986          37 :             for (int j = i + 1; j < nd - 2; j++)
    1987             :             {
    1988          19 :                 Sum *= panBandZLev[j];
    1989             :             }
    1990          18 :             result = static_cast<int>((nLevel - Taken) / Sum);
    1991             :         }
    1992             :         else
    1993             :         {
    1994          22 :             result = static_cast<int>((nLevel - Taken) % Sum);
    1995             :         }
    1996             : 
    1997          40 :         char szName[NC_MAX_NAME + 1] = {};
    1998          40 :         snprintf(szName, sizeof(szName), "%s",
    1999          40 :                  l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
    2000             : 
    2001             :         char szMetaName[NC_MAX_NAME + 1 + 32];
    2002          40 :         snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
    2003             : 
    2004          40 :         const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
    2005          40 :         const int nVarID = l_poDS->m_anExtraDimVarIds[i];
    2006          40 :         if (nVarID < 0)
    2007             :         {
    2008           2 :             GDALPamRasterBand::SetMetadataItem(szMetaName,
    2009             :                                                CPLSPrintf("%d", result + 1));
    2010             :         }
    2011             :         else
    2012             :         {
    2013             :             // TODO: Make sure all the status checks make sense.
    2014             : 
    2015          38 :             nc_type nVarType = NC_NAT;
    2016          38 :             /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
    2017             : 
    2018          38 :             int nDims = 0;
    2019          38 :             /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
    2020             : 
    2021          38 :             char szMetaTemp[256] = {};
    2022          38 :             if (nDims == 1)
    2023             :             {
    2024          38 :                 size_t count[1] = {1};
    2025          38 :                 size_t start[1] = {static_cast<size_t>(result)};
    2026             : 
    2027          38 :                 switch (nVarType)
    2028             :                 {
    2029           0 :                     case NC_BYTE:
    2030             :                         // TODO: Check for signed/unsigned byte.
    2031             :                         signed char cData;
    2032           0 :                         /* status = */ nc_get_vara_schar(nGroupID, nVarID,
    2033             :                                                          start, count, &cData);
    2034           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
    2035           0 :                         break;
    2036           0 :                     case NC_SHORT:
    2037             :                         short sData;
    2038           0 :                         /* status = */ nc_get_vara_short(nGroupID, nVarID,
    2039             :                                                          start, count, &sData);
    2040           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
    2041           0 :                         break;
    2042          19 :                     case NC_INT:
    2043             :                     {
    2044             :                         int nData;
    2045          19 :                         /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
    2046             :                                                        count, &nData);
    2047          19 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
    2048          19 :                         break;
    2049             :                     }
    2050           0 :                     case NC_FLOAT:
    2051             :                         float fData;
    2052           0 :                         /* status = */ nc_get_vara_float(nGroupID, nVarID,
    2053             :                                                          start, count, &fData);
    2054           0 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
    2055             :                                     fData);
    2056           0 :                         break;
    2057          18 :                     case NC_DOUBLE:
    2058             :                         double dfData;
    2059          18 :                         /* status = */ nc_get_vara_double(
    2060             :                             nGroupID, nVarID, start, count, &dfData);
    2061          18 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
    2062             :                                     dfData);
    2063          18 :                         break;
    2064           0 :                     case NC_UBYTE:
    2065             :                         unsigned char ucData;
    2066           0 :                         /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
    2067             :                                                          start, count, &ucData);
    2068           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
    2069           0 :                         break;
    2070           0 :                     case NC_USHORT:
    2071             :                         unsigned short usData;
    2072           0 :                         /* status = */ nc_get_vara_ushort(
    2073             :                             nGroupID, nVarID, start, count, &usData);
    2074           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
    2075           0 :                         break;
    2076           0 :                     case NC_UINT:
    2077             :                     {
    2078             :                         unsigned int unData;
    2079           0 :                         /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
    2080             :                                                         count, &unData);
    2081           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
    2082           0 :                         break;
    2083             :                     }
    2084           1 :                     case NC_INT64:
    2085             :                     {
    2086             :                         long long nData;
    2087           1 :                         /* status = */ nc_get_vara_longlong(
    2088             :                             nGroupID, nVarID, start, count, &nData);
    2089           1 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
    2090             :                                  nData);
    2091           1 :                         break;
    2092             :                     }
    2093           0 :                     case NC_UINT64:
    2094             :                     {
    2095             :                         unsigned long long unData;
    2096           0 :                         /* status = */ nc_get_vara_ulonglong(
    2097             :                             nGroupID, nVarID, start, count, &unData);
    2098           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
    2099             :                                  unData);
    2100           0 :                         break;
    2101             :                     }
    2102           0 :                     default:
    2103           0 :                         CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
    2104             :                                  szMetaTemp, nVarType);
    2105           0 :                         break;
    2106             :                 }
    2107             :             }
    2108             :             else
    2109             :             {
    2110           0 :                 snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
    2111             :             }
    2112             : 
    2113             :             // Save dimension value.
    2114             :             // NOTE: removed #original_units as not part of CF-1.
    2115             : 
    2116          38 :             GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
    2117             :         }
    2118             : 
    2119             :         // Avoid int32 overflow. Perhaps something more sensible to do here ?
    2120          40 :         if (result > 0 && Sum > INT_MAX / result)
    2121           0 :             break;
    2122          40 :         if (Taken > INT_MAX - result * Sum)
    2123           0 :             break;
    2124             : 
    2125          40 :         Taken += result * Sum;
    2126             :     }  // End loop non-spatial dimensions.
    2127             : 
    2128          53 :     l_poDS->nPamFlags = nPamFlagsBackup;
    2129          53 : }
    2130             : 
    2131             : /************************************************************************/
    2132             : /*                             CheckData()                              */
    2133             : /************************************************************************/
    2134             : template <class T>
    2135        5912 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
    2136             :                                  size_t nTmpBlockXSize, size_t nTmpBlockYSize,
    2137             :                                  bool bCheckIsNan)
    2138             : {
    2139        5912 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2140             : 
    2141             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2142             :     // the data this is because partial blocks are not arranged the same way in
    2143             :     // netcdf and gdal.
    2144        5912 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2145             :     {
    2146           6 :         T *ptrWrite = static_cast<T *>(pImage);
    2147           6 :         T *ptrRead = static_cast<T *>(pImageNC);
    2148          29 :         for (size_t j = 0; j < nTmpBlockYSize;
    2149          23 :              j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
    2150             :         {
    2151          23 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
    2152             :         }
    2153             :     }
    2154             : 
    2155             :     // Is valid data checking needed or requested?
    2156        5912 :     if (bValidRangeValid || bCheckIsNan)
    2157             :     {
    2158        1345 :         T *ptrImage = static_cast<T *>(pImage);
    2159        2744 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2160             :         {
    2161             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2162        1399 :             size_t k = j * nBlockXSize;
    2163       98618 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2164             :             {
    2165             :                 // Check for nodata and nan.
    2166       97219 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2167        6301 :                     continue;
    2168       90918 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2169             :                 {
    2170        5737 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2171        5737 :                     continue;
    2172             :                 }
    2173             :                 // Check for valid_range.
    2174       85181 :                 if (bValidRangeValid)
    2175             :                 {
    2176       40986 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2177       40986 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2178       40983 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2179       40983 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2180             :                     {
    2181           4 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2182             :                     }
    2183             :                 }
    2184             :             }
    2185             :         }
    2186             :     }
    2187             : 
    2188             :     // If minimum longitude is > 180, subtract 360 from all.
    2189             :     // If not, disable checking for further calls (check just once).
    2190             :     // Only check first and last block elements since lon must be monotonic.
    2191        5912 :     const bool bIsSigned = std::numeric_limits<T>::is_signed;
    2192        5581 :     if (bCheckLongitude && bIsSigned &&
    2193          11 :         !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
    2194          10 :         !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
    2195        2796 :                     m_dfNoDataValue) &&
    2196          10 :         std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
    2197             :     {
    2198           0 :         T *ptrImage = static_cast<T *>(pImage);
    2199           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2200             :         {
    2201           0 :             size_t k = j * nBlockXSize;
    2202           0 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2203             :             {
    2204           0 :                 if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2205           0 :                     ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
    2206             :             }
    2207             :         }
    2208             :     }
    2209             :     else
    2210             :     {
    2211        5912 :         bCheckLongitude = false;
    2212             :     }
    2213        5912 : }
    2214             : 
    2215             : /************************************************************************/
    2216             : /*                            CheckDataCpx()                            */
    2217             : /************************************************************************/
    2218             : template <class T>
    2219          25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
    2220             :                                     size_t nTmpBlockXSize,
    2221             :                                     size_t nTmpBlockYSize, bool bCheckIsNan)
    2222             : {
    2223          25 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2224             : 
    2225             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2226             :     // the data this is because partial blocks are not arranged the same way in
    2227             :     // netcdf and gdal.
    2228          25 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2229             :     {
    2230           0 :         T *ptrWrite = static_cast<T *>(pImage);
    2231           0 :         T *ptrRead = static_cast<T *>(pImageNC);
    2232           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++,
    2233           0 :                     ptrWrite += (2 * nBlockXSize),
    2234           0 :                     ptrRead += (2 * nTmpBlockXSize))
    2235             :         {
    2236           0 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
    2237             :         }
    2238             :     }
    2239             : 
    2240             :     // Is valid data checking needed or requested?
    2241          25 :     if (bValidRangeValid || bCheckIsNan)
    2242             :     {
    2243           0 :         T *ptrImage = static_cast<T *>(pImage);
    2244           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2245             :         {
    2246             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2247           0 :             size_t k = 2 * j * nBlockXSize;
    2248           0 :             for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
    2249             :             {
    2250             :                 // Check for nodata and nan.
    2251           0 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2252           0 :                     continue;
    2253           0 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2254             :                 {
    2255           0 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2256           0 :                     continue;
    2257             :                 }
    2258             :                 // Check for valid_range.
    2259           0 :                 if (bValidRangeValid)
    2260             :                 {
    2261           0 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2262           0 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2263           0 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2264           0 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2265             :                     {
    2266           0 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2267             :                     }
    2268             :                 }
    2269             :             }
    2270             :         }
    2271             :     }
    2272          25 : }
    2273             : 
    2274             : /************************************************************************/
    2275             : /*                          FetchNetcdfChunk()                          */
    2276             : /************************************************************************/
    2277             : 
    2278        5937 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
    2279             :                                         void *pImage)
    2280             : {
    2281        5937 :     size_t start[MAX_NC_DIMS] = {};
    2282        5937 :     size_t edge[MAX_NC_DIMS] = {};
    2283             : 
    2284        5937 :     start[nBandXPos] = xstart;
    2285        5937 :     edge[nBandXPos] = nBlockXSize;
    2286        5937 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2287           6 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2288        5937 :     if (nBandYPos >= 0)
    2289             :     {
    2290        5933 :         start[nBandYPos] = ystart;
    2291        5933 :         edge[nBandYPos] = nBlockYSize;
    2292        5933 :         if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2293           4 :             edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2294             :     }
    2295        5937 :     const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
    2296             : 
    2297             : #ifdef NCDF_DEBUG
    2298             :     CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
    2299             :              start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
    2300             :              edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
    2301             : #endif
    2302             : 
    2303        5937 :     int nd = 0;
    2304        5937 :     nc_inq_varndims(cdfid, nZId, &nd);
    2305        5937 :     if (nd == 3)
    2306             :     {
    2307        1078 :         start[panBandZPos[0]] = nLevel;  // z
    2308        1078 :         edge[panBandZPos[0]] = 1;
    2309             :     }
    2310             : 
    2311             :     // Compute multidimention band position.
    2312             :     //
    2313             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2314             :     // if Data[2,3,4,x,y]
    2315             :     //
    2316             :     //  BandPos0 = (nBand) / (3*4)
    2317             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2318             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2319        5937 :     if (nd > 3)
    2320             :     {
    2321         160 :         int Sum = -1;
    2322         160 :         int Taken = 0;
    2323         480 :         for (int i = 0; i < nd - 2; i++)
    2324             :         {
    2325         320 :             if (i != nd - 2 - 1)
    2326             :             {
    2327         160 :                 Sum = 1;
    2328         320 :                 for (int j = i + 1; j < nd - 2; j++)
    2329             :                 {
    2330         160 :                     Sum *= panBandZLev[j];
    2331             :                 }
    2332         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2333         160 :                 edge[panBandZPos[i]] = 1;
    2334             :             }
    2335             :             else
    2336             :             {
    2337         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2338         160 :                 edge[panBandZPos[i]] = 1;
    2339             :             }
    2340         320 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2341             :         }
    2342             :     }
    2343             : 
    2344             :     // Make sure we are in data mode.
    2345        5937 :     cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2346             : 
    2347             :     // If this block is not a full block in the x axis, we need to
    2348             :     // re-arrange the data because partial blocks are not arranged the
    2349             :     // same way in netcdf and gdal, so we first we read the netcdf data at
    2350             :     // the end of the gdal block buffer then re-arrange rows in CheckData().
    2351        5937 :     void *pImageNC = pImage;
    2352        5937 :     if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
    2353             :     {
    2354           6 :         pImageNC = static_cast<GByte *>(pImage) +
    2355           6 :                    ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
    2356          12 :                      edge[nBandXPos] * nYChunkSize) *
    2357           6 :                     GDALGetDataTypeSizeBytes(eDataType));
    2358             :     }
    2359             : 
    2360             :     // Read data according to type.
    2361             :     int status;
    2362        5937 :     if (eDataType == GDT_UInt8)
    2363             :     {
    2364        3105 :         if (bSignedData)
    2365             :         {
    2366           0 :             status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2367             :                                        static_cast<signed char *>(pImageNC));
    2368           0 :             if (status == NC_NOERR)
    2369           0 :                 CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2370             :                                        nYChunkSize, false);
    2371             :         }
    2372             :         else
    2373             :         {
    2374        3105 :             status = nc_get_vara_uchar(cdfid, nZId, start, edge,
    2375             :                                        static_cast<unsigned char *>(pImageNC));
    2376        3105 :             if (status == NC_NOERR)
    2377        3105 :                 CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
    2378             :                                          nYChunkSize, false);
    2379             :         }
    2380             :     }
    2381        2832 :     else if (eDataType == GDT_Int8)
    2382             :     {
    2383          60 :         status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2384             :                                    static_cast<signed char *>(pImageNC));
    2385          60 :         if (status == NC_NOERR)
    2386          60 :             CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2387             :                                    nYChunkSize, false);
    2388             :     }
    2389        2772 :     else if (nc_datatype == NC_SHORT)
    2390             :     {
    2391         465 :         status = nc_get_vara_short(cdfid, nZId, start, edge,
    2392             :                                    static_cast<short *>(pImageNC));
    2393         465 :         if (status == NC_NOERR)
    2394             :         {
    2395         465 :             if (eDataType == GDT_Int16)
    2396             :             {
    2397         462 :                 CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
    2398             :                                   nYChunkSize, false);
    2399             :             }
    2400             :             else
    2401             :             {
    2402           3 :                 CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
    2403             :                                    nYChunkSize, false);
    2404             :             }
    2405             :         }
    2406             :     }
    2407        2307 :     else if (eDataType == GDT_Int32)
    2408             :     {
    2409             : #if SIZEOF_UNSIGNED_LONG == 4
    2410             :         status = nc_get_vara_long(cdfid, nZId, start, edge,
    2411             :                                   static_cast<long *>(pImageNC));
    2412             :         if (status == NC_NOERR)
    2413             :             CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2414             :                             false);
    2415             : #else
    2416         912 :         status = nc_get_vara_int(cdfid, nZId, start, edge,
    2417             :                                  static_cast<int *>(pImageNC));
    2418         912 :         if (status == NC_NOERR)
    2419         912 :             CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2420             :                            false);
    2421             : #endif
    2422             :     }
    2423        1395 :     else if (eDataType == GDT_Float32)
    2424             :     {
    2425        1258 :         status = nc_get_vara_float(cdfid, nZId, start, edge,
    2426             :                                    static_cast<float *>(pImageNC));
    2427        1258 :         if (status == NC_NOERR)
    2428        1258 :             CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2429             :                              true);
    2430             :     }
    2431         137 :     else if (eDataType == GDT_Float64)
    2432             :     {
    2433          86 :         status = nc_get_vara_double(cdfid, nZId, start, edge,
    2434             :                                     static_cast<double *>(pImageNC));
    2435          86 :         if (status == NC_NOERR)
    2436          86 :             CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2437             :                               true);
    2438             :     }
    2439          51 :     else if (eDataType == GDT_UInt16)
    2440             :     {
    2441           6 :         status = nc_get_vara_ushort(cdfid, nZId, start, edge,
    2442             :                                     static_cast<unsigned short *>(pImageNC));
    2443           6 :         if (status == NC_NOERR)
    2444           6 :             CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
    2445             :                                       nYChunkSize, false);
    2446             :     }
    2447          45 :     else if (eDataType == GDT_UInt32)
    2448             :     {
    2449           6 :         status = nc_get_vara_uint(cdfid, nZId, start, edge,
    2450             :                                   static_cast<unsigned int *>(pImageNC));
    2451           6 :         if (status == NC_NOERR)
    2452           6 :             CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
    2453             :                                     nYChunkSize, false);
    2454             :     }
    2455          39 :     else if (eDataType == GDT_Int64)
    2456             :     {
    2457           7 :         status = nc_get_vara_longlong(cdfid, nZId, start, edge,
    2458             :                                       static_cast<long long *>(pImageNC));
    2459           7 :         if (status == NC_NOERR)
    2460           7 :             CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
    2461             :                                     nYChunkSize, false);
    2462             :     }
    2463          32 :     else if (eDataType == GDT_UInt64)
    2464             :     {
    2465             :         status =
    2466           7 :             nc_get_vara_ulonglong(cdfid, nZId, start, edge,
    2467             :                                   static_cast<unsigned long long *>(pImageNC));
    2468           7 :         if (status == NC_NOERR)
    2469           7 :             CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
    2470             :                                      nYChunkSize, false);
    2471             :     }
    2472          25 :     else if (eDataType == GDT_CInt16)
    2473             :     {
    2474           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2475           0 :         if (status == NC_NOERR)
    2476           0 :             CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2477             :                                 false);
    2478             :     }
    2479          25 :     else if (eDataType == GDT_CInt32)
    2480             :     {
    2481           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2482           0 :         if (status == NC_NOERR)
    2483           0 :             CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2484             :                               false);
    2485             :     }
    2486          25 :     else if (eDataType == GDT_CFloat32)
    2487             :     {
    2488          20 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2489          20 :         if (status == NC_NOERR)
    2490          20 :             CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2491             :                                 false);
    2492             :     }
    2493           5 :     else if (eDataType == GDT_CFloat64)
    2494             :     {
    2495           5 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2496           5 :         if (status == NC_NOERR)
    2497           5 :             CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2498             :                                  false);
    2499             :     }
    2500             : 
    2501             :     else
    2502           0 :         status = NC_EBADTYPE;
    2503             : 
    2504        5937 :     if (status != NC_NOERR)
    2505             :     {
    2506           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2507             :                  "netCDF chunk fetch failed: #%d (%s)", status,
    2508             :                  nc_strerror(status));
    2509           0 :         return false;
    2510             :     }
    2511        5937 :     return true;
    2512             : }
    2513             : 
    2514             : /************************************************************************/
    2515             : /*                             IReadBlock()                             */
    2516             : /************************************************************************/
    2517             : 
    2518        5937 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2519             :                                     void *pImage)
    2520             : 
    2521             : {
    2522       11874 :     CPLMutexHolderD(&hNCMutex);
    2523             : 
    2524             :     // Locate X, Y and Z position in the array.
    2525             : 
    2526        5937 :     size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2527        5937 :     size_t ystart = 0;
    2528             : 
    2529             :     // Check y order.
    2530        5937 :     if (nBandYPos >= 0)
    2531             :     {
    2532        5933 :         auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
    2533        5933 :         if (poGDS->bBottomUp)
    2534             :         {
    2535        5018 :             if (nBlockYSize == 1)
    2536             :             {
    2537        5005 :                 ystart = nRasterYSize - 1 - nBlockYOff;
    2538             :             }
    2539             :             else
    2540             :             {
    2541             :                 // in GDAL space
    2542          13 :                 ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2543             :                 const size_t yend =
    2544          26 :                     std::min(ystart + nBlockYSize - 1,
    2545          13 :                              static_cast<size_t>(nRasterYSize - 1));
    2546             :                 // in netCDF space
    2547          13 :                 const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
    2548          13 :                 const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
    2549          13 :                 const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
    2550          13 :                 const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
    2551             : 
    2552             :                 const auto firstKey = netCDFDataset::ChunkKey(
    2553          13 :                     nBlockXOff, nFirstChunkBlock, nBand);
    2554             :                 const auto secondKey =
    2555          13 :                     netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
    2556             : 
    2557             :                 // Retrieve data from the one or 2 needed netCDF chunks
    2558          13 :                 std::shared_ptr<std::vector<GByte>> firstChunk;
    2559          13 :                 std::shared_ptr<std::vector<GByte>> secondChunk;
    2560          13 :                 if (poGDS->poChunkCache)
    2561             :                 {
    2562          13 :                     poGDS->poChunkCache->tryGet(firstKey, firstChunk);
    2563          13 :                     if (firstKey != secondKey)
    2564           6 :                         poGDS->poChunkCache->tryGet(secondKey, secondChunk);
    2565             :                 }
    2566             :                 const size_t nChunkLineSize =
    2567          13 :                     static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
    2568          13 :                     nBlockXSize;
    2569          13 :                 const size_t nChunkSize = nChunkLineSize * nBlockYSize;
    2570          13 :                 if (!firstChunk)
    2571             :                 {
    2572          11 :                     firstChunk.reset(new std::vector<GByte>(nChunkSize));
    2573          11 :                     if (!FetchNetcdfChunk(xstart,
    2574          11 :                                           nFirstChunkBlock * nBlockYSize,
    2575          11 :                                           firstChunk.get()->data()))
    2576           0 :                         return CE_Failure;
    2577          11 :                     if (poGDS->poChunkCache)
    2578          11 :                         poGDS->poChunkCache->insert(firstKey, firstChunk);
    2579             :                 }
    2580          13 :                 if (!secondChunk && firstKey != secondKey)
    2581             :                 {
    2582           2 :                     secondChunk.reset(new std::vector<GByte>(nChunkSize));
    2583           2 :                     if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
    2584           2 :                                           secondChunk.get()->data()))
    2585           0 :                         return CE_Failure;
    2586           2 :                     if (poGDS->poChunkCache)
    2587           2 :                         poGDS->poChunkCache->insert(secondKey, secondChunk);
    2588             :                 }
    2589             : 
    2590             :                 // Assemble netCDF chunks into GDAL block
    2591          13 :                 GByte *pabyImage = static_cast<GByte *>(pImage);
    2592          13 :                 const size_t nFirstChunkBlockLine =
    2593          13 :                     nFirstChunkBlock * nBlockYSize;
    2594          13 :                 const size_t nLastChunkBlockLine =
    2595          13 :                     nLastChunkBlock * nBlockYSize;
    2596         146 :                 for (size_t iLine = ystart; iLine <= yend; iLine++)
    2597             :                 {
    2598         133 :                     const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
    2599         133 :                     const size_t nChunkY = nLineFromBottom / nBlockYSize;
    2600         133 :                     if (nChunkY == nFirstChunkBlock)
    2601             :                     {
    2602         121 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2603         121 :                                firstChunk.get()->data() +
    2604         121 :                                    (nLineFromBottom - nFirstChunkBlockLine) *
    2605             :                                        nChunkLineSize,
    2606             :                                nChunkLineSize);
    2607             :                     }
    2608             :                     else
    2609             :                     {
    2610          12 :                         CPLAssert(nChunkY == nLastChunkBlock);
    2611          12 :                         assert(secondChunk);
    2612          12 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2613          12 :                                secondChunk.get()->data() +
    2614          12 :                                    (nLineFromBottom - nLastChunkBlockLine) *
    2615             :                                        nChunkLineSize,
    2616             :                                nChunkLineSize);
    2617             :                     }
    2618             :                 }
    2619          13 :                 return CE_None;
    2620             :             }
    2621             :         }
    2622             :         else
    2623             :         {
    2624         915 :             ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2625             :         }
    2626             :     }
    2627             : 
    2628        5924 :     return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
    2629             : }
    2630             : 
    2631             : /************************************************************************/
    2632             : /*                            IWriteBlock()                             */
    2633             : /************************************************************************/
    2634             : 
    2635        6501 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
    2636             :                                      void *pImage)
    2637             : {
    2638       13002 :     CPLMutexHolderD(&hNCMutex);
    2639             : 
    2640             : #ifdef NCDF_DEBUG
    2641             :     if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
    2642             :         CPLDebug("GDAL_netCDF",
    2643             :                  "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
    2644             :                  nBlockXOff, nBlockYOff, nBand);
    2645             : #endif
    2646             : 
    2647        6501 :     int nd = 0;
    2648        6501 :     nc_inq_varndims(cdfid, nZId, &nd);
    2649             : 
    2650             :     // Locate X, Y and Z position in the array.
    2651             : 
    2652             :     size_t start[MAX_NC_DIMS];
    2653        6501 :     memset(start, 0, sizeof(start));
    2654        6501 :     start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2655             : 
    2656             :     // check y order.
    2657        6501 :     if (cpl::down_cast<netCDFDataset *>(poDS)->bBottomUp)
    2658             :     {
    2659        6437 :         if (nBlockYSize == 1)
    2660             :         {
    2661        6437 :             start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
    2662             :         }
    2663             :         else
    2664             :         {
    2665           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2666             :                      "nBlockYSize = %d, only 1 supported when "
    2667             :                      "writing bottom-up dataset",
    2668             :                      nBlockYSize);
    2669           0 :             return CE_Failure;
    2670             :         }
    2671             :     }
    2672             :     else
    2673             :     {
    2674          64 :         start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y
    2675             :     }
    2676             : 
    2677        6501 :     size_t edge[MAX_NC_DIMS] = {};
    2678             : 
    2679        6501 :     edge[nBandXPos] = nBlockXSize;
    2680        6501 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2681           0 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2682        6501 :     edge[nBandYPos] = nBlockYSize;
    2683        6501 :     if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2684           0 :         edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2685             : 
    2686        6501 :     if (nd == 3)
    2687             :     {
    2688         610 :         start[panBandZPos[0]] = nLevel;  // z
    2689         610 :         edge[panBandZPos[0]] = 1;
    2690             :     }
    2691             : 
    2692             :     // Compute multidimention band position.
    2693             :     //
    2694             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2695             :     // if Data[2,3,4,x,y]
    2696             :     //
    2697             :     //  BandPos0 = (nBand) / (3*4)
    2698             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2699             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2700        6501 :     if (nd > 3)
    2701             :     {
    2702         178 :         int Sum = -1;
    2703         178 :         int Taken = 0;
    2704         534 :         for (int i = 0; i < nd - 2; i++)
    2705             :         {
    2706         356 :             if (i != nd - 2 - 1)
    2707             :             {
    2708         178 :                 Sum = 1;
    2709         356 :                 for (int j = i + 1; j < nd - 2; j++)
    2710             :                 {
    2711         178 :                     Sum *= panBandZLev[j];
    2712             :                 }
    2713         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2714         178 :                 edge[panBandZPos[i]] = 1;
    2715             :             }
    2716             :             else
    2717             :             {
    2718         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2719         178 :                 edge[panBandZPos[i]] = 1;
    2720             :             }
    2721         356 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2722             :         }
    2723             :     }
    2724             : 
    2725             :     // Make sure we are in data mode.
    2726        6501 :     cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2727             : 
    2728             :     // Copy data according to type.
    2729        6501 :     int status = 0;
    2730        6501 :     if (eDataType == GDT_UInt8)
    2731             :     {
    2732        5942 :         if (bSignedData)
    2733           0 :             status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2734             :                                        static_cast<signed char *>(pImage));
    2735             :         else
    2736        5942 :             status = nc_put_vara_uchar(cdfid, nZId, start, edge,
    2737             :                                        static_cast<unsigned char *>(pImage));
    2738             :     }
    2739         559 :     else if (eDataType == GDT_Int8)
    2740             :     {
    2741          40 :         status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2742             :                                    static_cast<signed char *>(pImage));
    2743             :     }
    2744         519 :     else if (nc_datatype == NC_SHORT)
    2745             :     {
    2746         101 :         status = nc_put_vara_short(cdfid, nZId, start, edge,
    2747             :                                    static_cast<short *>(pImage));
    2748             :     }
    2749         418 :     else if (eDataType == GDT_Int32)
    2750             :     {
    2751         210 :         status = nc_put_vara_int(cdfid, nZId, start, edge,
    2752             :                                  static_cast<int *>(pImage));
    2753             :     }
    2754         208 :     else if (eDataType == GDT_Float32)
    2755             :     {
    2756         128 :         status = nc_put_vara_float(cdfid, nZId, start, edge,
    2757             :                                    static_cast<float *>(pImage));
    2758             :     }
    2759          80 :     else if (eDataType == GDT_Float64)
    2760             :     {
    2761          50 :         status = nc_put_vara_double(cdfid, nZId, start, edge,
    2762             :                                     static_cast<double *>(pImage));
    2763             :     }
    2764          42 :     else if (eDataType == GDT_UInt16 &&
    2765          12 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2766             :     {
    2767          12 :         status = nc_put_vara_ushort(cdfid, nZId, start, edge,
    2768             :                                     static_cast<unsigned short *>(pImage));
    2769             :     }
    2770          30 :     else if (eDataType == GDT_UInt32 &&
    2771          12 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2772             :     {
    2773          12 :         status = nc_put_vara_uint(cdfid, nZId, start, edge,
    2774             :                                   static_cast<unsigned int *>(pImage));
    2775             :     }
    2776           9 :     else if (eDataType == GDT_UInt64 &&
    2777           3 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2778             :     {
    2779             :         status =
    2780           3 :             nc_put_vara_ulonglong(cdfid, nZId, start, edge,
    2781             :                                   static_cast<unsigned long long *>(pImage));
    2782             :     }
    2783           6 :     else if (eDataType == GDT_Int64 &&
    2784           3 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2785             :     {
    2786           3 :         status = nc_put_vara_longlong(cdfid, nZId, start, edge,
    2787             :                                       static_cast<long long *>(pImage));
    2788             :     }
    2789             :     else
    2790             :     {
    2791           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2792             :                  "The NetCDF driver does not support GDAL data type %d",
    2793           0 :                  eDataType);
    2794           0 :         status = NC_EBADTYPE;
    2795             :     }
    2796        6501 :     NCDF_ERR(status);
    2797             : 
    2798        6501 :     if (status != NC_NOERR)
    2799             :     {
    2800           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2801             :                  "netCDF scanline write failed: %s", nc_strerror(status));
    2802           0 :         return CE_Failure;
    2803             :     }
    2804             : 
    2805        6501 :     return CE_None;
    2806             : }
    2807             : 
    2808             : /************************************************************************/
    2809             : /* ==================================================================== */
    2810             : /*                              netCDFDataset                           */
    2811             : /* ==================================================================== */
    2812             : /************************************************************************/
    2813             : 
    2814             : /************************************************************************/
    2815             : /*                           netCDFDataset()                            */
    2816             : /************************************************************************/
    2817             : 
    2818        1208 : netCDFDataset::netCDFDataset()
    2819             :     :
    2820             : // Basic dataset vars.
    2821             : #ifdef ENABLE_NCDUMP
    2822             :       bFileToDestroyAtClosing(false),
    2823             : #endif
    2824             :       cdfid(-1), nSubDatasets(0), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
    2825             :       bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
    2826             :       pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
    2827        1208 :       eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
    2828        1208 :       GeometryScribe(vcdf, this->generateLogName()),
    2829        1208 :       FieldScribe(vcdf, this->generateLogName()),
    2830        2416 :       bufManager(CPLGetUsablePhysicalRAM() / 5),
    2831             : 
    2832             :       // projection/GT.
    2833             :       nXDimID(-1), nYDimID(-1), bIsProjected(false),
    2834             :       bIsGeographic(false),  // Can be not projected, and also not geographic
    2835             :       // State vars.
    2836             :       bDefineMode(true), bAddedGridMappingRef(false),
    2837             : 
    2838             :       // Create vars.
    2839             :       eCompress(NCDF_COMPRESS_NONE), nZLevel(NCDF_DEFLATE_LEVEL),
    2840        3624 :       bChunking(false), nCreateMode(NC_CLOBBER), bSignedData(true)
    2841             : {
    2842        1208 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2843             : 
    2844             :     // Set buffers
    2845        1208 :     bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
    2846        1208 :     bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
    2847        1208 : }
    2848             : 
    2849             : /************************************************************************/
    2850             : /*                           ~netCDFDataset()                           */
    2851             : /************************************************************************/
    2852             : 
    2853        2318 : netCDFDataset::~netCDFDataset()
    2854             : 
    2855             : {
    2856        1208 :     netCDFDataset::Close();
    2857        2318 : }
    2858             : 
    2859             : /************************************************************************/
    2860             : /*                               Close()                                */
    2861             : /************************************************************************/
    2862             : 
    2863        2036 : CPLErr netCDFDataset::Close(GDALProgressFunc, void *)
    2864             : {
    2865        2036 :     CPLErr eErr = CE_None;
    2866        2036 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    2867             :     {
    2868        2416 :         CPLMutexHolderD(&hNCMutex);
    2869             : 
    2870             : #ifdef NCDF_DEBUG
    2871             :         CPLDebug("GDAL_netCDF",
    2872             :                  "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
    2873             :                  osFilename.c_str());
    2874             : #endif
    2875             : 
    2876             :         // Write data related to geotransform
    2877        1493 :         if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
    2878         285 :             (m_bHasProjection || m_bHasGeoTransform))
    2879             :         {
    2880             :             // Ensure projection is written if GeoTransform OR Projection are
    2881             :             // missing.
    2882          37 :             if (!m_bAddedProjectionVarsDefs)
    2883             :             {
    2884           2 :                 AddProjectionVars(true, nullptr, nullptr);
    2885             :             }
    2886          37 :             AddProjectionVars(false, nullptr, nullptr);
    2887             :         }
    2888             : 
    2889        1208 :         if (netCDFDataset::FlushCache(true) != CE_None)
    2890           0 :             eErr = CE_Failure;
    2891             : 
    2892        1208 :         if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
    2893           0 :             eErr = CE_Failure;
    2894             : 
    2895        1210 :         for (size_t i = 0; i < apoVectorDatasets.size(); i++)
    2896           2 :             delete apoVectorDatasets[i];
    2897             : 
    2898             :         // Make sure projection variable is written to band variable.
    2899        1208 :         if (GetAccess() == GA_Update && !bAddedGridMappingRef)
    2900             :         {
    2901         308 :             if (!AddGridMappingRef())
    2902           0 :                 eErr = CE_Failure;
    2903             :         }
    2904             : 
    2905        1208 :         CPLFree(pszCFProjection);
    2906             : 
    2907        1208 :         if (cdfid > 0)
    2908             :         {
    2909             : #ifdef NCDF_DEBUG
    2910             :             CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
    2911             : #endif
    2912         686 :             int status = GDAL_nc_close(cdfid);
    2913             : #ifdef ENABLE_UFFD
    2914         686 :             NETCDF_UFFD_UNMAP(pCtx);
    2915             : #endif
    2916         686 :             NCDF_ERR(status);
    2917         686 :             if (status != NC_NOERR)
    2918           0 :                 eErr = CE_Failure;
    2919             :         }
    2920             : 
    2921        1208 :         if (fpVSIMEM)
    2922          15 :             VSIFCloseL(fpVSIMEM);
    2923             : 
    2924             : #ifdef ENABLE_NCDUMP
    2925        1208 :         if (bFileToDestroyAtClosing)
    2926           0 :             VSIUnlink(osFilename);
    2927             : #endif
    2928             : 
    2929        1208 :         if (GDALPamDataset::Close() != CE_None)
    2930           0 :             eErr = CE_Failure;
    2931             :     }
    2932        2036 :     return eErr;
    2933             : }
    2934             : 
    2935             : /************************************************************************/
    2936             : /*                           SetDefineMode()                            */
    2937             : /************************************************************************/
    2938       14555 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
    2939             : {
    2940             :     // Do nothing if already in new define mode
    2941             :     // or if dataset is in read-only mode or if dataset is true NC4 dataset.
    2942       15132 :     if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
    2943         577 :         eFormat == NCDF_FORMAT_NC4)
    2944       14125 :         return true;
    2945             : 
    2946         430 :     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
    2947         430 :              static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
    2948             : 
    2949         430 :     bDefineMode = bNewDefineMode;
    2950             : 
    2951             :     int status;
    2952         430 :     if (bDefineMode)
    2953         149 :         status = nc_redef(cdfid);
    2954             :     else
    2955         281 :         status = nc_enddef(cdfid);
    2956             : 
    2957         430 :     NCDF_ERR(status);
    2958         430 :     return status == NC_NOERR;
    2959             : }
    2960             : 
    2961             : /************************************************************************/
    2962             : /*                       GetMetadataDomainList()                        */
    2963             : /************************************************************************/
    2964             : 
    2965          27 : char **netCDFDataset::GetMetadataDomainList()
    2966             : {
    2967          27 :     char **papszDomains = BuildMetadataDomainList(
    2968             :         GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
    2969          28 :     for (const auto &kv : m_oMapDomainToJSon)
    2970           1 :         papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
    2971          27 :     return papszDomains;
    2972             : }
    2973             : 
    2974             : /************************************************************************/
    2975             : /*                            GetMetadata()                             */
    2976             : /************************************************************************/
    2977         403 : CSLConstList netCDFDataset::GetMetadata(const char *pszDomain)
    2978             : {
    2979         403 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
    2980          39 :         return aosSubDatasets.List();
    2981             : 
    2982         364 :     if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
    2983             :     {
    2984           1 :         auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
    2985           1 :         if (iter != m_oMapDomainToJSon.end())
    2986           1 :             return iter->second.List();
    2987             :     }
    2988             : 
    2989         363 :     return GDALDataset::GetMetadata(pszDomain);
    2990             : }
    2991             : 
    2992             : /************************************************************************/
    2993             : /*                          SetMetadataItem()                           */
    2994             : /************************************************************************/
    2995             : 
    2996          43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    2997             :                                       const char *pszDomain)
    2998             : {
    2999          85 :     if (GetAccess() == GA_Update &&
    3000          85 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    3001             :     {
    3002          42 :         std::string osName(pszName);
    3003             : 
    3004             :         // Same logic as in CopyMetadata()
    3005          42 :         if (cpl::starts_with(osName, "NC_GLOBAL#"))
    3006           8 :             osName = osName.substr(strlen("NC_GLOBAL#"));
    3007          34 :         else if (strchr(osName.c_str(), '#') == nullptr)
    3008           5 :             osName = "GDAL_" + osName;
    3009             : 
    3010          84 :         if (cpl::starts_with(osName, "NETCDF_DIM_") ||
    3011          42 :             strchr(osName.c_str(), '#') != nullptr)
    3012             :         {
    3013             :             // do nothing
    3014          29 :             return CE_None;
    3015             :         }
    3016             :         else
    3017             :         {
    3018          13 :             SetDefineMode(true);
    3019             : 
    3020          13 :             if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
    3021          13 :                 return CE_Failure;
    3022             :         }
    3023             :     }
    3024             : 
    3025           1 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    3026             : }
    3027             : 
    3028             : /************************************************************************/
    3029             : /*                            SetMetadata()                             */
    3030             : /************************************************************************/
    3031             : 
    3032           8 : CPLErr netCDFDataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
    3033             : {
    3034          13 :     if (GetAccess() == GA_Update &&
    3035           5 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    3036             :     {
    3037             :         // We don't handle metadata item removal for now
    3038          50 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    3039             :              ++papszIter)
    3040             :         {
    3041          42 :             char *pszName = nullptr;
    3042          42 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    3043          42 :             if (pszName && pszValue)
    3044          42 :                 SetMetadataItem(pszName, pszValue);
    3045          42 :             CPLFree(pszName);
    3046             :         }
    3047           8 :         return CE_None;
    3048             :     }
    3049           0 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    3050             : }
    3051             : 
    3052             : /************************************************************************/
    3053             : /*                           GetSpatialRef()                            */
    3054             : /************************************************************************/
    3055             : 
    3056         230 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
    3057             : {
    3058         230 :     if (m_bHasProjection)
    3059         102 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3060             : 
    3061         128 :     return GDALPamDataset::GetSpatialRef();
    3062             : }
    3063             : 
    3064             : /************************************************************************/
    3065             : /*                           FetchCopyParam()                           */
    3066             : /************************************************************************/
    3067             : 
    3068         444 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
    3069             :                                      const char *pszParam, double dfDefault,
    3070             :                                      bool *pbFound) const
    3071             : 
    3072             : {
    3073         888 :     std::string osTemp = CPLOPrintf("%s#%s", pszGridMappingValue, pszParam);
    3074         444 :     const char *pszValue = aosMetadata.FetchNameValue(osTemp.c_str());
    3075             : 
    3076         444 :     if (pbFound)
    3077             :     {
    3078         444 :         *pbFound = (pszValue != nullptr);
    3079             :     }
    3080             : 
    3081         444 :     if (pszValue)
    3082             :     {
    3083           0 :         return CPLAtofM(pszValue);
    3084             :     }
    3085             : 
    3086         444 :     return dfDefault;
    3087             : }
    3088             : 
    3089             : /************************************************************************/
    3090             : /*                       FetchStandardParallels()                       */
    3091             : /************************************************************************/
    3092             : 
    3093             : std::vector<std::string>
    3094           0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) const
    3095             : {
    3096             :     // cf-1.0 tags
    3097           0 :     const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
    3098             : 
    3099           0 :     std::vector<std::string> ret;
    3100           0 :     if (pszValue != nullptr)
    3101             :     {
    3102           0 :         CPLStringList aosValues;
    3103           0 :         if (pszValue[0] != '{' &&
    3104           0 :             CPLString(pszValue).Trim().find(' ') != std::string::npos)
    3105             :         {
    3106             :             // Some files like
    3107             :             // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
    3108             :             // do not use standard formatting for arrays, but just space
    3109             :             // separated syntax
    3110           0 :             aosValues = CSLTokenizeString2(pszValue, " ", 0);
    3111             :         }
    3112             :         else
    3113             :         {
    3114           0 :             aosValues = NCDFTokenizeArray(pszValue);
    3115             :         }
    3116           0 :         for (int i = 0; i < aosValues.size(); i++)
    3117             :         {
    3118           0 :             ret.push_back(aosValues[i]);
    3119             :         }
    3120             :     }
    3121             :     // Try gdal tags.
    3122             :     else
    3123             :     {
    3124           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
    3125             : 
    3126           0 :         if (pszValue != nullptr)
    3127           0 :             ret.push_back(pszValue);
    3128             : 
    3129           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
    3130             : 
    3131           0 :         if (pszValue != nullptr)
    3132           0 :             ret.push_back(pszValue);
    3133             :     }
    3134             : 
    3135           0 :     return ret;
    3136             : }
    3137             : 
    3138             : /************************************************************************/
    3139             : /*                             FetchAttr()                              */
    3140             : /************************************************************************/
    3141             : 
    3142        3968 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
    3143             :                                      const char *pszAttr) const
    3144             : 
    3145             : {
    3146        3968 :     auto oKey = CPLOPrintf("%s#%s", pszVarFullName, pszAttr);
    3147        3968 :     const char *pszValue = aosMetadata.FetchNameValue(oKey.c_str());
    3148        7936 :     return pszValue;
    3149             : }
    3150             : 
    3151        2606 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
    3152             :                                      const char *pszAttr) const
    3153             : 
    3154             : {
    3155        2606 :     std::string osFullName;
    3156        2606 :     NCDFGetVarFullName(nGroupId, nVarId, osFullName);
    3157        2606 :     const char *pszValue = FetchAttr(osFullName.c_str(), pszAttr);
    3158        5212 :     return pszValue;
    3159             : }
    3160             : 
    3161             : /************************************************************************/
    3162             : /*                         IsDifferenceBelow()                          */
    3163             : /************************************************************************/
    3164             : 
    3165        1115 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
    3166             : {
    3167        1115 :     const double dfAbsDiff = fabs(dfA - dfB);
    3168        1115 :     return dfAbsDiff <= dfError;
    3169             : }
    3170             : 
    3171             : /************************************************************************/
    3172             : /*                        SetProjectionFromVar()                        */
    3173             : /************************************************************************/
    3174         560 : void netCDFDataset::SetProjectionFromVar(
    3175             :     int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
    3176             :     std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
    3177             :     std::vector<std::string> *paosRemovedMDItems)
    3178             : {
    3179         560 :     bool bGotGeogCS = false;
    3180         560 :     bool bGotCfSRS = false;
    3181         560 :     bool bGotCfWktSRS = false;
    3182         560 :     bool bGotGdalSRS = false;
    3183         560 :     bool bGotCfGT = false;
    3184         560 :     bool bGotGdalGT = false;
    3185             : 
    3186             :     // These values from CF metadata.
    3187         560 :     OGRSpatialReference oSRS;
    3188         560 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3189         560 :     size_t xdim = nRasterXSize;
    3190         560 :     size_t ydim = nRasterYSize;
    3191             : 
    3192             :     // These values from GDAL metadata.
    3193         560 :     const char *pszWKT = nullptr;
    3194         560 :     const char *pszGeoTransform = nullptr;
    3195             : 
    3196         560 :     netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.
    3197             : 
    3198         560 :     CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
    3199             :              nVarId);
    3200             : 
    3201             :     // Get x/y range information.
    3202             : 
    3203             :     // Temp variables to use in SetGeoTransform() and SetProjection().
    3204         560 :     GDALGeoTransform tmpGT;
    3205             : 
    3206             :     // Look for grid_mapping metadata.
    3207         560 :     const char *pszValue = pszGivenGM;
    3208         560 :     CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may
    3209             :     // point to it
    3210         560 :     if (pszValue == nullptr)
    3211             :     {
    3212         517 :         pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
    3213         517 :         if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
    3214             :         {
    3215             :             // Expanded form of grid_mapping
    3216             :             // e.g. "crsOSGB: x y crsWGS84: lat lon"
    3217             :             // Pickup the grid_mapping whose coordinates are dimensions of the
    3218             :             // variable
    3219           6 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
    3220           3 :             if ((aosTokens.size() % 3) == 0)
    3221             :             {
    3222           3 :                 for (int i = 0; i < aosTokens.size() / 3; i++)
    3223             :                 {
    3224           3 :                     if (CSLFindString(poDS->papszDimName,
    3225           9 :                                       aosTokens[3 * i + 1]) >= 0 &&
    3226           3 :                         CSLFindString(poDS->papszDimName,
    3227           3 :                                       aosTokens[3 * i + 2]) >= 0)
    3228             :                     {
    3229           3 :                         osTmpGridMapping = aosTokens[3 * i];
    3230           6 :                         if (!osTmpGridMapping.empty() &&
    3231           3 :                             osTmpGridMapping.back() == ':')
    3232             :                         {
    3233           3 :                             osTmpGridMapping.resize(osTmpGridMapping.size() -
    3234             :                                                     1);
    3235             :                         }
    3236           3 :                         pszValue = osTmpGridMapping.c_str();
    3237           3 :                         break;
    3238             :                     }
    3239             :                 }
    3240             :             }
    3241             :         }
    3242             :     }
    3243         560 :     std::string osGridMappingValue = pszValue ? pszValue : "";
    3244             : 
    3245         560 :     if (!osGridMappingValue.empty())
    3246             :     {
    3247             :         // Read grid_mapping metadata.
    3248         239 :         int nProjGroupID = -1;
    3249         239 :         int nProjVarID = -1;
    3250         239 :         if (NCDFResolveVar(nGroupId, osGridMappingValue.c_str(), &nProjGroupID,
    3251         239 :                            &nProjVarID) == CE_None)
    3252             :         {
    3253         238 :             poDS->ReadAttributes(nProjGroupID, nProjVarID);
    3254             : 
    3255             :             // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
    3256         238 :             if (NCDFGetVarFullName(nProjGroupID, nProjVarID,
    3257         238 :                                    osGridMappingValue) == CE_None)
    3258             :             {
    3259         238 :                 CPLDebug("GDAL_netCDF", "got grid_mapping %s",
    3260             :                          osGridMappingValue.c_str());
    3261             :                 pszWKT =
    3262         238 :                     FetchAttr(osGridMappingValue.c_str(), NCDF_SPATIAL_REF);
    3263         238 :                 if (!pszWKT)
    3264             :                 {
    3265             :                     pszWKT =
    3266          35 :                         FetchAttr(osGridMappingValue.c_str(), NCDF_CRS_WKT);
    3267             :                 }
    3268             :                 else
    3269             :                 {
    3270         203 :                     bGotGdalSRS = true;
    3271         203 :                     CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
    3272             :                 }
    3273         238 :                 if (pszWKT)
    3274             :                 {
    3275         208 :                     if (!bGotGdalSRS)
    3276             :                     {
    3277           5 :                         bGotCfWktSRS = true;
    3278           5 :                         CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3279             :                     }
    3280         208 :                     if (returnProjStr != nullptr)
    3281             :                     {
    3282          41 :                         (*returnProjStr) = std::string(pszWKT);
    3283             :                     }
    3284             :                     else
    3285             :                     {
    3286         167 :                         m_bAddedProjectionVarsDefs = true;
    3287         167 :                         m_bAddedProjectionVarsData = true;
    3288         334 :                         OGRSpatialReference oSRSTmp;
    3289         167 :                         oSRSTmp.SetAxisMappingStrategy(
    3290             :                             OAMS_TRADITIONAL_GIS_ORDER);
    3291         167 :                         oSRSTmp.importFromWkt(pszWKT);
    3292         167 :                         SetSpatialRefNoUpdate(&oSRSTmp);
    3293             :                     }
    3294         208 :                     pszGeoTransform = FetchAttr(osGridMappingValue.c_str(),
    3295             :                                                 NCDF_GEOTRANSFORM);
    3296             :                 }
    3297             :             }
    3298             :         }
    3299             :     }
    3300             : 
    3301             :     // Get information about the file.
    3302             :     //
    3303             :     // Was this file created by the GDAL netcdf driver?
    3304             :     // Was this file created by the newer (CF-conformant) driver?
    3305             :     //
    3306             :     // 1) If GDAL netcdf metadata is set, and version >= 1.9,
    3307             :     //    it was created with the new driver
    3308             :     // 2) Else, if spatial_ref and GeoTransform are present in the
    3309             :     //    grid_mapping variable, it was created by the old driver
    3310         560 :     pszValue = FetchAttr("NC_GLOBAL", "GDAL");
    3311             : 
    3312         560 :     if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
    3313             :     {
    3314         257 :         bIsGdalFile = true;
    3315         257 :         bIsGdalCfFile = true;
    3316             :     }
    3317         303 :     else if (pszWKT != nullptr && pszGeoTransform != nullptr)
    3318             :     {
    3319          24 :         bIsGdalFile = true;
    3320          24 :         bIsGdalCfFile = false;
    3321             :     }
    3322             : 
    3323             :     // Set default bottom-up default value.
    3324             :     // Y axis dimension and absence of GT can modify this value.
    3325             :     // Override with Config option GDAL_NETCDF_BOTTOMUP.
    3326             : 
    3327             :     // New driver is bottom-up by default.
    3328         560 :     if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
    3329          26 :         poDS->bBottomUp = false;
    3330             :     else
    3331         534 :         poDS->bBottomUp = true;
    3332             : 
    3333         560 :     CPLDebug("GDAL_netCDF",
    3334             :              "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
    3335         560 :              static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
    3336         560 :              static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
    3337             : 
    3338             :     // Read projection coordinates.
    3339             : 
    3340         560 :     int nGroupDimXID = -1;
    3341         560 :     int nVarDimXID = -1;
    3342         560 :     int nGroupDimYID = -1;
    3343         560 :     int nVarDimYID = -1;
    3344         560 :     if (sg != nullptr)
    3345             :     {
    3346          43 :         nGroupDimXID = sg->get_ncID();
    3347          43 :         nGroupDimYID = sg->get_ncID();
    3348          43 :         nVarDimXID = sg->getNodeCoordVars()[0];
    3349          43 :         nVarDimYID = sg->getNodeCoordVars()[1];
    3350             :     }
    3351             : 
    3352         560 :     if (!bReadSRSOnly)
    3353             :     {
    3354         368 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
    3355             :                        &nVarDimXID);
    3356         368 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
    3357             :                        &nVarDimYID);
    3358             :         // TODO: if above resolving fails we should also search for coordinate
    3359             :         // variables without same name than dimension using the same resolving
    3360             :         // logic. This should handle for example NASA Ocean Color L2 products.
    3361             : 
    3362             :         const bool bIgnoreXYAxisNameChecks =
    3363         736 :             CPLTestBool(CSLFetchNameValueDef(
    3364         368 :                 papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
    3365             :                 CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
    3366         368 :                                    "NO"))) ||
    3367             :             // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
    3368             :             // and transform attributes
    3369         368 :             (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
    3370         736 :              FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
    3371         367 :             FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
    3372             : 
    3373             :         // Check that they are 1D or 2D variables
    3374         368 :         if (nVarDimXID >= 0)
    3375             :         {
    3376         260 :             int ndims = -1;
    3377         260 :             nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
    3378         260 :             if (ndims == 0 || ndims > 2)
    3379           0 :                 nVarDimXID = -1;
    3380         260 :             else if (!bIgnoreXYAxisNameChecks)
    3381             :             {
    3382         258 :                 if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    3383         168 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
    3384             :                     // In case of inversion of X/Y
    3385         458 :                     !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
    3386          32 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
    3387             :                 {
    3388             :                     char szVarNameX[NC_MAX_NAME + 1];
    3389          32 :                     CPL_IGNORE_RET_VAL(
    3390          32 :                         nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    3391          32 :                     if (!(ndims == 1 &&
    3392          31 :                           (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
    3393          30 :                            EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
    3394             :                     {
    3395          31 :                         CPLDebug(
    3396             :                             "netCDF",
    3397             :                             "Georeferencing ignored due to non-specific "
    3398             :                             "enough X axis name. "
    3399             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3400             :                             "as configuration option to bypass this check");
    3401          31 :                         nVarDimXID = -1;
    3402             :                     }
    3403             :                 }
    3404             :             }
    3405             :         }
    3406             : 
    3407         368 :         if (nVarDimYID >= 0)
    3408             :         {
    3409         262 :             int ndims = -1;
    3410         262 :             nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
    3411         262 :             if (ndims == 0 || ndims > 2)
    3412           1 :                 nVarDimYID = -1;
    3413         261 :             else if (!bIgnoreXYAxisNameChecks)
    3414             :             {
    3415         259 :                 if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
    3416         169 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
    3417             :                     // In case of inversion of X/Y
    3418         461 :                     !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    3419          33 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
    3420             :                 {
    3421             :                     char szVarNameY[NC_MAX_NAME + 1];
    3422          33 :                     CPL_IGNORE_RET_VAL(
    3423          33 :                         nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    3424          33 :                     if (!(ndims == 1 &&
    3425          33 :                           (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
    3426          32 :                            EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
    3427             :                     {
    3428          32 :                         CPLDebug(
    3429             :                             "netCDF",
    3430             :                             "Georeferencing ignored due to non-specific "
    3431             :                             "enough Y axis name. "
    3432             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3433             :                             "as configuration option to bypass this check");
    3434          32 :                         nVarDimYID = -1;
    3435             :                     }
    3436             :                 }
    3437             :             }
    3438             :         }
    3439             : 
    3440         368 :         if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
    3441             :         {
    3442           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3443             :                      "1-pixel width/height files not supported, "
    3444             :                      "xdim: %ld ydim: %ld",
    3445             :                      static_cast<long>(xdim), static_cast<long>(ydim));
    3446           0 :             nVarDimXID = -1;
    3447           0 :             nVarDimYID = -1;
    3448             :         }
    3449             :     }
    3450             : 
    3451         560 :     const char *pszUnits = nullptr;
    3452         560 :     if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3453             :     {
    3454         272 :         const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
    3455         272 :         const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
    3456             :         // Normalize degrees_east/degrees_north to degrees
    3457             :         // Cf https://github.com/OSGeo/gdal/issues/11009
    3458         272 :         if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
    3459          79 :             pszUnitsX = "degrees";
    3460         272 :         if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
    3461          79 :             pszUnitsY = "degrees";
    3462             : 
    3463         272 :         if (pszUnitsX && pszUnitsY)
    3464             :         {
    3465         225 :             if (EQUAL(pszUnitsX, pszUnitsY))
    3466         222 :                 pszUnits = pszUnitsX;
    3467           3 :             else if (!pszWKT && !osGridMappingValue.empty())
    3468             :             {
    3469           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3470             :                          "X axis unit (%s) is different from Y axis "
    3471             :                          "unit (%s). SRS will ignore axis unit and be "
    3472             :                          "likely wrong.",
    3473             :                          pszUnitsX, pszUnitsY);
    3474             :             }
    3475             :         }
    3476          47 :         else if (pszUnitsX && !pszWKT && !osGridMappingValue.empty())
    3477             :         {
    3478           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3479             :                      "X axis unit is defined, but not Y one ."
    3480             :                      "SRS will ignore axis unit and be likely wrong.");
    3481             :         }
    3482          47 :         else if (pszUnitsY && !pszWKT && !osGridMappingValue.empty())
    3483             :         {
    3484           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3485             :                      "Y axis unit is defined, but not X one ."
    3486             :                      "SRS will ignore axis unit and be likely wrong.");
    3487             :         }
    3488             :     }
    3489             : 
    3490         560 :     if (!pszWKT && !osGridMappingValue.empty())
    3491             :     {
    3492          31 :         CPLStringList aosGridMappingKeyValues;
    3493          31 :         const size_t nLenGridMappingValue = osGridMappingValue.size();
    3494         789 :         for (const char *pszIter : aosMetadata)
    3495             :         {
    3496         994 :             if (STARTS_WITH(pszIter, osGridMappingValue.c_str()) &&
    3497         236 :                 pszIter[nLenGridMappingValue] == '#')
    3498             :             {
    3499         236 :                 char *pszKey = nullptr;
    3500         236 :                 pszValue = CPLParseNameValue(pszIter + nLenGridMappingValue + 1,
    3501             :                                              &pszKey);
    3502         236 :                 if (pszKey && pszValue)
    3503         236 :                     aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
    3504         236 :                 CPLFree(pszKey);
    3505             :             }
    3506             :         }
    3507             : 
    3508          31 :         bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
    3509             :                          CF_PP_SEMI_MAJOR_AXIS) != nullptr;
    3510             : 
    3511          31 :         oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
    3512          31 :         bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
    3513             :     }
    3514             :     else
    3515             :     {
    3516             :         // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
    3517             :         // attribute hold on the variable of interest that contains a PROJ.4
    3518             :         // string
    3519         529 :         pszValue = FetchAttr(nGroupId, nVarId, "crs");
    3520         530 :         if (pszValue &&
    3521           1 :             (strstr(pszValue, "+proj=") != nullptr ||
    3522           0 :              strstr(pszValue, "GEOGCS") != nullptr ||
    3523           0 :              strstr(pszValue, "PROJCS") != nullptr ||
    3524         530 :              strstr(pszValue, "EPSG:") != nullptr) &&
    3525           1 :             oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
    3526             :         {
    3527           1 :             bGotCfSRS = true;
    3528             :         }
    3529             :     }
    3530             : 
    3531             :     // Set Projection from CF.
    3532         560 :     double dfLinearUnitsConvFactor = 1.0;
    3533         560 :     if ((bGotGeogCS || bGotCfSRS))
    3534             :     {
    3535          31 :         if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3536             :         {
    3537             :             // Set SRS Units.
    3538             : 
    3539             :             // Check units for x and y.
    3540          28 :             if (oSRS.IsProjected())
    3541             :             {
    3542          25 :                 dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
    3543             : 
    3544             :                 // If the user doesn't ask to preserve the axis unit,
    3545             :                 // then normalize to metre
    3546          31 :                 if (dfLinearUnitsConvFactor != 1.0 &&
    3547           6 :                     !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
    3548             :                                   false))
    3549             :                 {
    3550           5 :                     oSRS.SetLinearUnits("metre", 1.0);
    3551           5 :                     oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
    3552             :                 }
    3553             :                 else
    3554             :                 {
    3555          20 :                     dfLinearUnitsConvFactor = 1.0;
    3556             :                 }
    3557             :             }
    3558             :         }
    3559             : 
    3560             :         // Set projection.
    3561          31 :         char *pszTempProjection = nullptr;
    3562          31 :         oSRS.exportToWkt(&pszTempProjection);
    3563          31 :         if (pszTempProjection)
    3564             :         {
    3565          31 :             CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3566          31 :             if (returnProjStr != nullptr)
    3567             :             {
    3568           2 :                 (*returnProjStr) = std::string(pszTempProjection);
    3569             :             }
    3570             :             else
    3571             :             {
    3572          29 :                 m_bAddedProjectionVarsDefs = true;
    3573          29 :                 m_bAddedProjectionVarsData = true;
    3574          29 :                 SetSpatialRefNoUpdate(&oSRS);
    3575             :             }
    3576             :         }
    3577          31 :         CPLFree(pszTempProjection);
    3578             :     }
    3579             : 
    3580         560 :     if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
    3581             :         ydim > 0)
    3582             :     {
    3583             :         double *pdfXCoord =
    3584         229 :             static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
    3585             :         double *pdfYCoord =
    3586         229 :             static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
    3587             : 
    3588         229 :         size_t start[2] = {0, 0};
    3589         229 :         size_t edge[2] = {xdim, 0};
    3590         229 :         int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
    3591             :                                         pdfXCoord);
    3592         229 :         NCDF_ERR(status);
    3593             : 
    3594         229 :         edge[0] = ydim;
    3595         229 :         status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
    3596             :                                     pdfYCoord);
    3597         229 :         NCDF_ERR(status);
    3598             : 
    3599         229 :         nc_type nc_var_dimx_datatype = NC_NAT;
    3600             :         status =
    3601         229 :             nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
    3602         229 :         NCDF_ERR(status);
    3603             : 
    3604         229 :         nc_type nc_var_dimy_datatype = NC_NAT;
    3605             :         status =
    3606         229 :             nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
    3607         229 :         NCDF_ERR(status);
    3608             : 
    3609         229 :         if (!poDS->bSwitchedXY)
    3610             :         {
    3611             :             // Convert ]180,540] longitude values to ]-180,0].
    3612         317 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3613          90 :                 CPLTestBool(
    3614             :                     CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
    3615             :             {
    3616             :                 // If minimum longitude is > 180, subtract 360 from all.
    3617             :                 // Add a check on the maximum X value too, since
    3618             :                 // NCDFIsVarLongitude() is not very specific by default (see
    3619             :                 // https://github.com/OSGeo/gdal/issues/1440)
    3620          97 :                 if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
    3621           7 :                     std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
    3622             :                 {
    3623           0 :                     CPLDebug(
    3624             :                         "GDAL_netCDF",
    3625             :                         "Offsetting longitudes from ]180,540] to ]-180,180]. "
    3626             :                         "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
    3627           0 :                     for (size_t i = 0; i < xdim; i++)
    3628           0 :                         pdfXCoord[i] -= 360;
    3629             :                 }
    3630             :             }
    3631             :         }
    3632             : 
    3633             :         // Is pixel spacing uniform across the map?
    3634             : 
    3635             :         // Check Longitude.
    3636             : 
    3637         229 :         bool bLonSpacingOK = false;
    3638         229 :         if (xdim == 2)
    3639             :         {
    3640          29 :             bLonSpacingOK = true;
    3641             :         }
    3642             :         else
    3643             :         {
    3644         200 :             bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
    3645             : 
    3646             :             // fix longitudes if longitudes should increase from
    3647             :             // west to east, but west > east
    3648         280 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3649          80 :                 !bWestIsLeft)
    3650             :             {
    3651           2 :                 size_t ndecreases = 0;
    3652             : 
    3653             :                 // there is lon wrap if longitudes increase
    3654             :                 // with one single decrease
    3655         107 :                 for (size_t i = 1; i < xdim; i++)
    3656             :                 {
    3657         105 :                     if (pdfXCoord[i] < pdfXCoord[i - 1])
    3658           1 :                         ndecreases++;
    3659             :                 }
    3660             : 
    3661           2 :                 if (ndecreases == 1)
    3662             :                 {
    3663           1 :                     CPLDebug("GDAL_netCDF", "longitude wrap detected");
    3664           4 :                     for (size_t i = 0; i < xdim; i++)
    3665             :                     {
    3666           3 :                         if (pdfXCoord[i] > pdfXCoord[xdim - 1])
    3667           1 :                             pdfXCoord[i] -= 360;
    3668             :                     }
    3669             :                 }
    3670             :             }
    3671             : 
    3672         200 :             const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
    3673         200 :             const double dfSpacingMiddle =
    3674         200 :                 pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
    3675         200 :             const double dfSpacingLast =
    3676         200 :                 pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
    3677             : 
    3678         200 :             CPLDebug("GDAL_netCDF",
    3679             :                      "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3680             :                      "dfSpacingLast: %f",
    3681             :                      static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
    3682             :                      dfSpacingLast);
    3683             : #ifdef NCDF_DEBUG
    3684             :             CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
    3685             :                      pdfXCoord[1], pdfXCoord[xdim / 2],
    3686             :                      pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
    3687             :                      pdfXCoord[xdim - 1]);
    3688             : #endif
    3689             : 
    3690             :             // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
    3691             :             // requires a 0.02% tolerance, so let's settle for 0.05%
    3692             : 
    3693             :             // For float variables, increase to 0.2% (as seen in
    3694             :             // https://github.com/OSGeo/gdal/issues/3663)
    3695         200 :             const double dfEpsRel =
    3696         200 :                 nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3697             : 
    3698             :             const double dfEps =
    3699             :                 dfEpsRel *
    3700         400 :                 std::max(fabs(dfSpacingBegin),
    3701         200 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3702         394 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3703         394 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3704         194 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3705             :             {
    3706         194 :                 bLonSpacingOK = true;
    3707             :             }
    3708           6 :             else if (CPLTestBool(CPLGetConfigOption(
    3709             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3710             :             {
    3711           0 :                 bLonSpacingOK = true;
    3712           0 :                 CPLDebug(
    3713             :                     "GDAL_netCDF",
    3714             :                     "Longitude/X is not equally spaced, but will be considered "
    3715             :                     "as such because of "
    3716             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3717             :             }
    3718             :         }
    3719             : 
    3720         229 :         if (bLonSpacingOK == false)
    3721             :         {
    3722           6 :             CPLDebug(
    3723             :                 "GDAL_netCDF", "%s",
    3724             :                 "Longitude/X is not equally spaced (with a 0.05% tolerance). "
    3725             :                 "You may set the "
    3726             :                 "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3727             :                 "option to YES to ignore this check");
    3728             :         }
    3729             : 
    3730             :         // Check Latitude.
    3731         229 :         bool bLatSpacingOK = false;
    3732             : 
    3733         229 :         if (ydim == 2)
    3734             :         {
    3735          49 :             bLatSpacingOK = true;
    3736             :         }
    3737             :         else
    3738             :         {
    3739         180 :             const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
    3740         180 :             const double dfSpacingMiddle =
    3741         180 :                 pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
    3742             : 
    3743         180 :             const double dfSpacingLast =
    3744         180 :                 pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
    3745             : 
    3746         180 :             CPLDebug("GDAL_netCDF",
    3747             :                      "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3748             :                      "dfSpacingLast: %f",
    3749             :                      (long)ydim, dfSpacingBegin, dfSpacingMiddle,
    3750             :                      dfSpacingLast);
    3751             : #ifdef NCDF_DEBUG
    3752             :             CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
    3753             :                      pdfYCoord[1], pdfYCoord[ydim / 2],
    3754             :                      pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
    3755             :                      pdfYCoord[ydim - 1]);
    3756             : #endif
    3757             : 
    3758         180 :             const double dfEpsRel =
    3759         180 :                 nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3760             : 
    3761             :             const double dfEps =
    3762             :                 dfEpsRel *
    3763         360 :                 std::max(fabs(dfSpacingBegin),
    3764         180 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3765         358 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3766         358 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3767         169 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3768             :             {
    3769         169 :                 bLatSpacingOK = true;
    3770             :             }
    3771          11 :             else if (CPLTestBool(CPLGetConfigOption(
    3772             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3773             :             {
    3774           0 :                 bLatSpacingOK = true;
    3775           0 :                 CPLDebug(
    3776             :                     "GDAL_netCDF",
    3777             :                     "Latitude/Y is not equally spaced, but will be considered "
    3778             :                     "as such because of "
    3779             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3780             :             }
    3781          11 :             else if (!oSRS.IsProjected() &&
    3782          11 :                      fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
    3783          30 :                      fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
    3784           8 :                      fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
    3785             :             {
    3786           8 :                 bLatSpacingOK = true;
    3787           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3788             :                          "Latitude grid not spaced evenly.  "
    3789             :                          "Setting projection for grid spacing is "
    3790             :                          "within 0.1 degrees threshold.");
    3791             : 
    3792           8 :                 CPLDebug("GDAL_netCDF",
    3793             :                          "Latitude grid not spaced evenly, but within 0.1 "
    3794             :                          "degree threshold (probably a Gaussian grid).  "
    3795             :                          "Saving original latitude values in Y_VALUES "
    3796             :                          "geolocation metadata");
    3797           8 :                 Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
    3798             :             }
    3799             : 
    3800         180 :             if (bLatSpacingOK == false)
    3801             :             {
    3802           3 :                 CPLDebug(
    3803             :                     "GDAL_netCDF", "%s",
    3804             :                     "Latitude/Y is not equally spaced (with a 0.05% "
    3805             :                     "tolerance). "
    3806             :                     "You may set the "
    3807             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3808             :                     "option to YES to ignore this check");
    3809             :             }
    3810             :         }
    3811             : 
    3812         229 :         if (bLonSpacingOK && bLatSpacingOK)
    3813             :         {
    3814             :             // We have gridded data so we can set the Georeferencing info.
    3815             : 
    3816             :             // Enable GeoTransform.
    3817             : 
    3818             :             // In the following "actual_range" and "node_offset"
    3819             :             // are attributes used by netCDF files created by GMT.
    3820             :             // If we find them we know how to proceed. Else, use
    3821             :             // the original algorithm.
    3822         222 :             bGotCfGT = true;
    3823             : 
    3824         222 :             int node_offset = 0;
    3825             :             const bool bUseActualRange =
    3826         222 :                 NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
    3827         222 :                                   &node_offset) == CE_None;
    3828             : 
    3829         222 :             double adfActualRange[2] = {0.0, 0.0};
    3830         222 :             double xMinMax[2] = {0.0, 0.0};
    3831         222 :             double yMinMax[2] = {0.0, 0.0};
    3832             : 
    3833             :             const auto RoundMinMaxForFloatVals =
    3834          60 :                 [](double &dfMin, double &dfMax, int nIntervals)
    3835             :             {
    3836             :                 // Helps for a case where longitudes range from
    3837             :                 // -179.99 to 180.0 with a 0.01 degree spacing.
    3838             :                 // However as this is encoded in a float array,
    3839             :                 // -179.99 is actually read as -179.99000549316406 as
    3840             :                 // a double. Try to detect that and correct the rounding
    3841             : 
    3842          88 :                 const auto IsAlmostInteger = [](double dfVal)
    3843             :                 {
    3844          88 :                     constexpr double THRESHOLD_INTEGER = 1e-3;
    3845          88 :                     return std::fabs(dfVal - std::round(dfVal)) <=
    3846          88 :                            THRESHOLD_INTEGER;
    3847             :                 };
    3848             : 
    3849          60 :                 const double dfSpacing = (dfMax - dfMin) / nIntervals;
    3850          60 :                 if (dfSpacing > 0)
    3851             :                 {
    3852          48 :                     const double dfInvSpacing = 1.0 / dfSpacing;
    3853          48 :                     if (IsAlmostInteger(dfInvSpacing))
    3854             :                     {
    3855          20 :                         const double dfRoundedSpacing =
    3856          20 :                             1.0 / std::round(dfInvSpacing);
    3857          20 :                         const double dfMinDivRoundedSpacing =
    3858          20 :                             dfMin / dfRoundedSpacing;
    3859          20 :                         const double dfMaxDivRoundedSpacing =
    3860          20 :                             dfMax / dfRoundedSpacing;
    3861          40 :                         if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
    3862          20 :                             IsAlmostInteger(dfMaxDivRoundedSpacing))
    3863             :                         {
    3864          20 :                             const double dfRoundedMin =
    3865          20 :                                 std::round(dfMinDivRoundedSpacing) *
    3866             :                                 dfRoundedSpacing;
    3867          20 :                             const double dfRoundedMax =
    3868          20 :                                 std::round(dfMaxDivRoundedSpacing) *
    3869             :                                 dfRoundedSpacing;
    3870          20 :                             if (static_cast<float>(dfMin) ==
    3871          20 :                                     static_cast<float>(dfRoundedMin) &&
    3872           8 :                                 static_cast<float>(dfMax) ==
    3873           8 :                                     static_cast<float>(dfRoundedMax))
    3874             :                             {
    3875           7 :                                 dfMin = dfRoundedMin;
    3876           7 :                                 dfMax = dfRoundedMax;
    3877             :                             }
    3878             :                         }
    3879             :                     }
    3880             :                 }
    3881          60 :             };
    3882             : 
    3883         225 :             if (bUseActualRange &&
    3884           3 :                 !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
    3885             :                                    adfActualRange))
    3886             :             {
    3887           1 :                 xMinMax[0] = adfActualRange[0];
    3888           1 :                 xMinMax[1] = adfActualRange[1];
    3889             : 
    3890             :                 // Present xMinMax[] in the same order as padfXCoord
    3891           1 :                 if ((xMinMax[0] - xMinMax[1]) *
    3892           1 :                         (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
    3893             :                     0)
    3894             :                 {
    3895           0 :                     std::swap(xMinMax[0], xMinMax[1]);
    3896             :                 }
    3897             :             }
    3898             :             else
    3899             :             {
    3900         221 :                 xMinMax[0] = pdfXCoord[0];
    3901         221 :                 xMinMax[1] = pdfXCoord[xdim - 1];
    3902         221 :                 node_offset = 0;
    3903             : 
    3904         221 :                 if (nc_var_dimx_datatype == NC_FLOAT)
    3905             :                 {
    3906          30 :                     RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
    3907          30 :                                             poDS->nRasterXSize - 1);
    3908             :                 }
    3909             :             }
    3910             : 
    3911         225 :             if (bUseActualRange &&
    3912           3 :                 !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
    3913             :                                    adfActualRange))
    3914             :             {
    3915           1 :                 yMinMax[0] = adfActualRange[0];
    3916           1 :                 yMinMax[1] = adfActualRange[1];
    3917             : 
    3918             :                 // Present yMinMax[] in the same order as pdfYCoord
    3919           1 :                 if ((yMinMax[0] - yMinMax[1]) *
    3920           1 :                         (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
    3921             :                     0)
    3922             :                 {
    3923           0 :                     std::swap(yMinMax[0], yMinMax[1]);
    3924             :                 }
    3925             :             }
    3926             :             else
    3927             :             {
    3928         221 :                 yMinMax[0] = pdfYCoord[0];
    3929         221 :                 yMinMax[1] = pdfYCoord[ydim - 1];
    3930         221 :                 node_offset = 0;
    3931             : 
    3932         221 :                 if (nc_var_dimy_datatype == NC_FLOAT)
    3933             :                 {
    3934          30 :                     RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
    3935          30 :                                             poDS->nRasterYSize - 1);
    3936             :                 }
    3937             :             }
    3938             : 
    3939         222 :             double dfCoordOffset = 0.0;
    3940         222 :             double dfCoordScale = 1.0;
    3941         222 :             if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
    3942         226 :                                    &dfCoordOffset) &&
    3943           4 :                 !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
    3944             :                                    &dfCoordScale))
    3945             :             {
    3946           4 :                 xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
    3947           4 :                 xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
    3948             :             }
    3949             : 
    3950         222 :             if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
    3951         226 :                                    &dfCoordOffset) &&
    3952           4 :                 !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
    3953             :                                    &dfCoordScale))
    3954             :             {
    3955           4 :                 yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
    3956           4 :                 yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
    3957             :             }
    3958             : 
    3959             :             // Check for reverse order of y-coordinate.
    3960         222 :             if (!bSwitchedXY)
    3961             :             {
    3962         220 :                 poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
    3963         220 :                 if (!poDS->bBottomUp)
    3964             :                 {
    3965          32 :                     std::swap(yMinMax[0], yMinMax[1]);
    3966             :                 }
    3967             :             }
    3968             : 
    3969             :             // Geostationary satellites can specify units in (micro)radians
    3970             :             // So we check if they do, and if so convert to linear units
    3971             :             // (meters)
    3972         222 :             const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
    3973         222 :             if (pszProjName != nullptr)
    3974             :             {
    3975          24 :                 if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
    3976             :                 {
    3977             :                     const double satelliteHeight =
    3978           3 :                         oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
    3979           6 :                     std::string osUnits;
    3980           3 :                     if (NCDFGetAttr(nGroupId, nVarDimXID, "units", osUnits) ==
    3981             :                         CE_None)
    3982             :                     {
    3983           3 :                         if (EQUAL(osUnits.c_str(), "microradian"))
    3984             :                         {
    3985           1 :                             xMinMax[0] =
    3986           1 :                                 xMinMax[0] * satelliteHeight * 0.000001;
    3987           1 :                             xMinMax[1] =
    3988           1 :                                 xMinMax[1] * satelliteHeight * 0.000001;
    3989             :                         }
    3990           3 :                         else if (EQUAL(osUnits.c_str(), "rad") ||
    3991           1 :                                  EQUAL(osUnits.c_str(), "radian"))
    3992             :                         {
    3993           2 :                             xMinMax[0] = xMinMax[0] * satelliteHeight;
    3994           2 :                             xMinMax[1] = xMinMax[1] * satelliteHeight;
    3995             :                         }
    3996             :                     }
    3997           3 :                     if (NCDFGetAttr(nGroupId, nVarDimYID, "units", osUnits) ==
    3998             :                         CE_None)
    3999             :                     {
    4000           3 :                         if (EQUAL(osUnits.c_str(), "microradian"))
    4001             :                         {
    4002           1 :                             yMinMax[0] =
    4003           1 :                                 yMinMax[0] * satelliteHeight * 0.000001;
    4004           1 :                             yMinMax[1] =
    4005           1 :                                 yMinMax[1] * satelliteHeight * 0.000001;
    4006             :                         }
    4007           3 :                         else if (EQUAL(osUnits.c_str(), "rad") ||
    4008           1 :                                  EQUAL(osUnits.c_str(), "radian"))
    4009             :                         {
    4010           2 :                             yMinMax[0] = yMinMax[0] * satelliteHeight;
    4011           2 :                             yMinMax[1] = yMinMax[1] * satelliteHeight;
    4012             :                         }
    4013             :                     }
    4014             :                 }
    4015             :             }
    4016             : 
    4017         222 :             tmpGT[0] = xMinMax[0];
    4018         444 :             tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
    4019         222 :                        (poDS->nRasterXSize + (node_offset - 1));
    4020         222 :             tmpGT[2] = 0;
    4021         222 :             if (bSwitchedXY)
    4022             :             {
    4023           2 :                 tmpGT[3] = yMinMax[0];
    4024           2 :                 tmpGT[4] = 0;
    4025           2 :                 tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
    4026           2 :                            (poDS->nRasterYSize + (node_offset - 1));
    4027             :             }
    4028             :             else
    4029             :             {
    4030         220 :                 tmpGT[3] = yMinMax[1];
    4031         220 :                 tmpGT[4] = 0;
    4032         220 :                 tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
    4033         220 :                            (poDS->nRasterYSize + (node_offset - 1));
    4034             :             }
    4035             : 
    4036             :             // Compute the center of the pixel.
    4037         222 :             if (!node_offset)
    4038             :             {
    4039             :                 // Otherwise its already the pixel center.
    4040         222 :                 tmpGT[0] -= (tmpGT[1] / 2);
    4041         222 :                 tmpGT[3] -= (tmpGT[5] / 2);
    4042             :             }
    4043             :         }
    4044             : 
    4045             :         const auto AreSRSEqualThroughProj4String =
    4046           2 :             [](const OGRSpatialReference &oSRS1,
    4047             :                const OGRSpatialReference &oSRS2)
    4048             :         {
    4049           2 :             char *pszProj4Str1 = nullptr;
    4050           2 :             oSRS1.exportToProj4(&pszProj4Str1);
    4051             : 
    4052           2 :             char *pszProj4Str2 = nullptr;
    4053           2 :             oSRS2.exportToProj4(&pszProj4Str2);
    4054             : 
    4055             :             {
    4056           2 :                 char *pszTmp = strstr(pszProj4Str1, "+datum=");
    4057           2 :                 if (pszTmp)
    4058           0 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4059             :             }
    4060             : 
    4061             :             {
    4062           2 :                 char *pszTmp = strstr(pszProj4Str2, "+datum=");
    4063           2 :                 if (pszTmp)
    4064           2 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4065             :             }
    4066             : 
    4067           2 :             bool bRet = false;
    4068           2 :             if (pszProj4Str1 && pszProj4Str2 &&
    4069           2 :                 EQUAL(pszProj4Str1, pszProj4Str2))
    4070             :             {
    4071           1 :                 bRet = true;
    4072             :             }
    4073             : 
    4074           2 :             CPLFree(pszProj4Str1);
    4075           2 :             CPLFree(pszProj4Str2);
    4076           2 :             return bRet;
    4077             :         };
    4078             : 
    4079         229 :         if (dfLinearUnitsConvFactor != 1.0)
    4080             :         {
    4081          35 :             for (int i = 0; i < 6; ++i)
    4082          30 :                 tmpGT[i] *= dfLinearUnitsConvFactor;
    4083             : 
    4084           5 :             if (paosRemovedMDItems)
    4085             :             {
    4086             :                 char szVarNameX[NC_MAX_NAME + 1];
    4087           5 :                 CPL_IGNORE_RET_VAL(
    4088           5 :                     nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4089             : 
    4090             :                 char szVarNameY[NC_MAX_NAME + 1];
    4091           5 :                 CPL_IGNORE_RET_VAL(
    4092           5 :                     nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4093             : 
    4094           5 :                 paosRemovedMDItems->push_back(
    4095             :                     CPLSPrintf("%s#units", szVarNameX));
    4096           5 :                 paosRemovedMDItems->push_back(
    4097             :                     CPLSPrintf("%s#units", szVarNameY));
    4098             :             }
    4099             :         }
    4100             : 
    4101             :         // If there is a global "geospatial_bounds_crs" attribute, check that it
    4102             :         // is consistent with the SRS, and if so, use it as the SRS
    4103             :         const char *pszGBCRS =
    4104         229 :             FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
    4105         229 :         if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
    4106             :         {
    4107           4 :             OGRSpatialReference oSRSFromGBCRS;
    4108           2 :             oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4109           2 :             if (oSRSFromGBCRS.SetFromUserInput(
    4110             :                     pszGBCRS,
    4111             :                     OGRSpatialReference::
    4112           4 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
    4113           2 :                 AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
    4114             :             {
    4115           1 :                 oSRS = std::move(oSRSFromGBCRS);
    4116           1 :                 SetSpatialRefNoUpdate(&oSRS);
    4117             :             }
    4118             :         }
    4119             : 
    4120         229 :         CPLFree(pdfXCoord);
    4121         229 :         CPLFree(pdfYCoord);
    4122             :     }  // end if(has dims)
    4123             : 
    4124             :     // Process custom GeoTransform GDAL value.
    4125         560 :     if (!osGridMappingValue.empty())
    4126             :     {
    4127         239 :         if (pszGeoTransform != nullptr)
    4128             :         {
    4129             :             const CPLStringList aosGeoTransform(
    4130         256 :                 CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
    4131         128 :             if (aosGeoTransform.size() == 6)
    4132             :             {
    4133         128 :                 bool bUseGeoTransformFromAttribute = true;
    4134             : 
    4135         128 :                 GDALGeoTransform gtFromAttribute;
    4136         896 :                 for (int i = 0; i < 6; i++)
    4137             :                 {
    4138         768 :                     gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
    4139             :                 }
    4140             : 
    4141             :                 // When GDAL writes a raster that is north-up oriented, it
    4142             :                 // writes the "GeoTransform" attribute unmodified, that is with
    4143             :                 // gt.yscale < 0, but the first line is actually the southern-most
    4144             :                 // one, consistently with the values of the "y" coordinate
    4145             :                 // variable. This is wrong... but we have always done that, so
    4146             :                 // this is hard to fix now.
    4147             :                 // However there are datasets like
    4148             :                 // https://public.hub.geosphere.at/datahub/resources/spartacus-v2-1d-1km/filelisting/TN/SPARTACUS2-DAILY_TN_2026.nc
    4149             :                 // that correctly use a positive gt.yscale value. So make sure to not emit
    4150             :                 // a warning when comparing against the geotransform derived from
    4151             :                 // the x/y coordinates.
    4152         128 :                 GDALGeoTransform gtFromAttributeNorthUp = gtFromAttribute;
    4153         133 :                 if (gtFromAttributeNorthUp.yscale > 0 &&
    4154           5 :                     gtFromAttributeNorthUp.IsAxisAligned())
    4155             :                 {
    4156           1 :                     gtFromAttributeNorthUp.yorig +=
    4157           1 :                         poDS->nRasterYSize * gtFromAttributeNorthUp.yscale;
    4158           1 :                     gtFromAttributeNorthUp.yscale =
    4159           1 :                         -gtFromAttributeNorthUp.yscale;
    4160             :                 }
    4161             : 
    4162         128 :                 if (bGotCfGT)
    4163             :                 {
    4164          98 :                     constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
    4165          98 :                     double dfMaxAbsoluteError = 0.0;
    4166         686 :                     for (int i = 0; i < 6; i++)
    4167             :                     {
    4168             :                         double dfAbsoluteError =
    4169         588 :                             std::abs(tmpGT[i] - gtFromAttributeNorthUp[i]);
    4170         588 :                         if (dfAbsoluteError >
    4171         588 :                             std::abs(gtFromAttributeNorthUp[i] *
    4172             :                                      GT_RELERROR_WARN_THRESHOLD))
    4173             :                         {
    4174           3 :                             dfMaxAbsoluteError =
    4175           3 :                                 std::max(dfMaxAbsoluteError, dfAbsoluteError);
    4176             :                         }
    4177             :                     }
    4178             : 
    4179          98 :                     if (dfMaxAbsoluteError > 0)
    4180             :                     {
    4181           3 :                         bUseGeoTransformFromAttribute = false;
    4182           3 :                         CPLError(CE_Warning, CPLE_AppDefined,
    4183             :                                  "GeoTransform read from attribute of %s "
    4184             :                                  "variable differs from value calculated from "
    4185             :                                  "dimension variables (max diff = %g). Using "
    4186             :                                  "value calculated from dimension variables.",
    4187             :                                  osGridMappingValue.c_str(),
    4188             :                                  dfMaxAbsoluteError);
    4189             :                     }
    4190             :                 }
    4191             : 
    4192         128 :                 if (bUseGeoTransformFromAttribute)
    4193             :                 {
    4194         125 :                     if (bGotCfGT)
    4195             :                     {
    4196          95 :                         tmpGT = gtFromAttributeNorthUp;
    4197          95 :                         if (gtFromAttributeNorthUp.IsAxisAligned())
    4198             :                         {
    4199          95 :                             poDS->bBottomUp = true;
    4200             :                         }
    4201             :                     }
    4202             :                     else
    4203             :                     {
    4204          30 :                         tmpGT = gtFromAttribute;
    4205             :                     }
    4206         125 :                     bGotGdalGT = true;
    4207             :                 }
    4208             :             }
    4209             :         }
    4210             :         else
    4211             :         {
    4212             :             // Look for corner array values.
    4213             :             // CPLDebug("GDAL_netCDF",
    4214             :             //           "looking for geotransform corners");
    4215         111 :             bool bGotNN = false;
    4216         111 :             double dfNN = FetchCopyParam(osGridMappingValue.c_str(),
    4217             :                                          "Northernmost_Northing", 0, &bGotNN);
    4218             : 
    4219         111 :             bool bGotSN = false;
    4220         111 :             double dfSN = FetchCopyParam(osGridMappingValue.c_str(),
    4221             :                                          "Southernmost_Northing", 0, &bGotSN);
    4222             : 
    4223         111 :             bool bGotEE = false;
    4224         111 :             double dfEE = FetchCopyParam(osGridMappingValue.c_str(),
    4225             :                                          "Easternmost_Easting", 0, &bGotEE);
    4226             : 
    4227         111 :             bool bGotWE = false;
    4228         111 :             double dfWE = FetchCopyParam(osGridMappingValue.c_str(),
    4229             :                                          "Westernmost_Easting", 0, &bGotWE);
    4230             : 
    4231             :             // Only set the GeoTransform if we got all the values.
    4232         111 :             if (bGotNN && bGotSN && bGotEE && bGotWE)
    4233             :             {
    4234           0 :                 bGotGdalGT = true;
    4235             : 
    4236           0 :                 tmpGT[0] = dfWE;
    4237           0 :                 tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
    4238           0 :                 tmpGT[2] = 0.0;
    4239           0 :                 tmpGT[3] = dfNN;
    4240           0 :                 tmpGT[4] = 0.0;
    4241           0 :                 tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
    4242             :                 // Compute the center of the pixel.
    4243           0 :                 tmpGT[0] = dfWE - (tmpGT[1] / 2);
    4244           0 :                 tmpGT[3] = dfNN - (tmpGT[5] / 2);
    4245             :             }
    4246             :         }  // (pszGeoTransform != NULL)
    4247             : 
    4248         239 :         if (bGotGdalSRS && !bGotGdalGT)
    4249          78 :             CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
    4250             :     }
    4251             : 
    4252         560 :     if (bGotCfGT || bGotGdalGT)
    4253             :     {
    4254         252 :         CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
    4255         252 :                  static_cast<int>(poDS->bBottomUp));
    4256             :     }
    4257             : 
    4258         560 :     if (!pszWKT && !bGotCfSRS)
    4259             :     {
    4260             :         // Some netCDF files have a srid attribute (#6613) like
    4261             :         // urn:ogc:def:crs:EPSG::6931
    4262         321 :         const char *pszSRID = FetchAttr(osGridMappingValue.c_str(), "srid");
    4263         321 :         if (pszSRID != nullptr)
    4264             :         {
    4265           0 :             oSRS.Clear();
    4266           0 :             if (oSRS.SetFromUserInput(
    4267             :                     pszSRID,
    4268             :                     OGRSpatialReference::
    4269           0 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    4270             :             {
    4271           0 :                 CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
    4272           0 :                 std::string osWKTExport = oSRS.exportToWkt();
    4273           0 :                 if (!osWKTExport.empty())
    4274             :                 {
    4275           0 :                     (*returnProjStr) = std::move(osWKTExport);
    4276             :                 }
    4277             :                 else
    4278             :                 {
    4279           0 :                     m_bAddedProjectionVarsDefs = true;
    4280           0 :                     m_bAddedProjectionVarsData = true;
    4281           0 :                     SetSpatialRefNoUpdate(&oSRS);
    4282             :                 }
    4283             :             }
    4284             :         }
    4285             :     }
    4286             : 
    4287         560 :     if (bReadSRSOnly)
    4288         192 :         return;
    4289             : 
    4290             :     // Determines the SRS to be used by the geolocation array, if any
    4291         736 :     std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
    4292         368 :     if (!m_oSRS.IsEmpty())
    4293             :     {
    4294         290 :         OGRSpatialReference oGeogCRS;
    4295         145 :         oGeogCRS.CopyGeogCSFrom(&m_oSRS);
    4296         145 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
    4297             : 
    4298         290 :         std::string osWKTTmp = oGeogCRS.exportToWkt(apszOptions);
    4299         145 :         if (!osWKTTmp.empty())
    4300             :         {
    4301         145 :             osGeolocWKT = std::move(osWKTTmp);
    4302             :         }
    4303             :     }
    4304             : 
    4305             :     // Process geolocation arrays from CF "coordinates" attribute.
    4306         736 :     std::string osGeolocXName, osGeolocYName;
    4307         368 :     if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
    4308         368 :                              osGeolocYName))
    4309             :     {
    4310          61 :         bool bCanCancelGT = true;
    4311          61 :         if ((nVarDimXID != -1) && (nVarDimYID != -1))
    4312             :         {
    4313             :             char szVarNameX[NC_MAX_NAME + 1];
    4314          44 :             CPL_IGNORE_RET_VAL(
    4315          44 :                 nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4316             :             char szVarNameY[NC_MAX_NAME + 1];
    4317          44 :             CPL_IGNORE_RET_VAL(
    4318          44 :                 nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4319          44 :             bCanCancelGT =
    4320          44 :                 !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
    4321             :         }
    4322         100 :         if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
    4323          39 :             !bSwitchedXY)
    4324             :         {
    4325          37 :             bGotCfGT = false;
    4326             :         }
    4327             :     }
    4328         125 :     else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
    4329         435 :              (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
    4330           3 :              ((!bSwitchedXY &&
    4331           3 :                NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    4332           1 :                NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
    4333           2 :               (bSwitchedXY &&
    4334           0 :                NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    4335           0 :                NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
    4336             :     {
    4337             :         // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
    4338             :         // which is indexed by lat, lon variables, but lat has irregular
    4339             :         // spacing.
    4340           1 :         const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
    4341           1 :         const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
    4342           1 :         if (bSwitchedXY)
    4343             :         {
    4344           0 :             std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4345           0 :             GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4346             :         }
    4347             : 
    4348           1 :         CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4349             :                  pszGeolocXFullName, pszGeolocYFullName);
    4350             : 
    4351           1 :         GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4352             :                                         "GEOLOCATION");
    4353             : 
    4354           2 :         CPLString osTMP;
    4355           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4356           1 :                      pszGeolocXFullName);
    4357             : 
    4358           1 :         GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4359           1 :         GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4360           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4361           1 :                      pszGeolocYFullName);
    4362             : 
    4363           1 :         GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4364           1 :         GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4365             : 
    4366           1 :         GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4367           1 :         GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4368             : 
    4369           1 :         GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4370           1 :         GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4371             : 
    4372           1 :         GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4373             :                                         "PIXEL_CENTER", "GEOLOCATION");
    4374             :     }
    4375             : 
    4376             :     // Set GeoTransform if we got a complete one - after projection has been set
    4377         368 :     if (bGotCfGT || bGotGdalGT)
    4378             :     {
    4379         212 :         m_bAddedProjectionVarsDefs = true;
    4380         212 :         m_bAddedProjectionVarsData = true;
    4381         212 :         SetGeoTransformNoUpdate(tmpGT);
    4382             :     }
    4383             : 
    4384             :     // Debugging reports.
    4385         368 :     CPLDebug("GDAL_netCDF",
    4386             :              "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
    4387             :              "bGotGdalSRS=%d bGotGdalGT=%d",
    4388             :              static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
    4389             :              static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
    4390             :              static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
    4391             : 
    4392         368 :     if (!bGotCfGT && !bGotGdalGT)
    4393         156 :         CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
    4394             : 
    4395         368 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
    4396         156 :         CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
    4397             : 
    4398             :     // wish of 6195
    4399             :     // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
    4400         368 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
    4401             :     {
    4402         223 :         if (bGotCfGT || bGotGdalGT)
    4403             :         {
    4404         134 :             bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
    4405          67 :                 papszOpenOptions, "ASSUME_LONGLAT",
    4406             :                 CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
    4407             : 
    4408           2 :             if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
    4409           2 :                 (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
    4410          71 :                 tmpGT[3] <= 90 && tmpGT[3] > -90 &&
    4411           2 :                 (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
    4412             :             {
    4413             : 
    4414           2 :                 poDS->bIsGeographic = true;
    4415             :                 // seems odd to use 4326 so OGC:CRS84
    4416           2 :                 oSRS.SetFromUserInput("OGC:CRS84");
    4417           2 :                 if (returnProjStr != nullptr)
    4418             :                 {
    4419           0 :                     *returnProjStr = oSRS.exportToWkt();
    4420             :                 }
    4421             :                 else
    4422             :                 {
    4423           2 :                     m_bAddedProjectionVarsDefs = true;
    4424           2 :                     m_bAddedProjectionVarsData = true;
    4425           2 :                     SetSpatialRefNoUpdate(&oSRS);
    4426             :                 }
    4427             : 
    4428           2 :                 CPLDebug("netCDF",
    4429             :                          "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
    4430             :                          "none otherwise available and geotransform within "
    4431             :                          "suitable bounds. "
    4432             :                          "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
    4433             :                          "option or "
    4434             :                          "    ASSUME_LONGLAT=NO as open option to bypass this "
    4435             :                          "assumption.");
    4436             :             }
    4437             :         }
    4438             :     }
    4439             : 
    4440             : // Search for Well-known GeogCS if got only CF WKT
    4441             : // Disabled for now, as a named datum also include control points
    4442             : // (see mailing list and bug#4281
    4443             : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
    4444             : 
    4445             : // Disabled for now, but could be set in a config option.
    4446             : #if 0
    4447             :     bool bLookForWellKnownGCS = false;  // This could be a Config Option.
    4448             : 
    4449             :     if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
    4450             :     {
    4451             :         // ET - Could use a more exhaustive method by scanning all EPSG codes in
    4452             :         // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
    4453             :         // for comparing two WKT".
    4454             :         // This code could be contributed to a new function.
    4455             :         // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
    4456             :         //     const OGRSpatialReference *poOther) */
    4457             :         CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
    4458             :         const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
    4459             :         char *pszWKGCS = NULL;
    4460             :         oSRS.exportToPrettyWkt(&pszWKGCS);
    4461             :         for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
    4462             :         {
    4463             :             pszWKGCS = CPLStrdup(pszWKGCSList[i]);
    4464             :             OGRSpatialReference oSRSTmp;
    4465             :             oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
    4466             :             // Set datum to unknown, bug #4281.
    4467             :             if( oSRSTmp.GetAttrNode("DATUM" ) )
    4468             :                 oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
    4469             :             // Could use OGRSpatialReference::StripCTParms(), but let's keep
    4470             :             // TOWGS84.
    4471             :             oSRSTmp.GetRoot()->StripNodes("AXIS");
    4472             :             oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
    4473             :             oSRSTmp.GetRoot()->StripNodes("EXTENSION");
    4474             : 
    4475             :             oSRSTmp.exportToPrettyWkt(&pszWKGCS);
    4476             :             if( oSRS.IsSameGeogCS(&oSRSTmp) )
    4477             :             {
    4478             :                 oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
    4479             :                 oSRS.exportToWkt(&(pszTempProjection));
    4480             :                 SetProjection(pszTempProjection);
    4481             :                 CPLFree(pszTempProjection);
    4482             :             }
    4483             :         }
    4484             :     }
    4485             : #endif
    4486             : }
    4487             : 
    4488         149 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
    4489             :                                          bool bReadSRSOnly)
    4490             : {
    4491         149 :     SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
    4492             :                          nullptr, nullptr);
    4493         149 : }
    4494             : 
    4495         296 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
    4496             : {
    4497             :     // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
    4498             :     // and https://github.com/OSGeo/gdal/issues/7605
    4499             : 
    4500             :     // Check for a structure like:
    4501             :     /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
    4502             :         dimensions:
    4503             :             number_of_lines = 3248 ;
    4504             :             pixels_per_line = 3200 ;
    4505             :             [...]
    4506             :             pixel_control_points = 3200 ;
    4507             :         [...]
    4508             :         group: geophysical_data {
    4509             :           variables:
    4510             :             short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId
    4511             :                 [...]
    4512             :         }
    4513             :         group: navigation_data {
    4514             :           variables:
    4515             :             float longitude(number_of_lines, pixel_control_points) ;
    4516             :                 [...]
    4517             :             float latitude(number_of_lines, pixel_control_points) ;
    4518             :                 [...]
    4519             :         }
    4520             :     }
    4521             :     */
    4522             :     // Note that the longitude and latitude arrays are not indexed by the
    4523             :     // same dimensions. Handle only the case where
    4524             :     // pixel_control_points == pixels_per_line
    4525             :     // If there was a subsampling of the geolocation arrays, we'd need to
    4526             :     // add more logic.
    4527             : 
    4528         592 :     std::string osGroupName;
    4529         296 :     osGroupName.resize(NC_MAX_NAME);
    4530         296 :     NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
    4531         296 :     osGroupName.resize(strlen(osGroupName.data()));
    4532         296 :     if (osGroupName != "geophysical_data")
    4533         295 :         return false;
    4534             : 
    4535           1 :     int nVarDims = 0;
    4536           1 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4537           1 :     if (nVarDims != 2)
    4538           0 :         return false;
    4539             : 
    4540           1 :     int nNavigationDataGrpId = 0;
    4541           1 :     if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
    4542             :         NC_NOERR)
    4543           0 :         return false;
    4544             : 
    4545             :     std::array<int, 2> anVarDimIds;
    4546           1 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4547             : 
    4548           1 :     int nLongitudeId = 0;
    4549           1 :     int nLatitudeId = 0;
    4550           1 :     if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
    4551           2 :             NC_NOERR ||
    4552           1 :         nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
    4553             :             NC_NOERR)
    4554             :     {
    4555           0 :         return false;
    4556             :     }
    4557             : 
    4558           1 :     int nDimsLongitude = 0;
    4559           1 :     NCDF_ERR(
    4560             :         nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
    4561           1 :     int nDimsLatitude = 0;
    4562           1 :     NCDF_ERR(
    4563             :         nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
    4564           1 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4565             :     {
    4566           0 :         return false;
    4567             :     }
    4568             : 
    4569             :     std::array<int, 2> anDimLongitudeIds;
    4570           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
    4571             :                              anDimLongitudeIds.data()));
    4572             :     std::array<int, 2> anDimLatitudeIds;
    4573           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
    4574             :                              anDimLatitudeIds.data()));
    4575           1 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4576             :     {
    4577           0 :         return false;
    4578             :     }
    4579             : 
    4580             :     std::array<size_t, 2> anSizeVarDimIds;
    4581             :     std::array<size_t, 2> anSizeLongLatIds;
    4582           2 :     if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
    4583           1 :               NC_NOERR &&
    4584           1 :           nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
    4585           1 :               NC_NOERR &&
    4586           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
    4587           1 :               NC_NOERR &&
    4588           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
    4589             :               NC_NOERR &&
    4590           1 :           anSizeVarDimIds == anSizeLongLatIds))
    4591             :     {
    4592           0 :         return false;
    4593             :     }
    4594             : 
    4595           1 :     const char *pszGeolocXFullName = "/navigation_data/longitude";
    4596           1 :     const char *pszGeolocYFullName = "/navigation_data/latitude";
    4597             : 
    4598           1 :     if (bSwitchedXY)
    4599             :     {
    4600           0 :         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4601           0 :         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4602             :     }
    4603             : 
    4604           1 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4605             :              pszGeolocXFullName, pszGeolocYFullName);
    4606             : 
    4607           1 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4608             :                                     "GEOLOCATION");
    4609             : 
    4610           1 :     CPLString osTMP;
    4611           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4612             : 
    4613           1 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4614           1 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4615           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4616             : 
    4617           1 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4618           1 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4619             : 
    4620           1 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4621           1 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4622             : 
    4623           1 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4624           1 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4625             : 
    4626           1 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4627             :                                     "GEOLOCATION");
    4628           1 :     return true;
    4629             : }
    4630             : 
    4631         295 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
    4632             : {
    4633             :     // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
    4634             : 
    4635             :     // Check for a structure like:
    4636             :     /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
    4637             :         dimensions:
    4638             :             downtrack = 1280 ;
    4639             :             crosstrack = 1242 ;
    4640             :             bands = 285 ;
    4641             :             [...]
    4642             : 
    4643             :         variables:
    4644             :             float reflectance(downtrack, crosstrack, bands) ;
    4645             : 
    4646             :         group: location {
    4647             :           variables:
    4648             :                 double lon(downtrack, crosstrack) ;
    4649             :                         lon:_FillValue = -9999. ;
    4650             :                         lon:long_name = "Longitude (WGS-84)" ;
    4651             :                         lon:units = "degrees east" ;
    4652             :                 double lat(downtrack, crosstrack) ;
    4653             :                         lat:_FillValue = -9999. ;
    4654             :                         lat:long_name = "Latitude (WGS-84)" ;
    4655             :                         lat:units = "degrees north" ;
    4656             :           } // group location
    4657             : 
    4658             :     }
    4659             :     or
    4660             :     netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
    4661             :         dimensions:
    4662             :                 downtrack = 1664 ;
    4663             :                 crosstrack = 1242 ;
    4664             :                 [...]
    4665             :         variables:
    4666             :                 float group_1_band_depth(downtrack, crosstrack) ;
    4667             :                         group_1_band_depth:_FillValue = -9999.f ;
    4668             :                         group_1_band_depth:long_name = "Group 1 Band Depth" ;
    4669             :                         group_1_band_depth:units = "unitless" ;
    4670             :                 [...]
    4671             :         group: location {
    4672             :           variables:
    4673             :                 double lon(downtrack, crosstrack) ;
    4674             :                         lon:_FillValue = -9999. ;
    4675             :                         lon:long_name = "Longitude (WGS-84)" ;
    4676             :                         lon:units = "degrees east" ;
    4677             :                 double lat(downtrack, crosstrack) ;
    4678             :                         lat:_FillValue = -9999. ;
    4679             :                         lat:long_name = "Latitude (WGS-84)" ;
    4680             :                         lat:units = "degrees north" ;
    4681             :         }
    4682             :     */
    4683             : 
    4684         295 :     int nVarDims = 0;
    4685         295 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4686         295 :     if (nVarDims != 2 && nVarDims != 3)
    4687          14 :         return false;
    4688             : 
    4689         281 :     int nLocationGrpId = 0;
    4690         281 :     if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
    4691          60 :         return false;
    4692             : 
    4693             :     std::array<int, 3> anVarDimIds;
    4694         221 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4695         221 :     if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
    4696          21 :         return false;
    4697             : 
    4698         200 :     int nLongitudeId = 0;
    4699         200 :     int nLatitudeId = 0;
    4700         238 :     if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
    4701          38 :         nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
    4702             :     {
    4703         162 :         return false;
    4704             :     }
    4705             : 
    4706          38 :     int nDimsLongitude = 0;
    4707          38 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
    4708          38 :     int nDimsLatitude = 0;
    4709          38 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
    4710          38 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4711             :     {
    4712          34 :         return false;
    4713             :     }
    4714             : 
    4715             :     std::array<int, 2> anDimLongitudeIds;
    4716           4 :     NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
    4717             :                              anDimLongitudeIds.data()));
    4718             :     std::array<int, 2> anDimLatitudeIds;
    4719           4 :     NCDF_ERR(
    4720             :         nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
    4721           4 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4722             :     {
    4723           0 :         return false;
    4724             :     }
    4725             : 
    4726           8 :     if (anDimLongitudeIds[0] != anVarDimIds[0] ||
    4727           4 :         anDimLongitudeIds[1] != anVarDimIds[1])
    4728             :     {
    4729           0 :         return false;
    4730             :     }
    4731             : 
    4732           4 :     const char *pszGeolocXFullName = "/location/lon";
    4733           4 :     const char *pszGeolocYFullName = "/location/lat";
    4734             : 
    4735           4 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4736             :              pszGeolocXFullName, pszGeolocYFullName);
    4737             : 
    4738           4 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4739             :                                     "GEOLOCATION");
    4740             : 
    4741           4 :     CPLString osTMP;
    4742           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4743             : 
    4744           4 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4745           4 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4746           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4747             : 
    4748           4 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4749           4 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4750             : 
    4751           4 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4752           4 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4753             : 
    4754           4 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4755           4 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4756             : 
    4757           4 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4758             :                                     "GEOLOCATION");
    4759           4 :     return true;
    4760             : }
    4761             : 
    4762         368 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
    4763             :                                         const std::string &osGeolocWKT,
    4764             :                                         std::string &osGeolocXNameOut,
    4765             :                                         std::string &osGeolocYNameOut)
    4766             : {
    4767         368 :     bool bAddGeoloc = false;
    4768         368 :     char *pszCoordinates = nullptr;
    4769             : 
    4770             :     // If there is no explicit "coordinates" attribute, check if there are
    4771             :     // "lon" and "lat" 2D variables whose dimensions are the last
    4772             :     // 2 ones of the variable of interest.
    4773         368 :     if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
    4774             :         CE_None)
    4775             :     {
    4776         317 :         CPLFree(pszCoordinates);
    4777         317 :         pszCoordinates = nullptr;
    4778             : 
    4779         317 :         int nVarDims = 0;
    4780         317 :         NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4781         317 :         if (nVarDims >= 2)
    4782             :         {
    4783         634 :             std::vector<int> anVarDimIds(nVarDims);
    4784         317 :             NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4785             : 
    4786         317 :             int nLongitudeId = 0;
    4787         317 :             int nLatitudeId = 0;
    4788         389 :             if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
    4789          72 :                 nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
    4790             :             {
    4791          72 :                 int nDimsLongitude = 0;
    4792          72 :                 NCDF_ERR(
    4793             :                     nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
    4794          72 :                 int nDimsLatitude = 0;
    4795          72 :                 NCDF_ERR(
    4796             :                     nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
    4797          72 :                 if (nDimsLongitude == 2 && nDimsLatitude == 2)
    4798             :                 {
    4799          42 :                     std::vector<int> anDimLongitudeIds(2);
    4800          21 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
    4801             :                                              anDimLongitudeIds.data()));
    4802          42 :                     std::vector<int> anDimLatitudeIds(2);
    4803          21 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
    4804             :                                              anDimLatitudeIds.data()));
    4805          21 :                     if (anDimLongitudeIds == anDimLatitudeIds &&
    4806          42 :                         anVarDimIds[anVarDimIds.size() - 2] ==
    4807          63 :                             anDimLongitudeIds[0] &&
    4808          42 :                         anVarDimIds[anVarDimIds.size() - 1] ==
    4809          21 :                             anDimLongitudeIds[1])
    4810             :                     {
    4811          21 :                         pszCoordinates = CPLStrdup("lon lat");
    4812             :                     }
    4813             :                 }
    4814             :             }
    4815             :         }
    4816             :     }
    4817             : 
    4818         368 :     if (pszCoordinates)
    4819             :     {
    4820             :         // Get X and Y geolocation names from coordinates attribute.
    4821             :         const CPLStringList aosCoordinates(
    4822         144 :             NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    4823          72 :         if (aosCoordinates.size() >= 2)
    4824             :         {
    4825             :             char szGeolocXName[NC_MAX_NAME + 1];
    4826             :             char szGeolocYName[NC_MAX_NAME + 1];
    4827          69 :             szGeolocXName[0] = '\0';
    4828          69 :             szGeolocYName[0] = '\0';
    4829             : 
    4830             :             // Test that each variable is longitude/latitude.
    4831         220 :             for (int i = 0; i < aosCoordinates.size(); i++)
    4832             :             {
    4833         151 :                 if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
    4834             :                 {
    4835          58 :                     int nOtherGroupId = -1;
    4836          58 :                     int nOtherVarId = -1;
    4837             :                     // Check that the variable actually exists
    4838             :                     // Needed on Sentinel-3 products
    4839          58 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4840          58 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4841             :                     {
    4842          56 :                         snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
    4843             :                                  aosCoordinates[i]);
    4844             :                     }
    4845             :                 }
    4846          93 :                 else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
    4847             :                 {
    4848          58 :                     int nOtherGroupId = -1;
    4849          58 :                     int nOtherVarId = -1;
    4850             :                     // Check that the variable actually exists
    4851             :                     // Needed on Sentinel-3 products
    4852          58 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4853          58 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4854             :                     {
    4855          56 :                         snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
    4856             :                                  aosCoordinates[i]);
    4857             :                     }
    4858             :                 }
    4859             :             }
    4860             :             // Add GEOLOCATION metadata.
    4861          69 :             if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
    4862             :             {
    4863          56 :                 osGeolocXNameOut = szGeolocXName;
    4864          56 :                 osGeolocYNameOut = szGeolocYName;
    4865             : 
    4866         112 :                 std::string osGeolocXFullName;
    4867         112 :                 std::string osGeolocYFullName;
    4868          56 :                 if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
    4869         112 :                                            osGeolocXFullName) == CE_None &&
    4870          56 :                     NCDFResolveVarFullName(nGroupId, szGeolocYName,
    4871             :                                            osGeolocYFullName) == CE_None)
    4872             :                 {
    4873          56 :                     if (bSwitchedXY)
    4874             :                     {
    4875           2 :                         std::swap(osGeolocXFullName, osGeolocYFullName);
    4876           2 :                         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
    4877             :                                                         "GEOLOCATION");
    4878             :                     }
    4879             : 
    4880          56 :                     bAddGeoloc = true;
    4881          56 :                     CPLDebug("GDAL_netCDF",
    4882             :                              "using variables %s and %s for GEOLOCATION",
    4883             :                              osGeolocXFullName.c_str(),
    4884             :                              osGeolocYFullName.c_str());
    4885             : 
    4886          56 :                     GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4887             :                                                     "GEOLOCATION");
    4888             : 
    4889         112 :                     CPLString osTMP;
    4890             :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4891          56 :                                  osGeolocXFullName.c_str());
    4892             : 
    4893          56 :                     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
    4894             :                                                     "GEOLOCATION");
    4895          56 :                     GDALPamDataset::SetMetadataItem("X_BAND", "1",
    4896             :                                                     "GEOLOCATION");
    4897             :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4898          56 :                                  osGeolocYFullName.c_str());
    4899             : 
    4900          56 :                     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
    4901             :                                                     "GEOLOCATION");
    4902          56 :                     GDALPamDataset::SetMetadataItem("Y_BAND", "1",
    4903             :                                                     "GEOLOCATION");
    4904             : 
    4905          56 :                     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
    4906             :                                                     "GEOLOCATION");
    4907          56 :                     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
    4908             :                                                     "GEOLOCATION");
    4909             : 
    4910          56 :                     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
    4911             :                                                     "GEOLOCATION");
    4912          56 :                     GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
    4913             :                                                     "GEOLOCATION");
    4914             : 
    4915          56 :                     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4916             :                                                     "PIXEL_CENTER",
    4917             :                                                     "GEOLOCATION");
    4918             :                 }
    4919             :                 else
    4920             :                 {
    4921           0 :                     CPLDebug("GDAL_netCDF",
    4922             :                              "cannot resolve location of "
    4923             :                              "lat/lon variables specified by the coordinates "
    4924             :                              "attribute [%s]",
    4925             :                              pszCoordinates);
    4926          56 :                 }
    4927             :             }
    4928             :             else
    4929             :             {
    4930          13 :                 CPLDebug("GDAL_netCDF",
    4931             :                          "coordinates attribute [%s] is unsupported",
    4932             :                          pszCoordinates);
    4933             :             }
    4934             :         }
    4935             :         else
    4936             :         {
    4937           3 :             CPLDebug("GDAL_netCDF",
    4938             :                      "coordinates attribute [%s] with %d element(s) is "
    4939             :                      "unsupported",
    4940             :                      pszCoordinates, aosCoordinates.size());
    4941             :         }
    4942             :     }
    4943             : 
    4944             :     else
    4945             :     {
    4946         296 :         bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
    4947             : 
    4948         296 :         if (!bAddGeoloc)
    4949         295 :             bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
    4950             :     }
    4951             : 
    4952         368 :     CPLFree(pszCoordinates);
    4953             : 
    4954         368 :     return bAddGeoloc;
    4955             : }
    4956             : 
    4957           8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
    4958             :                                        const char *szDimName)
    4959             : {
    4960             :     // Get values.
    4961           8 :     char *pszVarValues = nullptr;
    4962           8 :     CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
    4963           8 :     if (eErr != CE_None)
    4964           0 :         return eErr;
    4965             : 
    4966             :     // Write metadata.
    4967           8 :     char szTemp[NC_MAX_NAME + 1 + 32] = {};
    4968           8 :     snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
    4969           8 :     GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
    4970             : 
    4971           8 :     CPLFree(pszVarValues);
    4972             : 
    4973           8 :     return CE_None;
    4974             : }
    4975             : 
    4976           0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
    4977             :                                         int &nVarLen)
    4978             : {
    4979           0 :     nVarLen = 0;
    4980             : 
    4981             :     // Get Y_VALUES as tokens.
    4982             :     const CPLStringList aosValues(
    4983           0 :         NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")));
    4984           0 :     if (aosValues.empty())
    4985           0 :         return nullptr;
    4986             : 
    4987             :     // Initialize and fill array.
    4988           0 :     nVarLen = aosValues.size();
    4989             :     double *pdfVarValues =
    4990           0 :         static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
    4991             : 
    4992           0 :     for (int i = 0, j = 0; i < nVarLen; i++)
    4993             :     {
    4994           0 :         if (!bBottomUp)
    4995           0 :             j = nVarLen - 1 - i;
    4996             :         else
    4997           0 :             j = i;  // Invert latitude values.
    4998           0 :         char *pszTemp = nullptr;
    4999           0 :         pdfVarValues[j] = CPLStrtod(aosValues[i], &pszTemp);
    5000             :     }
    5001             : 
    5002           0 :     return pdfVarValues;
    5003             : }
    5004             : 
    5005             : /************************************************************************/
    5006             : /*                       SetSpatialRefNoUpdate()                        */
    5007             : /************************************************************************/
    5008             : 
    5009         281 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
    5010             : {
    5011         281 :     m_oSRS.Clear();
    5012         281 :     if (poSRS)
    5013         274 :         m_oSRS = *poSRS;
    5014         281 :     m_bHasProjection = true;
    5015         281 : }
    5016             : 
    5017             : /************************************************************************/
    5018             : /*                           SetSpatialRef()                            */
    5019             : /************************************************************************/
    5020             : 
    5021          82 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    5022             : {
    5023         164 :     CPLMutexHolderD(&hNCMutex);
    5024             : 
    5025          82 :     if (GetAccess() != GA_Update || m_bHasProjection)
    5026             :     {
    5027           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5028             :                  "netCDFDataset::_SetProjection() should only be called once "
    5029             :                  "in update mode!");
    5030           0 :         return CE_Failure;
    5031             :     }
    5032             : 
    5033          82 :     if (m_bHasGeoTransform)
    5034             :     {
    5035          32 :         SetSpatialRefNoUpdate(poSRS);
    5036             : 
    5037             :         // For NC4/NC4C, writing both projection variables and data,
    5038             :         // followed by redefining nodata value, cancels the projection
    5039             :         // info from the Band variable, so for now only write the
    5040             :         // variable definitions, and write data at the end.
    5041             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5042          32 :         return AddProjectionVars(true, nullptr, nullptr);
    5043             :     }
    5044             : 
    5045          50 :     SetSpatialRefNoUpdate(poSRS);
    5046             : 
    5047          50 :     return CE_None;
    5048             : }
    5049             : 
    5050             : /************************************************************************/
    5051             : /*                      SetGeoTransformNoUpdate()                       */
    5052             : /************************************************************************/
    5053             : 
    5054         295 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform &gt)
    5055             : {
    5056         295 :     m_gt = gt;
    5057         295 :     m_bHasGeoTransform = true;
    5058         295 : }
    5059             : 
    5060             : /************************************************************************/
    5061             : /*                          SetGeoTransform()                           */
    5062             : /************************************************************************/
    5063             : 
    5064          83 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform &gt)
    5065             : {
    5066         166 :     CPLMutexHolderD(&hNCMutex);
    5067             : 
    5068          83 :     if (GetAccess() != GA_Update || m_bHasGeoTransform)
    5069             :     {
    5070           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5071             :                  "netCDFDataset::SetGeoTransform() should only be called once "
    5072             :                  "in update mode!");
    5073           0 :         return CE_Failure;
    5074             :     }
    5075             : 
    5076          83 :     CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt.xorig,
    5077          83 :              gt.xscale, gt.xrot, gt.yorig, gt.yrot, gt.yscale);
    5078             : 
    5079          83 :     SetGeoTransformNoUpdate(gt);
    5080             : 
    5081          83 :     if (m_bHasProjection)
    5082             :     {
    5083             : 
    5084             :         // For NC4/NC4C, writing both projection variables and data,
    5085             :         // followed by redefining nodata value, cancels the projection
    5086             :         // info from the Band variable, so for now only write the
    5087             :         // variable definitions, and write data at the end.
    5088             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5089           3 :         return AddProjectionVars(true, nullptr, nullptr);
    5090             :     }
    5091             : 
    5092          80 :     return CE_None;
    5093             : }
    5094             : 
    5095             : /************************************************************************/
    5096             : /*                        NCDFWriteSRSVariable()                        */
    5097             : /************************************************************************/
    5098             : 
    5099         136 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
    5100             :                          char **ppszCFProjection, bool bWriteGDALTags,
    5101             :                          const std::string &srsVarName)
    5102             : {
    5103         136 :     char *pszCFProjection = nullptr;
    5104         136 :     char **papszKeyValues = nullptr;
    5105         136 :     poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
    5106             : 
    5107         136 :     if (bWriteGDALTags)
    5108             :     {
    5109         135 :         const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
    5110         135 :         if (pszWKT)
    5111             :         {
    5112             :             // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
    5113         135 :             papszKeyValues =
    5114         135 :                 CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
    5115             :         }
    5116             :     }
    5117             : 
    5118         136 :     const int nValues = CSLCount(papszKeyValues);
    5119             : 
    5120             :     int NCDFVarID;
    5121         272 :     std::string varNameRadix(pszCFProjection);
    5122         136 :     int nCounter = 2;
    5123             :     while (true)
    5124             :     {
    5125         138 :         NCDFVarID = -1;
    5126         138 :         nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
    5127         138 :         if (NCDFVarID < 0)
    5128         133 :             break;
    5129             : 
    5130           5 :         int nbAttr = 0;
    5131           5 :         NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
    5132           5 :         bool bSame = nbAttr == nValues;
    5133          41 :         for (int i = 0; bSame && (i < nbAttr); i++)
    5134             :         {
    5135             :             char szAttrName[NC_MAX_NAME + 1];
    5136          38 :             szAttrName[0] = 0;
    5137          38 :             NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
    5138             : 
    5139             :             const char *pszValue =
    5140          38 :                 CSLFetchNameValue(papszKeyValues, szAttrName);
    5141          38 :             if (!pszValue)
    5142             :             {
    5143           0 :                 bSame = false;
    5144           2 :                 break;
    5145             :             }
    5146             : 
    5147          38 :             nc_type atttype = NC_NAT;
    5148          38 :             size_t attlen = 0;
    5149          38 :             NCDF_ERR(
    5150             :                 nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
    5151          38 :             if (atttype != NC_CHAR && atttype != NC_DOUBLE)
    5152             :             {
    5153           0 :                 bSame = false;
    5154           0 :                 break;
    5155             :             }
    5156          38 :             if (atttype == NC_CHAR)
    5157             :             {
    5158          15 :                 if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
    5159             :                 {
    5160           0 :                     bSame = false;
    5161           0 :                     break;
    5162             :                 }
    5163          15 :                 std::string val;
    5164          15 :                 NCDFGetAttr(cdfid, NCDFVarID, szAttrName, val);
    5165          15 :                 if (val != pszValue)
    5166             :                 {
    5167           0 :                     bSame = false;
    5168           0 :                     break;
    5169             :                 }
    5170             :             }
    5171             :             else
    5172             :             {
    5173             :                 const CPLStringList aosTokens(
    5174          23 :                     CSLTokenizeString2(pszValue, ",", 0));
    5175          23 :                 if (static_cast<size_t>(aosTokens.size()) != attlen)
    5176             :                 {
    5177           0 :                     bSame = false;
    5178           0 :                     break;
    5179             :                 }
    5180             :                 double vals[2];
    5181          23 :                 nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
    5182          44 :                 if (vals[0] != CPLAtof(aosTokens[0]) ||
    5183          21 :                     (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
    5184             :                 {
    5185           2 :                     bSame = false;
    5186           2 :                     break;
    5187             :                 }
    5188             :             }
    5189             :         }
    5190           5 :         if (bSame)
    5191             :         {
    5192           3 :             *ppszCFProjection = pszCFProjection;
    5193           3 :             CSLDestroy(papszKeyValues);
    5194           3 :             return NCDFVarID;
    5195             :         }
    5196           2 :         CPLFree(pszCFProjection);
    5197           2 :         pszCFProjection =
    5198           2 :             CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
    5199           2 :         nCounter++;
    5200           2 :     }
    5201             : 
    5202         133 :     *ppszCFProjection = pszCFProjection;
    5203             : 
    5204             :     const char *pszVarName;
    5205             : 
    5206         133 :     if (srsVarName != "")
    5207             :     {
    5208          38 :         pszVarName = srsVarName.c_str();
    5209             :     }
    5210             :     else
    5211             :     {
    5212          95 :         pszVarName = pszCFProjection;
    5213             :     }
    5214             : 
    5215         133 :     int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
    5216         133 :     NCDF_ERR(status);
    5217        1303 :     for (int i = 0; i < nValues; ++i)
    5218             :     {
    5219        1170 :         char *pszKey = nullptr;
    5220        1170 :         const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
    5221        1170 :         if (pszKey && pszValue)
    5222             :         {
    5223        2340 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
    5224        1170 :             double adfValues[2] = {0, 0};
    5225        1170 :             const int nDoubleCount = std::min(2, aosTokens.size());
    5226        1170 :             if (!(aosTokens.size() == 2 &&
    5227        2339 :                   CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
    5228        1169 :                 CPLGetValueType(pszValue) == CPL_VALUE_STRING)
    5229             :             {
    5230         531 :                 status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
    5231             :                                          strlen(pszValue), pszValue);
    5232             :             }
    5233             :             else
    5234             :             {
    5235        1279 :                 for (int j = 0; j < nDoubleCount; ++j)
    5236         640 :                     adfValues[j] = CPLAtof(aosTokens[j]);
    5237         639 :                 status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
    5238             :                                            nDoubleCount, adfValues);
    5239             :             }
    5240        1170 :             NCDF_ERR(status);
    5241             :         }
    5242        1170 :         CPLFree(pszKey);
    5243             :     }
    5244             : 
    5245         133 :     CSLDestroy(papszKeyValues);
    5246         133 :     return NCDFVarID;
    5247             : }
    5248             : 
    5249             : /************************************************************************/
    5250             : /*                   NCDFWriteLonLatVarsAttributes()                    */
    5251             : /************************************************************************/
    5252             : 
    5253         103 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
    5254             :                                    int nVarLatID)
    5255             : {
    5256             : 
    5257             :     try
    5258             :     {
    5259         103 :         vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
    5260         103 :         vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
    5261         103 :         vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
    5262         103 :         vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
    5263         103 :         vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
    5264         103 :         vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
    5265             :     }
    5266           0 :     catch (nccfdriver::SG_Exception &e)
    5267             :     {
    5268           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5269             :     }
    5270         103 : }
    5271             : 
    5272             : /************************************************************************/
    5273             : /*                  NCDFWriteRLonRLatVarsAttributes()                   */
    5274             : /************************************************************************/
    5275             : 
    5276           0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
    5277             :                                      int nVarRLonID, int nVarRLatID)
    5278             : {
    5279             :     try
    5280             :     {
    5281           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
    5282           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
    5283             :                               "latitude in rotated pole grid");
    5284           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
    5285           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
    5286             : 
    5287           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
    5288           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
    5289             :                               "longitude in rotated pole grid");
    5290           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
    5291           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
    5292             :     }
    5293           0 :     catch (nccfdriver::SG_Exception &e)
    5294             :     {
    5295           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5296             :     }
    5297           0 : }
    5298             : 
    5299             : /************************************************************************/
    5300             : /*                       NCDFGetProjectedCFUnit()                       */
    5301             : /************************************************************************/
    5302             : 
    5303          44 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
    5304             : {
    5305          44 :     char *pszUnitsToWrite = nullptr;
    5306          44 :     poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
    5307          44 :     std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
    5308          44 :     CPLFree(pszUnitsToWrite);
    5309          88 :     return osRet;
    5310             : }
    5311             : 
    5312             : /************************************************************************/
    5313             : /*                     NCDFWriteXYVarsAttributes()                      */
    5314             : /************************************************************************/
    5315             : 
    5316          31 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
    5317             :                                int nVarYID, const OGRSpatialReference *poSRS)
    5318             : {
    5319          62 :     const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
    5320             : 
    5321             :     try
    5322             :     {
    5323          31 :         vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
    5324          31 :         vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
    5325          31 :         if (!osUnitsToWrite.empty())
    5326          31 :             vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
    5327          31 :         vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
    5328          31 :         vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
    5329          31 :         if (!osUnitsToWrite.empty())
    5330          31 :             vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
    5331             :     }
    5332           0 :     catch (nccfdriver::SG_Exception &e)
    5333             :     {
    5334           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5335             :     }
    5336          31 : }
    5337             : 
    5338             : /************************************************************************/
    5339             : /*                         AddProjectionVars()                          */
    5340             : /************************************************************************/
    5341             : 
    5342         176 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
    5343             :                                         GDALProgressFunc pfnProgress,
    5344             :                                         void *pProgressData)
    5345             : {
    5346         176 :     if (nCFVersion >= 1.8)
    5347           0 :         return CE_None;  // do nothing
    5348             : 
    5349         176 :     bool bWriteGridMapping = false;
    5350         176 :     bool bWriteLonLat = false;
    5351         176 :     bool bHasGeoloc = false;
    5352         176 :     bool bWriteGDALTags = false;
    5353         176 :     bool bWriteGeoTransform = false;
    5354             : 
    5355             :     // For GEOLOCATION information.
    5356         176 :     GDALDatasetUniquePtr poDS_X;
    5357         176 :     GDALDatasetUniquePtr poDS_Y;
    5358         176 :     GDALRasterBand *poBand_X = nullptr;
    5359         176 :     GDALRasterBand *poBand_Y = nullptr;
    5360             : 
    5361         352 :     OGRSpatialReference oSRS(m_oSRS);
    5362         176 :     if (!m_oSRS.IsEmpty())
    5363             :     {
    5364         150 :         if (oSRS.IsProjected())
    5365          62 :             bIsProjected = true;
    5366          88 :         else if (oSRS.IsGeographic())
    5367          88 :             bIsGeographic = true;
    5368             :     }
    5369             : 
    5370         176 :     if (bDefsOnly)
    5371             :     {
    5372         176 :         const std::string osProjection = m_oSRS.exportToWkt();
    5373         163 :         CPLDebug("GDAL_netCDF",
    5374             :                  "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
    5375          75 :                  osProjection.empty() ? "(null)" : osProjection.c_str(),
    5376          88 :                  static_cast<int>(bIsProjected),
    5377          88 :                  static_cast<int>(bIsGeographic));
    5378             : 
    5379          88 :         if (!m_bHasGeoTransform)
    5380           5 :             CPLDebug("GDAL_netCDF",
    5381             :                      "netCDFDataset::AddProjectionVars() called, "
    5382             :                      "but GeoTransform has not yet been defined!");
    5383             : 
    5384          88 :         if (!m_bHasProjection)
    5385           6 :             CPLDebug("GDAL_netCDF",
    5386             :                      "netCDFDataset::AddProjectionVars() called, "
    5387             :                      "but Projection has not yet been defined!");
    5388             :     }
    5389             : 
    5390             :     // Check GEOLOCATION information.
    5391             :     CSLConstList papszGeolocationInfo =
    5392         176 :         netCDFDataset::GetMetadata("GEOLOCATION");
    5393         176 :     if (papszGeolocationInfo != nullptr)
    5394             :     {
    5395             :         // Look for geolocation datasets.
    5396             :         const char *pszDSName =
    5397          10 :             CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
    5398          10 :         if (pszDSName != nullptr)
    5399          10 :             poDS_X.reset(GDALDataset::Open(
    5400             :                 pszDSName,
    5401             :                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
    5402          10 :         pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
    5403          10 :         if (pszDSName != nullptr)
    5404          10 :             poDS_Y.reset(GDALDataset::Open(
    5405             :                 pszDSName,
    5406             :                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
    5407             : 
    5408          10 :         if (poDS_X != nullptr && poDS_Y != nullptr)
    5409             :         {
    5410          10 :             int nBand = std::max(1, atoi(CSLFetchNameValueDef(
    5411          10 :                                         papszGeolocationInfo, "X_BAND", "0")));
    5412          10 :             poBand_X = poDS_X->GetRasterBand(nBand);
    5413          10 :             nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
    5414          10 :                                                           "Y_BAND", "0")));
    5415          10 :             poBand_Y = poDS_Y->GetRasterBand(nBand);
    5416             : 
    5417             :             // If geoloc bands are found, do basic validation based on their
    5418             :             // dimensions.
    5419          10 :             if (poBand_X != nullptr && poBand_Y != nullptr)
    5420             :             {
    5421          10 :                 const int nXSize_XBand = poBand_X->GetXSize();
    5422          10 :                 const int nYSize_XBand = poBand_X->GetYSize();
    5423          10 :                 const int nXSize_YBand = poBand_Y->GetXSize();
    5424          10 :                 const int nYSize_YBand = poBand_Y->GetYSize();
    5425             : 
    5426             :                 // TODO 1D geolocation arrays not implemented.
    5427          10 :                 if (nYSize_XBand == 1 && nYSize_YBand == 1)
    5428             :                 {
    5429           0 :                     bHasGeoloc = false;
    5430           0 :                     CPLDebug("GDAL_netCDF",
    5431             :                              "1D GEOLOCATION arrays not supported yet");
    5432             :                 }
    5433             :                 // 2D bands must have same sizes as the raster bands.
    5434          10 :                 else if (nXSize_XBand != nRasterXSize ||
    5435          10 :                          nYSize_XBand != nRasterYSize ||
    5436          10 :                          nXSize_YBand != nRasterXSize ||
    5437          10 :                          nYSize_YBand != nRasterYSize)
    5438             :                 {
    5439           0 :                     bHasGeoloc = false;
    5440           0 :                     CPLDebug("GDAL_netCDF",
    5441             :                              "GEOLOCATION array sizes (%dx%d %dx%d) differ "
    5442             :                              "from raster (%dx%d), not supported",
    5443             :                              nXSize_XBand, nYSize_XBand, nXSize_YBand,
    5444             :                              nYSize_YBand, nRasterXSize, nRasterYSize);
    5445             :                 }
    5446             :                 else
    5447             :                 {
    5448          10 :                     bHasGeoloc = true;
    5449          10 :                     CPLDebug("GDAL_netCDF",
    5450             :                              "dataset has GEOLOCATION information, will try to "
    5451             :                              "write it");
    5452             :                 }
    5453             :             }
    5454             :         }
    5455             :     }
    5456             : 
    5457             :     // Process projection options.
    5458         176 :     if (bIsProjected)
    5459             :     {
    5460             :         bool bIsCfProjection =
    5461          62 :             oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
    5462          62 :         bWriteGridMapping = true;
    5463          62 :         bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS", true);
    5464             :         // Force WRITE_GDAL_TAGS if is not a CF projection.
    5465          62 :         if (!bWriteGDALTags && !bIsCfProjection)
    5466           0 :             bWriteGDALTags = true;
    5467          62 :         if (bWriteGDALTags)
    5468          62 :             bWriteGeoTransform = true;
    5469             : 
    5470             :         // Write lon/lat: default is NO, except if has geolocation.
    5471             :         // With IF_NEEDED: write if has geoloc or is not CF projection.
    5472             :         const char *pszValue =
    5473          62 :             aosCreationOptions.FetchNameValue("WRITE_LONLAT");
    5474          62 :         if (pszValue)
    5475             :         {
    5476           6 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5477             :             {
    5478           0 :                 bWriteLonLat = bHasGeoloc || !bIsCfProjection;
    5479             :             }
    5480             :             else
    5481             :             {
    5482           6 :                 bWriteLonLat = CPLTestBool(pszValue);
    5483             :             }
    5484             :         }
    5485             :         else
    5486             :         {
    5487          56 :             bWriteLonLat = bHasGeoloc;
    5488             :         }
    5489             : 
    5490             :         // Save value of pszCFCoordinates for later.
    5491          62 :         if (bWriteLonLat)
    5492             :         {
    5493           8 :             pszCFCoordinates = NCDF_LONLAT;
    5494             :         }
    5495             :     }
    5496             :     else
    5497             :     {
    5498             :         // Files without a Datum will not have a grid_mapping variable and
    5499             :         // geographic information.
    5500         114 :         bWriteGridMapping = bIsGeographic;
    5501             : 
    5502         114 :         if (bHasGeoloc)
    5503             :         {
    5504           8 :             bWriteLonLat = true;
    5505             :         }
    5506             :         else
    5507             :         {
    5508         106 :             bWriteGDALTags = aosCreationOptions.FetchBool("WRITE_GDAL_TAGS",
    5509             :                                                           bWriteGridMapping);
    5510         106 :             if (bWriteGDALTags)
    5511          88 :                 bWriteGeoTransform = true;
    5512             : 
    5513             :             const char *pszValue =
    5514         106 :                 aosCreationOptions.FetchNameValueDef("WRITE_LONLAT", "YES");
    5515         106 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5516           0 :                 bWriteLonLat = true;
    5517             :             else
    5518         106 :                 bWriteLonLat = CPLTestBool(pszValue);
    5519             :             //  Don't write lon/lat if no source geotransform.
    5520         106 :             if (!m_bHasGeoTransform)
    5521           0 :                 bWriteLonLat = false;
    5522             :             // If we don't write lon/lat, set dimnames to X/Y and write gdal
    5523             :             // tags.
    5524         106 :             if (!bWriteLonLat)
    5525             :             {
    5526           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5527             :                          "creating geographic file without lon/lat values!");
    5528           0 :                 if (m_bHasGeoTransform)
    5529             :                 {
    5530           0 :                     bWriteGDALTags = true;  // Not desirable if no geotransform.
    5531           0 :                     bWriteGeoTransform = true;
    5532             :                 }
    5533             :             }
    5534             :         }
    5535             :     }
    5536             : 
    5537             :     // Make sure we write grid_mapping if we need to write GDAL tags.
    5538         176 :     if (bWriteGDALTags)
    5539         150 :         bWriteGridMapping = true;
    5540             : 
    5541             :     // bottom-up value: new driver is bottom-up by default.
    5542             :     // Override with WRITE_BOTTOMUP.
    5543         176 :     bBottomUp = aosCreationOptions.FetchBool("WRITE_BOTTOMUP", true);
    5544             : 
    5545         176 :     if (bDefsOnly)
    5546             :     {
    5547          88 :         CPLDebug(
    5548             :             "GDAL_netCDF",
    5549             :             "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
    5550             :             "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
    5551          88 :             static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
    5552             :             static_cast<int>(bWriteGridMapping),
    5553             :             static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
    5554          88 :             static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
    5555             :     }
    5556             : 
    5557             :     // Exit if nothing to do.
    5558         176 :     if (!bIsProjected && !bWriteLonLat)
    5559           0 :         return CE_None;
    5560             : 
    5561             :     // Define dimension names.
    5562             : 
    5563         176 :     constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
    5564             : 
    5565         176 :     if (bDefsOnly)
    5566             :     {
    5567          88 :         int nVarLonID = -1;
    5568          88 :         int nVarLatID = -1;
    5569          88 :         int nVarXID = -1;
    5570          88 :         int nVarYID = -1;
    5571             : 
    5572          88 :         m_bAddedProjectionVarsDefs = true;
    5573             : 
    5574             :         // Make sure we are in define mode.
    5575          88 :         SetDefineMode(true);
    5576             : 
    5577             :         // Write projection attributes.
    5578          88 :         if (bWriteGridMapping)
    5579             :         {
    5580          75 :             const int NCDFVarID = NCDFWriteSRSVariable(
    5581             :                 cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
    5582          75 :             if (NCDFVarID < 0)
    5583           0 :                 return CE_Failure;
    5584             : 
    5585             :             // Optional GDAL custom projection tags.
    5586          75 :             if (bWriteGDALTags && bWriteGeoTransform && m_bHasGeoTransform)
    5587             :             {
    5588          74 :                 GDALGeoTransform gt(m_gt);
    5589          74 :                 if (!bBottomUp)
    5590             :                 {
    5591             :                     // Change origin from top to bottom and sign of coefficients
    5592             :                     // indexed by row
    5593           2 :                     gt.yorig += nRasterYSize * gt.yscale;
    5594           2 :                     gt.xorig += nRasterYSize * gt.xrot;
    5595           2 :                     gt.xrot = -gt.xrot;
    5596           2 :                     gt.yscale = -gt.yscale;
    5597             :                 }
    5598         148 :                 std::string osGeoTransform = gt.ToString(" ");
    5599          74 :                 CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
    5600             :                          osGeoTransform.c_str());
    5601             : 
    5602          74 :                 const int status = nc_put_att_text(
    5603             :                     cdfid, NCDFVarID, NCDF_GEOTRANSFORM, osGeoTransform.size(),
    5604             :                     osGeoTransform.c_str());
    5605          74 :                 NCDF_ERR(status);
    5606             :             }
    5607             : 
    5608             :             // Write projection variable to band variable.
    5609             :             // Need to call later if there are no bands.
    5610          75 :             AddGridMappingRef();
    5611             :         }  // end if( bWriteGridMapping )
    5612             : 
    5613             :         // Write CF Projection vars.
    5614             : 
    5615          88 :         const bool bIsRotatedPole =
    5616         163 :             pszCFProjection != nullptr &&
    5617          75 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5618             : 
    5619          88 :         if (m_bHasGeoTransform && !m_gt.IsAxisAligned())
    5620             :         {
    5621             :             // Do not write X/Y coordinate arrays
    5622             :         }
    5623             : 
    5624          84 :         else if (bIsRotatedPole)
    5625             :         {
    5626             :             // Rename dims to rlat/rlon.
    5627             :             papszDimName
    5628           0 :                 .Clear();  // If we add other dims one day, this has to change
    5629           0 :             papszDimName.AddString(NCDF_DIMNAME_RLAT);
    5630           0 :             papszDimName.AddString(NCDF_DIMNAME_RLON);
    5631             : 
    5632           0 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
    5633           0 :             NCDF_ERR(status);
    5634           0 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
    5635           0 :             NCDF_ERR(status);
    5636             :         }
    5637             :         // Rename dimensions if lon/lat.
    5638          84 :         else if (!bIsProjected && !bHasGeoloc)
    5639             :         {
    5640             :             // Rename dims to lat/lon.
    5641             :             papszDimName
    5642          53 :                 .Clear();  // If we add other dims one day, this has to change
    5643          53 :             papszDimName.AddString(NCDF_DIMNAME_LAT);
    5644          53 :             papszDimName.AddString(NCDF_DIMNAME_LON);
    5645             : 
    5646          53 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
    5647          53 :             NCDF_ERR(status);
    5648          53 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
    5649          53 :             NCDF_ERR(status);
    5650             :         }
    5651             : 
    5652             :         // Write X/Y attributes.
    5653             :         else /* if( bIsProjected || bHasGeoloc ) */
    5654             :         {
    5655             :             // X
    5656             :             int anXDims[1];
    5657          31 :             anXDims[0] = nXDimID;
    5658          31 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5659             :                      CF_PROJ_X_VAR_NAME, NC_DOUBLE);
    5660          31 :             int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
    5661             :                                     anXDims, &nVarXID);
    5662          31 :             NCDF_ERR(status);
    5663             : 
    5664             :             // Y
    5665             :             int anYDims[1];
    5666          31 :             anYDims[0] = nYDimID;
    5667          31 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5668             :                      CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
    5669          31 :             status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
    5670             :                                 anYDims, &nVarYID);
    5671          31 :             NCDF_ERR(status);
    5672             : 
    5673          31 :             if (bIsProjected)
    5674             :             {
    5675          27 :                 NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
    5676             :             }
    5677             :             else
    5678             :             {
    5679           4 :                 CPLAssert(bHasGeoloc);
    5680             :                 try
    5681             :                 {
    5682           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
    5683           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
    5684             :                                           "x-coordinate in Cartesian system");
    5685           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
    5686           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
    5687           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
    5688             :                                           "y-coordinate in Cartesian system");
    5689           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
    5690             : 
    5691           4 :                     pszCFCoordinates = NCDF_LONLAT;
    5692             :                 }
    5693           0 :                 catch (nccfdriver::SG_Exception &e)
    5694             :                 {
    5695           0 :                     CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5696           0 :                     return CE_Failure;
    5697             :                 }
    5698             :             }
    5699             :         }
    5700             : 
    5701             :         // Write lat/lon attributes if needed.
    5702          88 :         if (bWriteLonLat)
    5703             :         {
    5704          61 :             int anLatDims[2] = {0, 0};
    5705          61 :             int anLonDims[2] = {0, 0};
    5706          61 :             int nLatDims = -1;
    5707          61 :             int nLonDims = -1;
    5708             : 
    5709             :             // Get information.
    5710          61 :             if (bHasGeoloc)
    5711             :             {
    5712             :                 // Geoloc
    5713           5 :                 nLatDims = 2;
    5714           5 :                 anLatDims[0] = nYDimID;
    5715           5 :                 anLatDims[1] = nXDimID;
    5716           5 :                 nLonDims = 2;
    5717           5 :                 anLonDims[0] = nYDimID;
    5718           5 :                 anLonDims[1] = nXDimID;
    5719             :             }
    5720          56 :             else if (bIsProjected)
    5721             :             {
    5722             :                 // Projected
    5723           3 :                 nLatDims = 2;
    5724           3 :                 anLatDims[0] = nYDimID;
    5725           3 :                 anLatDims[1] = nXDimID;
    5726           3 :                 nLonDims = 2;
    5727           3 :                 anLonDims[0] = nYDimID;
    5728           3 :                 anLonDims[1] = nXDimID;
    5729             :             }
    5730             :             else
    5731             :             {
    5732             :                 // Geographic
    5733          53 :                 nLatDims = 1;
    5734          53 :                 anLatDims[0] = nYDimID;
    5735          53 :                 nLonDims = 1;
    5736          53 :                 anLonDims[0] = nXDimID;
    5737             :             }
    5738             : 
    5739          61 :             nc_type eLonLatType = NC_NAT;
    5740          61 :             if (bIsProjected)
    5741             :             {
    5742           4 :                 eLonLatType = NC_FLOAT;
    5743           4 :                 const char *pszValue = aosCreationOptions.FetchNameValueDef(
    5744             :                     "TYPE_LONLAT", "FLOAT");
    5745           4 :                 if (EQUAL(pszValue, "DOUBLE"))
    5746           0 :                     eLonLatType = NC_DOUBLE;
    5747             :             }
    5748             :             else
    5749             :             {
    5750          57 :                 eLonLatType = NC_DOUBLE;
    5751          57 :                 const char *pszValue = aosCreationOptions.FetchNameValueDef(
    5752             :                     "TYPE_LONLAT", "DOUBLE");
    5753          57 :                 if (EQUAL(pszValue, "FLOAT"))
    5754           0 :                     eLonLatType = NC_FLOAT;
    5755             :             }
    5756             : 
    5757             :             // Def vars and attributes.
    5758             :             {
    5759          61 :                 const char *pszVarName =
    5760          61 :                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
    5761          61 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5762             :                                         nLatDims, anLatDims, &nVarLatID);
    5763          61 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5764             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
    5765          61 :                 NCDF_ERR(status);
    5766          61 :                 DefVarDeflate(nVarLatID, false);  // Don't set chunking.
    5767             :             }
    5768             : 
    5769             :             {
    5770          61 :                 const char *pszVarName =
    5771          61 :                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
    5772          61 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5773             :                                         nLonDims, anLonDims, &nVarLonID);
    5774          61 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5775             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
    5776          61 :                 NCDF_ERR(status);
    5777          61 :                 DefVarDeflate(nVarLonID, false);  // Don't set chunking.
    5778             :             }
    5779             : 
    5780          61 :             if (bIsRotatedPole)
    5781           0 :                 NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
    5782             :                                                 nVarLatID);
    5783             :             else
    5784          61 :                 NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
    5785             :         }
    5786             :     }
    5787             : 
    5788         176 :     if (!bDefsOnly)
    5789             :     {
    5790          88 :         m_bAddedProjectionVarsData = true;
    5791             : 
    5792          88 :         int nVarXID = -1;
    5793          88 :         int nVarYID = -1;
    5794             : 
    5795          88 :         nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
    5796          88 :         nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
    5797             : 
    5798          88 :         int nVarLonID = -1;
    5799          88 :         int nVarLatID = -1;
    5800             : 
    5801          88 :         const bool bIsRotatedPole =
    5802         163 :             pszCFProjection != nullptr &&
    5803          75 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5804          88 :         nc_inq_varid(cdfid,
    5805             :                      bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
    5806             :                      &nVarLonID);
    5807          88 :         nc_inq_varid(cdfid,
    5808             :                      bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
    5809             :                      &nVarLatID);
    5810             : 
    5811             :         // Get projection values.
    5812             : 
    5813          88 :         if (bIsProjected)
    5814             :         {
    5815           0 :             std::unique_ptr<OGRSpatialReference> poLatLonSRS;
    5816           0 :             std::unique_ptr<OGRCoordinateTransformation> poTransform;
    5817             : 
    5818             :             size_t startX[1];
    5819             :             size_t countX[1];
    5820             :             size_t startY[1];
    5821             :             size_t countY[1];
    5822             : 
    5823          31 :             CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
    5824             : 
    5825             :             std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
    5826             :                 static_cast<double *>(
    5827          62 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5828          31 :                 VSIFree);
    5829             :             std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
    5830             :                 static_cast<double *>(
    5831          62 :                     VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
    5832          31 :                 VSIFree);
    5833          31 :             double *padXVal = adXValKeeper.get();
    5834          31 :             double *padYVal = adYValKeeper.get();
    5835          31 :             if (!padXVal || !padYVal)
    5836             :             {
    5837           0 :                 return CE_Failure;
    5838             :             }
    5839             : 
    5840             :             // Make sure we are in data mode.
    5841          31 :             SetDefineMode(false);
    5842             : 
    5843          31 :             int status = NC_NOERR;
    5844             : 
    5845          31 :             if (m_gt.IsAxisAligned())
    5846             :             {
    5847             :                 // Get Y values.
    5848          27 :                 const double dfY0 =
    5849          27 :                     (!bBottomUp) ? m_gt.yorig :
    5850             :                                  // Invert latitude values.
    5851          27 :                         m_gt.yorig + (m_gt.yscale * nRasterYSize);
    5852          27 :                 const double dfDY = m_gt.yscale;
    5853             : 
    5854        1478 :                 for (int j = 0; j < nRasterYSize; j++)
    5855             :                 {
    5856             :                     // The data point is centered inside the pixel.
    5857        1451 :                     if (!bBottomUp)
    5858           0 :                         padYVal[j] = dfY0 + (j + 0.5) * dfDY;
    5859             :                     else  // Invert latitude values.
    5860        1451 :                         padYVal[j] = dfY0 - (j + 0.5) * dfDY;
    5861             :                 }
    5862          27 :                 startX[0] = 0;
    5863          27 :                 countX[0] = nRasterXSize;
    5864             : 
    5865             :                 // Get X values.
    5866          27 :                 const double dfX0 = m_gt.xorig;
    5867          27 :                 const double dfDX = m_gt.xscale;
    5868             : 
    5869        1519 :                 for (int i = 0; i < nRasterXSize; i++)
    5870             :                 {
    5871             :                     // The data point is centered inside the pixel.
    5872        1492 :                     padXVal[i] = dfX0 + (i + 0.5) * dfDX;
    5873             :                 }
    5874          27 :                 startY[0] = 0;
    5875          27 :                 countY[0] = nRasterYSize;
    5876             : 
    5877             :                 // Write X/Y values.
    5878             : 
    5879          27 :                 CPLDebug("GDAL_netCDF", "Writing X values");
    5880             :                 status =
    5881          27 :                     nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
    5882          27 :                 NCDF_ERR(status);
    5883             : 
    5884          27 :                 CPLDebug("GDAL_netCDF", "Writing Y values");
    5885             :                 status =
    5886          27 :                     nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
    5887          27 :                 NCDF_ERR(status);
    5888             :             }
    5889             : 
    5890          31 :             if (pfnProgress)
    5891          27 :                 pfnProgress(0.20, nullptr, pProgressData);
    5892             : 
    5893             :             // Write lon/lat arrays (CF coordinates) if requested.
    5894             : 
    5895             :             // Get OGR transform if GEOLOCATION is not available.
    5896          31 :             if (bWriteLonLat && !bHasGeoloc)
    5897             :             {
    5898           3 :                 poLatLonSRS.reset(m_oSRS.CloneGeogCS());
    5899           3 :                 if (poLatLonSRS != nullptr)
    5900             :                 {
    5901           3 :                     poLatLonSRS->SetAxisMappingStrategy(
    5902             :                         OAMS_TRADITIONAL_GIS_ORDER);
    5903           3 :                     poTransform.reset(OGRCreateCoordinateTransformation(
    5904           3 :                         &m_oSRS, poLatLonSRS.get()));
    5905             :                 }
    5906             :                 // If no OGR transform, then don't write CF lon/lat.
    5907           3 :                 if (poTransform == nullptr)
    5908             :                 {
    5909           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5910             :                              "Unable to get Coordinate Transform");
    5911           0 :                     bWriteLonLat = false;
    5912             :                 }
    5913             :             }
    5914             : 
    5915          31 :             if (bWriteLonLat)
    5916             :             {
    5917           4 :                 if (!bHasGeoloc)
    5918           3 :                     CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
    5919             :                 else
    5920           1 :                     CPLDebug("GDAL_netCDF",
    5921             :                              "Writing (lon,lat) from GEOLOCATION arrays");
    5922             : 
    5923           4 :                 bool bOK = true;
    5924           4 :                 double dfProgress = 0.2;
    5925             : 
    5926           4 :                 size_t start[] = {0, 0};
    5927           4 :                 size_t count[] = {1, (size_t)nRasterXSize};
    5928             :                 std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
    5929             :                     static_cast<double *>(
    5930           8 :                         VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5931           4 :                     VSIFree);
    5932             :                 std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    5933             :                     static_cast<double *>(
    5934           8 :                         VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5935           4 :                     VSIFree);
    5936           4 :                 double *padLonVal = adLonValKeeper.get();
    5937           4 :                 double *padLatVal = adLatValKeeper.get();
    5938           4 :                 if (!padLonVal || !padLatVal)
    5939             :                 {
    5940           0 :                     return CE_Failure;
    5941             :                 }
    5942             : 
    5943         103 :                 for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
    5944             :                      j++)
    5945             :                 {
    5946          99 :                     start[0] = j;
    5947             : 
    5948             :                     // Get values from geotransform.
    5949          99 :                     if (!bHasGeoloc)
    5950             :                     {
    5951             :                         // Fill values to transform.
    5952          60 :                         if (m_gt.IsAxisAligned())
    5953             :                         {
    5954         420 :                             for (int i = 0; i < nRasterXSize; i++)
    5955             :                             {
    5956         400 :                                 padLatVal[i] = padYVal[j];
    5957         400 :                                 padLonVal[i] = padXVal[i];
    5958             :                             }
    5959             :                         }
    5960             :                         else
    5961             :                         {
    5962         840 :                             for (int i = 0; i < nRasterXSize; i++)
    5963             :                             {
    5964         800 :                                 if (!bBottomUp)
    5965             :                                 {
    5966         400 :                                     padLatVal[i] = m_gt.yorig +
    5967         400 :                                                    (i + 0.5) * m_gt.yrot +
    5968         400 :                                                    (j + 0.5) * m_gt.yscale;
    5969         400 :                                     padLonVal[i] = m_gt.xorig +
    5970         400 :                                                    (i + 0.5) * m_gt.xscale +
    5971         400 :                                                    (j + 0.5) * m_gt.xrot;
    5972             :                                 }
    5973             :                                 else
    5974             :                                 {
    5975         400 :                                     padLatVal[i] =
    5976         400 :                                         m_gt.yorig + (i + 0.5) * m_gt.yrot +
    5977         400 :                                         (nRasterYSize - j - 0.5) * m_gt.yscale;
    5978         400 :                                     padLonVal[i] =
    5979         400 :                                         m_gt.xorig + (i + 0.5) * m_gt.xscale +
    5980         400 :                                         (nRasterYSize - j - 0.5) * m_gt.xrot;
    5981             :                                 }
    5982             :                             }
    5983             :                         }
    5984             : 
    5985             :                         // Do the transform.
    5986         120 :                         bOK = CPL_TO_BOOL(poTransform->Transform(
    5987          60 :                             nRasterXSize, padLonVal, padLatVal, nullptr));
    5988          60 :                         if (!bOK)
    5989             :                         {
    5990           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5991             :                                      "Unable to Transform (X,Y) to (lon,lat).");
    5992             :                         }
    5993             :                     }
    5994             :                     // Get values from geoloc arrays.
    5995             :                     else
    5996             :                     {
    5997          39 :                         CPLErr eErr = poBand_Y->RasterIO(
    5998             :                             GF_Read, 0, j, nRasterXSize, 1, padLatVal,
    5999             :                             nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
    6000          39 :                         if (eErr == CE_None)
    6001             :                         {
    6002          39 :                             eErr = poBand_X->RasterIO(
    6003             :                                 GF_Read, 0, j, nRasterXSize, 1, padLonVal,
    6004             :                                 nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
    6005             :                         }
    6006             : 
    6007          39 :                         if (eErr == CE_None)
    6008             :                         {
    6009          39 :                             bOK = true;
    6010             :                         }
    6011             :                         else
    6012             :                         {
    6013           0 :                             bOK = false;
    6014           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    6015             :                                      "Unable to get scanline %d", j);
    6016             :                         }
    6017             :                     }
    6018             : 
    6019             :                     // Write data.
    6020          99 :                     if (bOK)
    6021             :                     {
    6022          99 :                         status = nc_put_vara_double(cdfid, nVarLatID, start,
    6023             :                                                     count, padLatVal);
    6024          99 :                         NCDF_ERR(status);
    6025          99 :                         status = nc_put_vara_double(cdfid, nVarLonID, start,
    6026             :                                                     count, padLonVal);
    6027          99 :                         NCDF_ERR(status);
    6028             :                     }
    6029             : 
    6030          99 :                     if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6031          99 :                         (j % (nRasterYSize / 10) == 0))
    6032             :                     {
    6033          43 :                         dfProgress += 0.08;
    6034          43 :                         pfnProgress(dfProgress, nullptr, pProgressData);
    6035             :                     }
    6036             :                 }
    6037             :             }
    6038             :         }  // Projected
    6039             : 
    6040             :         // If not projected/geographic and has geoloc
    6041          57 :         else if (!bIsGeographic && bHasGeoloc && m_gt.IsAxisAligned())
    6042             :         {
    6043             :             // Use
    6044             :             // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
    6045             : 
    6046           4 :             bool bOK = true;
    6047           4 :             double dfProgress = 0.2;
    6048             : 
    6049             :             // Make sure we are in data mode.
    6050           4 :             SetDefineMode(false);
    6051             : 
    6052             :             size_t startX[1];
    6053             :             size_t countX[1];
    6054             :             size_t startY[1];
    6055             :             size_t countY[1];
    6056           4 :             startX[0] = 0;
    6057           4 :             countX[0] = nRasterXSize;
    6058             : 
    6059           4 :             startY[0] = 0;
    6060           4 :             countY[0] = nRasterYSize;
    6061             : 
    6062           4 :             std::vector<double> adfXVal;
    6063           4 :             std::vector<double> adfYVal;
    6064             :             try
    6065             :             {
    6066           4 :                 adfXVal.resize(nRasterXSize);
    6067           4 :                 adfYVal.resize(nRasterYSize);
    6068             :             }
    6069           0 :             catch (const std::exception &)
    6070             :             {
    6071           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
    6072             :                          "Out of memory allocating temporary array");
    6073           0 :                 return CE_Failure;
    6074             :             }
    6075          16 :             for (int i = 0; i < nRasterXSize; i++)
    6076          12 :                 adfXVal[i] = i;
    6077          12 :             for (int i = 0; i < nRasterYSize; i++)
    6078           8 :                 adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
    6079             : 
    6080           4 :             CPLDebug("GDAL_netCDF", "Writing X values");
    6081           4 :             int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
    6082           4 :                                             adfXVal.data());
    6083           4 :             NCDF_ERR(status);
    6084             : 
    6085           4 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    6086           4 :             status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
    6087           4 :                                         adfYVal.data());
    6088           4 :             NCDF_ERR(status);
    6089             : 
    6090           4 :             if (pfnProgress)
    6091           0 :                 pfnProgress(0.20, nullptr, pProgressData);
    6092             : 
    6093           4 :             size_t start[] = {0, 0};
    6094           4 :             size_t count[] = {1, (size_t)nRasterXSize};
    6095             : 
    6096             :             std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
    6097             :                 static_cast<double *>(
    6098           8 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6099           4 :                 VSIFree);
    6100             :             std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    6101             :                 static_cast<double *>(
    6102           8 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6103           4 :                 VSIFree);
    6104           4 :             double *padLonVal = adLonValKeeper.get();
    6105           4 :             double *padLatVal = adLatValKeeper.get();
    6106           4 :             if (!padLonVal || !padLatVal)
    6107             :             {
    6108           0 :                 return CE_Failure;
    6109             :             }
    6110             : 
    6111          12 :             for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
    6112             :             {
    6113           8 :                 start[0] = j;
    6114             : 
    6115           8 :                 CPLErr eErr = poBand_Y->RasterIO(
    6116           8 :                     GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
    6117             :                     nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
    6118             :                     0, nullptr);
    6119           8 :                 if (eErr == CE_None)
    6120             :                 {
    6121           8 :                     eErr = poBand_X->RasterIO(
    6122           8 :                         GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
    6123             :                         nRasterXSize, 1, padLonVal, nRasterXSize, 1,
    6124             :                         GDT_Float64, 0, 0, nullptr);
    6125             :                 }
    6126             : 
    6127           8 :                 if (eErr == CE_None)
    6128             :                 {
    6129           8 :                     bOK = true;
    6130             :                 }
    6131             :                 else
    6132             :                 {
    6133           0 :                     bOK = false;
    6134           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    6135             :                              "Unable to get scanline %d", j);
    6136             :                 }
    6137             : 
    6138             :                 // Write data.
    6139           8 :                 if (bOK)
    6140             :                 {
    6141           8 :                     status = nc_put_vara_double(cdfid, nVarLatID, start, count,
    6142             :                                                 padLatVal);
    6143           8 :                     NCDF_ERR(status);
    6144           8 :                     status = nc_put_vara_double(cdfid, nVarLonID, start, count,
    6145             :                                                 padLonVal);
    6146           8 :                     NCDF_ERR(status);
    6147             :                 }
    6148             : 
    6149           8 :                 if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6150           0 :                     (j % (nRasterYSize / 10) == 0))
    6151             :                 {
    6152           0 :                     dfProgress += 0.08;
    6153           0 :                     pfnProgress(dfProgress, nullptr, pProgressData);
    6154             :                 }
    6155             :             }
    6156             :         }
    6157             : 
    6158             :         // If not projected, assume geographic to catch grids without Datum.
    6159          53 :         else if (bWriteLonLat)
    6160             :         {
    6161             :             // Get latitude values.
    6162          53 :             const double dfY0 = (!bBottomUp) ? m_gt.yorig :
    6163             :                                              // Invert latitude values.
    6164          53 :                                     m_gt.yorig + (m_gt.yscale * nRasterYSize);
    6165          53 :             const double dfDY = m_gt.yscale;
    6166             : 
    6167             :             std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
    6168          53 :                                                                        VSIFree);
    6169          53 :             double *padLatVal = nullptr;
    6170             :             // Override lat values with the ones in GEOLOCATION/Y_VALUES.
    6171          53 :             if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
    6172             :                 nullptr)
    6173             :             {
    6174           0 :                 int nTemp = 0;
    6175           0 :                 adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
    6176           0 :                 padLatVal = adLatValKeeper.get();
    6177             :                 // Make sure we got the correct amount, if not fallback to GT */
    6178             :                 // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
    6179           0 :                 if (nTemp == nRasterYSize)
    6180             :                 {
    6181           0 :                     CPLDebug(
    6182             :                         "GDAL_netCDF",
    6183             :                         "Using Y_VALUES geolocation metadata for lat values");
    6184             :                 }
    6185             :                 else
    6186             :                 {
    6187           0 :                     CPLDebug("GDAL_netCDF",
    6188             :                              "Got %d elements from Y_VALUES geolocation "
    6189             :                              "metadata, need %d",
    6190             :                              nTemp, nRasterYSize);
    6191           0 :                     padLatVal = nullptr;
    6192             :                 }
    6193             :             }
    6194             : 
    6195          53 :             if (padLatVal == nullptr)
    6196             :             {
    6197          53 :                 adLatValKeeper.reset(static_cast<double *>(
    6198          53 :                     VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
    6199          53 :                 padLatVal = adLatValKeeper.get();
    6200          53 :                 if (!padLatVal)
    6201             :                 {
    6202           0 :                     return CE_Failure;
    6203             :                 }
    6204        7105 :                 for (int i = 0; i < nRasterYSize; i++)
    6205             :                 {
    6206             :                     // The data point is centered inside the pixel.
    6207        7052 :                     if (!bBottomUp)
    6208           0 :                         padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
    6209             :                     else  // Invert latitude values.
    6210        7052 :                         padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
    6211             :                 }
    6212             :             }
    6213             : 
    6214          53 :             size_t startLat[1] = {0};
    6215          53 :             size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
    6216             : 
    6217             :             // Get longitude values.
    6218          53 :             const double dfX0 = m_gt.xorig;
    6219          53 :             const double dfDX = m_gt.xscale;
    6220             : 
    6221             :             std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    6222             :                 static_cast<double *>(
    6223         106 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6224          53 :                 VSIFree);
    6225          53 :             double *padLonVal = adLonValKeeper.get();
    6226          53 :             if (!padLonVal)
    6227             :             {
    6228           0 :                 return CE_Failure;
    6229             :             }
    6230        7157 :             for (int i = 0; i < nRasterXSize; i++)
    6231             :             {
    6232             :                 // The data point is centered inside the pixel.
    6233        7104 :                 padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
    6234             :             }
    6235             : 
    6236          53 :             size_t startLon[1] = {0};
    6237          53 :             size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
    6238             : 
    6239             :             // Write latitude and longitude values.
    6240             : 
    6241             :             // Make sure we are in data mode.
    6242          53 :             SetDefineMode(false);
    6243             : 
    6244             :             // Write values.
    6245          53 :             CPLDebug("GDAL_netCDF", "Writing lat values");
    6246             : 
    6247          53 :             int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
    6248             :                                             countLat, padLatVal);
    6249          53 :             NCDF_ERR(status);
    6250             : 
    6251          53 :             CPLDebug("GDAL_netCDF", "Writing lon values");
    6252          53 :             status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
    6253             :                                         padLonVal);
    6254          53 :             NCDF_ERR(status);
    6255             : 
    6256             :         }  // Not projected.
    6257             : 
    6258          88 :         if (pfnProgress)
    6259          47 :             pfnProgress(1.00, nullptr, pProgressData);
    6260             :     }
    6261             : 
    6262         176 :     return CE_None;
    6263             : }
    6264             : 
    6265             : // Write Projection variable to band variable.
    6266             : // Moved from AddProjectionVars() for cases when bands are added after
    6267             : // projection.
    6268         447 : bool netCDFDataset::AddGridMappingRef()
    6269             : {
    6270         447 :     bool bRet = true;
    6271         447 :     bool bOldDefineMode = bDefineMode;
    6272             : 
    6273         650 :     if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
    6274         203 :         ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
    6275         195 :          (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
    6276             :     {
    6277          79 :         bAddedGridMappingRef = true;
    6278             : 
    6279             :         // Make sure we are in define mode.
    6280          79 :         SetDefineMode(true);
    6281             : 
    6282         204 :         for (int i = 1; i <= nBands; i++)
    6283             :         {
    6284             :             const int nVarId =
    6285         125 :                 cpl::down_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
    6286             : 
    6287         125 :             if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
    6288             :             {
    6289             :                 int status =
    6290         242 :                     nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
    6291         121 :                                     strlen(pszCFProjection), pszCFProjection);
    6292         121 :                 NCDF_ERR(status);
    6293         121 :                 if (status != NC_NOERR)
    6294           0 :                     bRet = false;
    6295             :             }
    6296         125 :             if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
    6297             :             {
    6298             :                 int status =
    6299           8 :                     nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
    6300             :                                     strlen(pszCFCoordinates), pszCFCoordinates);
    6301           8 :                 NCDF_ERR(status);
    6302           8 :                 if (status != NC_NOERR)
    6303           0 :                     bRet = false;
    6304             :             }
    6305             :         }
    6306             : 
    6307             :         // Go back to previous define mode.
    6308          79 :         SetDefineMode(bOldDefineMode);
    6309             :     }
    6310         447 :     return bRet;
    6311             : }
    6312             : 
    6313             : /************************************************************************/
    6314             : /*                          GetGeoTransform()                           */
    6315             : /************************************************************************/
    6316             : 
    6317         129 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform &gt) const
    6318             : 
    6319             : {
    6320         129 :     gt = m_gt;
    6321         129 :     if (m_bHasGeoTransform)
    6322          97 :         return CE_None;
    6323             : 
    6324          32 :     return GDALPamDataset::GetGeoTransform(gt);
    6325             : }
    6326             : 
    6327             : /************************************************************************/
    6328             : /*                                rint()                                */
    6329             : /************************************************************************/
    6330             : 
    6331           0 : double netCDFDataset::rint(double dfX)
    6332             : {
    6333           0 :     return std::round(dfX);
    6334             : }
    6335             : 
    6336             : /************************************************************************/
    6337             : /*                        NCDFReadIsoMetadata()                         */
    6338             : /************************************************************************/
    6339             : 
    6340          16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
    6341             : {
    6342          16 :     int nbAttr = 0;
    6343          16 :     NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
    6344             : 
    6345          32 :     std::map<std::string, CPLJSONArray> oMapNameToArray;
    6346          40 :     for (int l = 0; l < nbAttr; l++)
    6347             :     {
    6348             :         char szAttrName[NC_MAX_NAME + 1];
    6349          24 :         szAttrName[0] = 0;
    6350          24 :         NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
    6351             : 
    6352          24 :         char *pszMetaValue = nullptr;
    6353          24 :         if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
    6354             :         {
    6355          24 :             nc_type nAttrType = NC_NAT;
    6356          24 :             size_t nAttrLen = 0;
    6357             : 
    6358          24 :             NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
    6359             :                                 &nAttrLen));
    6360             : 
    6361          24 :             std::string osAttrName(szAttrName);
    6362          24 :             const auto sharpPos = osAttrName.find('#');
    6363          24 :             if (sharpPos == std::string::npos)
    6364             :             {
    6365          16 :                 if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
    6366           4 :                     obj.Add(osAttrName, CPLAtof(pszMetaValue));
    6367             :                 else
    6368          12 :                     obj.Add(osAttrName, pszMetaValue);
    6369             :             }
    6370             :             else
    6371             :             {
    6372           8 :                 osAttrName.resize(sharpPos);
    6373           8 :                 auto iter = oMapNameToArray.find(osAttrName);
    6374           8 :                 if (iter == oMapNameToArray.end())
    6375             :                 {
    6376           8 :                     CPLJSONArray array;
    6377           4 :                     obj.Add(osAttrName, array);
    6378           4 :                     oMapNameToArray[osAttrName] = array;
    6379           4 :                     array.Add(pszMetaValue);
    6380             :                 }
    6381             :                 else
    6382             :                 {
    6383           4 :                     iter->second.Add(pszMetaValue);
    6384             :                 }
    6385             :             }
    6386          24 :             CPLFree(pszMetaValue);
    6387          24 :             pszMetaValue = nullptr;
    6388             :         }
    6389             :     }
    6390             : 
    6391          16 :     int nSubGroups = 0;
    6392          16 :     int *panSubGroupIds = nullptr;
    6393          16 :     NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
    6394          16 :     oMapNameToArray.clear();
    6395          28 :     for (int i = 0; i < nSubGroups; i++)
    6396             :     {
    6397          24 :         CPLJSONObject subObj;
    6398          12 :         NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
    6399             : 
    6400          24 :         std::string osGroupName;
    6401          12 :         osGroupName.resize(NC_MAX_NAME);
    6402          12 :         NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
    6403          12 :         osGroupName.resize(strlen(osGroupName.data()));
    6404          12 :         const auto sharpPos = osGroupName.find('#');
    6405          12 :         if (sharpPos == std::string::npos)
    6406             :         {
    6407           4 :             obj.Add(osGroupName, subObj);
    6408             :         }
    6409             :         else
    6410             :         {
    6411           8 :             osGroupName.resize(sharpPos);
    6412           8 :             auto iter = oMapNameToArray.find(osGroupName);
    6413           8 :             if (iter == oMapNameToArray.end())
    6414             :             {
    6415           8 :                 CPLJSONArray array;
    6416           4 :                 obj.Add(osGroupName, array);
    6417           4 :                 oMapNameToArray[osGroupName] = array;
    6418           4 :                 array.Add(subObj);
    6419             :             }
    6420             :             else
    6421             :             {
    6422           4 :                 iter->second.Add(subObj);
    6423             :             }
    6424             :         }
    6425             :     }
    6426          16 :     CPLFree(panSubGroupIds);
    6427          16 : }
    6428             : 
    6429           4 : std::string NCDFReadMetadataAsJson(int cdfid)
    6430             : {
    6431           8 :     CPLJSONDocument oDoc;
    6432           8 :     CPLJSONObject oRoot = oDoc.GetRoot();
    6433           4 :     NCDFReadMetadataAsJson(cdfid, oRoot);
    6434           8 :     return oDoc.SaveAsString();
    6435             : }
    6436             : 
    6437             : /************************************************************************/
    6438             : /*                           ReadAttributes()                           */
    6439             : /************************************************************************/
    6440        1900 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
    6441             : 
    6442             : {
    6443        3800 :     std::string osVarFullName;
    6444        1900 :     ERR_RET(NCDFGetVarFullName(cdfidIn, var, osVarFullName));
    6445             : 
    6446             :     // For metadata in Sentinel 5
    6447        1900 :     if (cpl::starts_with(osVarFullName, "/METADATA/"))
    6448             :     {
    6449           6 :         for (const char *key :
    6450             :              {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
    6451           8 :               "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
    6452             :         {
    6453          14 :             if (var == NC_GLOBAL &&
    6454          14 :                 osVarFullName == CPLOPrintf("/METADATA/%s/NC_GLOBAL", key))
    6455             :             {
    6456           1 :                 CPLStringList aosList;
    6457           2 :                 aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
    6458           1 :                                       .replaceAll("\\/", '/'));
    6459           1 :                 m_oMapDomainToJSon[key] = std::move(aosList);
    6460           1 :                 return CE_None;
    6461             :             }
    6462             :         }
    6463             :     }
    6464        1899 :     if (cpl::starts_with(osVarFullName, "/PRODUCT/SUPPORT_DATA/"))
    6465             :     {
    6466           0 :         CPLStringList aosList;
    6467             :         aosList.AddString(
    6468           0 :             CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
    6469           0 :         m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
    6470           0 :         return CE_None;
    6471             :     }
    6472             : 
    6473             :     size_t nMetaNameSize =
    6474        1899 :         sizeof(char) * (osVarFullName.size() + 1 + NC_MAX_NAME + 1);
    6475        1899 :     char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
    6476             : 
    6477        1899 :     int nbAttr = 0;
    6478        1899 :     NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
    6479             : 
    6480        9620 :     for (int l = 0; l < nbAttr; l++)
    6481             :     {
    6482             :         char szAttrName[NC_MAX_NAME + 1];
    6483        7721 :         szAttrName[0] = 0;
    6484        7721 :         NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
    6485        7721 :         snprintf(pszMetaName, nMetaNameSize, "%s#%s", osVarFullName.c_str(),
    6486             :                  szAttrName);
    6487             : 
    6488        7721 :         char *pszMetaTemp = nullptr;
    6489        7721 :         if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
    6490             :         {
    6491        7720 :             aosMetadata.SetNameValue(pszMetaName, pszMetaTemp);
    6492        7720 :             CPLFree(pszMetaTemp);
    6493        7720 :             pszMetaTemp = nullptr;
    6494             :         }
    6495             :         else
    6496             :         {
    6497           1 :             CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
    6498             :         }
    6499             :     }
    6500             : 
    6501        1899 :     CPLFree(pszMetaName);
    6502             : 
    6503        1899 :     if (var == NC_GLOBAL)
    6504             :     {
    6505             :         // Recurse on sub-groups.
    6506         547 :         int nSubGroups = 0;
    6507         547 :         int *panSubGroupIds = nullptr;
    6508         547 :         NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
    6509         580 :         for (int i = 0; i < nSubGroups; i++)
    6510             :         {
    6511          33 :             ReadAttributes(panSubGroupIds[i], var);
    6512             :         }
    6513         547 :         CPLFree(panSubGroupIds);
    6514             :     }
    6515             : 
    6516        1899 :     return CE_None;
    6517             : }
    6518             : 
    6519             : /************************************************************************/
    6520             : /*                netCDFDataset::CreateSubDatasetList()                 */
    6521             : /************************************************************************/
    6522          61 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
    6523             : {
    6524         122 :     std::string osVarStdName;
    6525          61 :     int *ponDimIds = nullptr;
    6526             : 
    6527          61 :     netCDFDataset *poDS = this;
    6528             : 
    6529             :     int nVarCount;
    6530          61 :     nc_inq_nvars(nGroupId, &nVarCount);
    6531             : 
    6532          61 :     const bool bListAllArrays = CPLTestBool(
    6533          61 :         CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    6534             : 
    6535         366 :     for (int nVar = 0; nVar < nVarCount; nVar++)
    6536             :     {
    6537             : 
    6538             :         int nDims;
    6539         305 :         nc_inq_varndims(nGroupId, nVar, &nDims);
    6540             : 
    6541         305 :         if ((bListAllArrays && nDims > 0) || nDims >= 2)
    6542             :         {
    6543         177 :             ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
    6544         177 :             nc_inq_vardimid(nGroupId, nVar, ponDimIds);
    6545             : 
    6546             :             // Create Sub dataset list.
    6547         177 :             CPLString osDim;
    6548         545 :             for (int i = 0; i < nDims; i++)
    6549             :             {
    6550             :                 size_t nDimLen;
    6551         368 :                 nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
    6552         368 :                 if (!osDim.empty())
    6553         191 :                     osDim += 'x';
    6554         368 :                 osDim += CPLSPrintf("%d", (int)nDimLen);
    6555             :             }
    6556         177 :             CPLFree(ponDimIds);
    6557             : 
    6558             :             nc_type nVarType;
    6559         177 :             nc_inq_vartype(nGroupId, nVar, &nVarType);
    6560         177 :             const char *pszType = "";
    6561         177 :             switch (nVarType)
    6562             :             {
    6563          42 :                 case NC_BYTE:
    6564          42 :                     pszType = "8-bit integer";
    6565          42 :                     break;
    6566           2 :                 case NC_CHAR:
    6567           2 :                     pszType = "8-bit character";
    6568           2 :                     break;
    6569           6 :                 case NC_SHORT:
    6570           6 :                     pszType = "16-bit integer";
    6571           6 :                     break;
    6572          10 :                 case NC_INT:
    6573          10 :                     pszType = "32-bit integer";
    6574          10 :                     break;
    6575          62 :                 case NC_FLOAT:
    6576          62 :                     pszType = "32-bit floating-point";
    6577          62 :                     break;
    6578          34 :                 case NC_DOUBLE:
    6579          34 :                     pszType = "64-bit floating-point";
    6580          34 :                     break;
    6581           4 :                 case NC_UBYTE:
    6582           4 :                     pszType = "8-bit unsigned integer";
    6583           4 :                     break;
    6584           1 :                 case NC_USHORT:
    6585           1 :                     pszType = "16-bit unsigned integer";
    6586           1 :                     break;
    6587           1 :                 case NC_UINT:
    6588           1 :                     pszType = "32-bit unsigned integer";
    6589           1 :                     break;
    6590           1 :                 case NC_INT64:
    6591           1 :                     pszType = "64-bit integer";
    6592           1 :                     break;
    6593           1 :                 case NC_UINT64:
    6594           1 :                     pszType = "64-bit unsigned integer";
    6595           1 :                     break;
    6596          13 :                 default:
    6597          13 :                     break;
    6598             :             }
    6599             : 
    6600         177 :             std::string osVarName;
    6601         177 :             if (NCDFGetVarFullName(nGroupId, nVar, osVarName) != CE_None)
    6602           0 :                 continue;
    6603             : 
    6604         177 :             nSubDatasets++;
    6605             : 
    6606         177 :             if (NCDFGetAttr(nGroupId, nVar, CF_STD_NAME, osVarStdName) !=
    6607             :                 CE_None)
    6608             :             {
    6609         113 :                 osVarStdName = osVarName;
    6610             :             }
    6611             : 
    6612             :             const std::string osSubDatasetName =
    6613         354 :                 CPLOPrintf("SUBDATASET_%d_NAME", nSubDatasets);
    6614             : 
    6615         354 :             if (osVarName.find(' ') != std::string::npos ||
    6616         177 :                 osVarName.find(':') != std::string::npos)
    6617             :             {
    6618             :                 poDS->aosSubDatasets.SetNameValue(
    6619             :                     osSubDatasetName.c_str(),
    6620             :                     CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
    6621           1 :                                osVarName.c_str()));
    6622             :             }
    6623             :             else
    6624             :             {
    6625             :                 poDS->aosSubDatasets.SetNameValue(
    6626             :                     osSubDatasetName.c_str(),
    6627             :                     CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
    6628         176 :                                osVarName.c_str()));
    6629             :             }
    6630             : 
    6631             :             const std::string osSubDatasetDesc =
    6632         354 :                 CPLOPrintf("SUBDATASET_%d_DESC", nSubDatasets);
    6633             : 
    6634             :             poDS->aosSubDatasets.SetNameValue(
    6635             :                 osSubDatasetDesc.c_str(),
    6636             :                 CPLSPrintf("[%s] %s (%s)", osDim.c_str(), osVarStdName.c_str(),
    6637         177 :                            pszType));
    6638             :         }
    6639             :     }
    6640             : 
    6641             :     // Recurse on sub groups.
    6642          61 :     int nSubGroups = 0;
    6643          61 :     int *panSubGroupIds = nullptr;
    6644          61 :     NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
    6645          69 :     for (int i = 0; i < nSubGroups; i++)
    6646             :     {
    6647           8 :         CreateSubDatasetList(panSubGroupIds[i]);
    6648             :     }
    6649          61 :     CPLFree(panSubGroupIds);
    6650          61 : }
    6651             : 
    6652             : /************************************************************************/
    6653             : /*                           TestCapability()                           */
    6654             : /************************************************************************/
    6655             : 
    6656         249 : int netCDFDataset::TestCapability(const char *pszCap) const
    6657             : {
    6658         249 :     if (EQUAL(pszCap, ODsCCreateLayer))
    6659             :     {
    6660         225 :         return eAccess == GA_Update && nBands == 0 &&
    6661         219 :                (eMultipleLayerBehavior != SINGLE_LAYER ||
    6662         230 :                 this->GetLayerCount() == 0 || bSGSupport);
    6663             :     }
    6664         136 :     else if (EQUAL(pszCap, ODsCZGeometries))
    6665           2 :         return true;
    6666             : 
    6667         134 :     return false;
    6668             : }
    6669             : 
    6670             : /************************************************************************/
    6671             : /*                              GetLayer()                              */
    6672             : /************************************************************************/
    6673             : 
    6674         395 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
    6675             : {
    6676         395 :     if (nIdx < 0 || nIdx >= this->GetLayerCount())
    6677           2 :         return nullptr;
    6678         393 :     return papoLayers[nIdx].get();
    6679             : }
    6680             : 
    6681             : /************************************************************************/
    6682             : /*                            ICreateLayer()                            */
    6683             : /************************************************************************/
    6684             : 
    6685          60 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
    6686             :                                       const OGRGeomFieldDefn *poGeomFieldDefn,
    6687             :                                       CSLConstList papszOptions)
    6688             : {
    6689          60 :     int nLayerCDFId = cdfid;
    6690          60 :     if (!TestCapability(ODsCCreateLayer))
    6691           0 :         return nullptr;
    6692             : 
    6693          60 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    6694             :     const auto poSpatialRef =
    6695          60 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6696             : 
    6697         120 :     CPLString osNetCDFLayerName(pszName);
    6698          60 :     const netCDFWriterConfigLayer *poLayerConfig = nullptr;
    6699          60 :     if (oWriterConfig.m_bIsValid)
    6700             :     {
    6701             :         std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
    6702           2 :             oLayerIter = oWriterConfig.m_oLayers.find(pszName);
    6703           2 :         if (oLayerIter != oWriterConfig.m_oLayers.end())
    6704             :         {
    6705           1 :             poLayerConfig = &(oLayerIter->second);
    6706           1 :             osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
    6707             :         }
    6708             :     }
    6709             : 
    6710          60 :     netCDFDataset *poLayerDataset = nullptr;
    6711          60 :     if (eMultipleLayerBehavior == SEPARATE_FILES)
    6712             :     {
    6713           3 :         if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
    6714             :             osNetCDFLayerName)
    6715             :         {
    6716           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6717             :                      "Illegal characters in '%s' to form a valid filename",
    6718             :                      osNetCDFLayerName.c_str());
    6719           1 :             return nullptr;
    6720             :         }
    6721           2 :         CPLStringList aosDatasetOptions;
    6722             :         aosDatasetOptions.SetNameValue(
    6723           2 :             "CONFIG_FILE", aosCreationOptions.FetchNameValue("CONFIG_FILE"));
    6724             :         aosDatasetOptions.SetNameValue(
    6725           2 :             "FORMAT", aosCreationOptions.FetchNameValue("FORMAT"));
    6726             :         aosDatasetOptions.SetNameValue(
    6727             :             "WRITE_GDAL_TAGS",
    6728           2 :             aosCreationOptions.FetchNameValue("WRITE_GDAL_TAGS"));
    6729             :         const CPLString osLayerFilename(
    6730           2 :             CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
    6731           2 :         CPLAcquireMutex(hNCMutex, 1000.0);
    6732           2 :         poLayerDataset =
    6733           2 :             CreateLL(osLayerFilename, 0, 0, 0, aosDatasetOptions.List());
    6734           2 :         CPLReleaseMutex(hNCMutex);
    6735           2 :         if (poLayerDataset == nullptr)
    6736           0 :             return nullptr;
    6737             : 
    6738           2 :         nLayerCDFId = poLayerDataset->cdfid;
    6739           2 :         NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
    6740           2 :                            bWriteGDALHistory, "", "Create",
    6741             :                            NCDF_CONVENTIONS_CF_V1_6);
    6742             :     }
    6743          57 :     else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
    6744             :     {
    6745           2 :         SetDefineMode(true);
    6746             : 
    6747           2 :         nLayerCDFId = -1;
    6748           2 :         int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
    6749           2 :         NCDF_ERR(status);
    6750           2 :         if (status != NC_NOERR)
    6751           0 :             return nullptr;
    6752             : 
    6753           2 :         NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
    6754           2 :                            bWriteGDALHistory, "", "Create",
    6755             :                            NCDF_CONVENTIONS_CF_V1_6);
    6756             :     }
    6757             : 
    6758             :     // Make a clone to workaround a bug in released MapServer versions
    6759             :     // that destroys the passed SRS instead of releasing it .
    6760          59 :     OGRSpatialReference *poSRS = nullptr;
    6761          59 :     if (poSpatialRef)
    6762             :     {
    6763          43 :         poSRS = poSpatialRef->Clone();
    6764          43 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6765             :     }
    6766             :     std::shared_ptr<netCDFLayer> poLayer(
    6767          59 :         new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
    6768         118 :                         osNetCDFLayerName, eGType, poSRS));
    6769          59 :     if (poSRS != nullptr)
    6770          43 :         poSRS->Release();
    6771             : 
    6772             :     // Fetch layer creation options coming from config file
    6773         118 :     CPLStringList aosNewOptions(CSLDuplicate(papszOptions));
    6774          59 :     if (oWriterConfig.m_bIsValid)
    6775             :     {
    6776           2 :         for (const auto &[osName, osValue] :
    6777           4 :              oWriterConfig.m_oLayerCreationOptions)
    6778             :         {
    6779           1 :             aosNewOptions.SetNameValue(osName, osValue);
    6780             :         }
    6781           2 :         if (poLayerConfig != nullptr)
    6782             :         {
    6783           4 :             for (const auto &[osName, osValue] :
    6784           5 :                  poLayerConfig->m_oLayerCreationOptions)
    6785             :             {
    6786           2 :                 aosNewOptions.SetNameValue(osName, osValue);
    6787             :             }
    6788             :         }
    6789             :     }
    6790             : 
    6791          59 :     const bool bRet = poLayer->Create(aosNewOptions.List(), poLayerConfig);
    6792             : 
    6793          59 :     if (!bRet)
    6794             :     {
    6795           0 :         return nullptr;
    6796             :     }
    6797             : 
    6798          59 :     if (poLayerDataset != nullptr)
    6799           2 :         apoVectorDatasets.push_back(poLayerDataset);
    6800             : 
    6801          59 :     papoLayers.push_back(poLayer);
    6802          59 :     return poLayer.get();
    6803             : }
    6804             : 
    6805             : /************************************************************************/
    6806             : /*                          CloneAttributes()                           */
    6807             : /************************************************************************/
    6808             : 
    6809         137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
    6810             :                                     int nDstVarId)
    6811             : {
    6812         137 :     int nAttCount = -1;
    6813         137 :     int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
    6814         137 :     NCDF_ERR(status);
    6815             : 
    6816         693 :     for (int i = 0; i < nAttCount; i++)
    6817             :     {
    6818             :         char szName[NC_MAX_NAME + 1];
    6819         556 :         szName[0] = 0;
    6820         556 :         status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
    6821         556 :         NCDF_ERR(status);
    6822             : 
    6823             :         status =
    6824         556 :             nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
    6825         556 :         NCDF_ERR(status);
    6826         556 :         if (status != NC_NOERR)
    6827           0 :             return false;
    6828             :     }
    6829             : 
    6830         137 :     return true;
    6831             : }
    6832             : 
    6833             : /************************************************************************/
    6834             : /*                        CloneVariableContent()                        */
    6835             : /************************************************************************/
    6836             : 
    6837         121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
    6838             :                                          int nSrcVarId, int nDstVarId)
    6839             : {
    6840         121 :     int nVarDimCount = -1;
    6841         121 :     int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
    6842         121 :     NCDF_ERR(status);
    6843         121 :     int anDimIds[] = {-1, 1};
    6844         121 :     status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
    6845         121 :     NCDF_ERR(status);
    6846         121 :     nc_type nc_datatype = NC_NAT;
    6847         121 :     status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
    6848         121 :     NCDF_ERR(status);
    6849         121 :     size_t nTypeSize = 0;
    6850         121 :     switch (nc_datatype)
    6851             :     {
    6852          35 :         case NC_BYTE:
    6853             :         case NC_CHAR:
    6854          35 :             nTypeSize = 1;
    6855          35 :             break;
    6856           4 :         case NC_SHORT:
    6857           4 :             nTypeSize = 2;
    6858           4 :             break;
    6859          24 :         case NC_INT:
    6860          24 :             nTypeSize = 4;
    6861          24 :             break;
    6862           4 :         case NC_FLOAT:
    6863           4 :             nTypeSize = 4;
    6864           4 :             break;
    6865          43 :         case NC_DOUBLE:
    6866          43 :             nTypeSize = 8;
    6867          43 :             break;
    6868           2 :         case NC_UBYTE:
    6869           2 :             nTypeSize = 1;
    6870           2 :             break;
    6871           2 :         case NC_USHORT:
    6872           2 :             nTypeSize = 2;
    6873           2 :             break;
    6874           2 :         case NC_UINT:
    6875           2 :             nTypeSize = 4;
    6876           2 :             break;
    6877           4 :         case NC_INT64:
    6878             :         case NC_UINT64:
    6879           4 :             nTypeSize = 8;
    6880           4 :             break;
    6881           1 :         case NC_STRING:
    6882           1 :             nTypeSize = sizeof(char *);
    6883           1 :             break;
    6884           0 :         default:
    6885             :         {
    6886           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
    6887             :                      nc_datatype);
    6888           0 :             return false;
    6889             :         }
    6890             :     }
    6891             : 
    6892         121 :     size_t nElems = 1;
    6893             :     size_t anStart[NC_MAX_DIMS];
    6894             :     size_t anCount[NC_MAX_DIMS];
    6895         121 :     size_t nRecords = 1;
    6896         261 :     for (int i = 0; i < nVarDimCount; i++)
    6897             :     {
    6898         140 :         anStart[i] = 0;
    6899         140 :         if (i == 0)
    6900             :         {
    6901         116 :             anCount[i] = 1;
    6902         116 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
    6903         116 :             NCDF_ERR(status);
    6904             :         }
    6905             :         else
    6906             :         {
    6907          24 :             anCount[i] = 0;
    6908          24 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
    6909          24 :             NCDF_ERR(status);
    6910          24 :             nElems *= anCount[i];
    6911             :         }
    6912             :     }
    6913             : 
    6914             :     /* Workaround in some cases a netCDF bug:
    6915             :      * https://github.com/Unidata/netcdf-c/pull/1442 */
    6916         121 :     if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
    6917             :     {
    6918         119 :         nElems *= nRecords;
    6919         119 :         anCount[0] = nRecords;
    6920         119 :         nRecords = 1;
    6921             :     }
    6922             : 
    6923         121 :     void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
    6924         121 :     if (pBuffer == nullptr)
    6925           0 :         return false;
    6926             : 
    6927         240 :     for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
    6928             :     {
    6929         119 :         anStart[0] = iRecord;
    6930             : 
    6931         119 :         switch (nc_datatype)
    6932             :         {
    6933           5 :             case NC_BYTE:
    6934             :                 status =
    6935           5 :                     nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
    6936             :                                       static_cast<signed char *>(pBuffer));
    6937           5 :                 if (!status)
    6938           5 :                     status = nc_put_vara_schar(
    6939             :                         new_cdfid, nDstVarId, anStart, anCount,
    6940             :                         static_cast<signed char *>(pBuffer));
    6941           5 :                 break;
    6942          28 :             case NC_CHAR:
    6943             :                 status =
    6944          28 :                     nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
    6945             :                                      static_cast<char *>(pBuffer));
    6946          28 :                 if (!status)
    6947             :                     status =
    6948          28 :                         nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
    6949             :                                          static_cast<char *>(pBuffer));
    6950          28 :                 break;
    6951           4 :             case NC_SHORT:
    6952             :                 status =
    6953           4 :                     nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
    6954             :                                       static_cast<short *>(pBuffer));
    6955           4 :                 if (!status)
    6956           4 :                     status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
    6957             :                                                anCount,
    6958             :                                                static_cast<short *>(pBuffer));
    6959           4 :                 break;
    6960          24 :             case NC_INT:
    6961          24 :                 status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
    6962             :                                          static_cast<int *>(pBuffer));
    6963          24 :                 if (!status)
    6964             :                     status =
    6965          24 :                         nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
    6966             :                                         static_cast<int *>(pBuffer));
    6967          24 :                 break;
    6968           4 :             case NC_FLOAT:
    6969             :                 status =
    6970           4 :                     nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
    6971             :                                       static_cast<float *>(pBuffer));
    6972           4 :                 if (!status)
    6973           4 :                     status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
    6974             :                                                anCount,
    6975             :                                                static_cast<float *>(pBuffer));
    6976           4 :                 break;
    6977          43 :             case NC_DOUBLE:
    6978             :                 status =
    6979          43 :                     nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
    6980             :                                        static_cast<double *>(pBuffer));
    6981          43 :                 if (!status)
    6982          43 :                     status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
    6983             :                                                 anCount,
    6984             :                                                 static_cast<double *>(pBuffer));
    6985          43 :                 break;
    6986           1 :             case NC_STRING:
    6987             :                 status =
    6988           1 :                     nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
    6989             :                                        static_cast<char **>(pBuffer));
    6990           1 :                 if (!status)
    6991             :                 {
    6992           1 :                     status = nc_put_vara_string(
    6993             :                         new_cdfid, nDstVarId, anStart, anCount,
    6994             :                         static_cast<const char **>(pBuffer));
    6995           1 :                     nc_free_string(nElems, static_cast<char **>(pBuffer));
    6996             :                 }
    6997           1 :                 break;
    6998             : 
    6999           2 :             case NC_UBYTE:
    7000             :                 status =
    7001           2 :                     nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
    7002             :                                       static_cast<unsigned char *>(pBuffer));
    7003           2 :                 if (!status)
    7004           2 :                     status = nc_put_vara_uchar(
    7005             :                         new_cdfid, nDstVarId, anStart, anCount,
    7006             :                         static_cast<unsigned char *>(pBuffer));
    7007           2 :                 break;
    7008           2 :             case NC_USHORT:
    7009             :                 status =
    7010           2 :                     nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
    7011             :                                        static_cast<unsigned short *>(pBuffer));
    7012           2 :                 if (!status)
    7013           2 :                     status = nc_put_vara_ushort(
    7014             :                         new_cdfid, nDstVarId, anStart, anCount,
    7015             :                         static_cast<unsigned short *>(pBuffer));
    7016           2 :                 break;
    7017           2 :             case NC_UINT:
    7018             :                 status =
    7019           2 :                     nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
    7020             :                                      static_cast<unsigned int *>(pBuffer));
    7021           2 :                 if (!status)
    7022             :                     status =
    7023           2 :                         nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
    7024             :                                          static_cast<unsigned int *>(pBuffer));
    7025           2 :                 break;
    7026           2 :             case NC_INT64:
    7027             :                 status =
    7028           2 :                     nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
    7029             :                                          static_cast<long long *>(pBuffer));
    7030           2 :                 if (!status)
    7031           2 :                     status = nc_put_vara_longlong(
    7032             :                         new_cdfid, nDstVarId, anStart, anCount,
    7033             :                         static_cast<long long *>(pBuffer));
    7034           2 :                 break;
    7035           2 :             case NC_UINT64:
    7036           2 :                 status = nc_get_vara_ulonglong(
    7037             :                     old_cdfid, nSrcVarId, anStart, anCount,
    7038             :                     static_cast<unsigned long long *>(pBuffer));
    7039           2 :                 if (!status)
    7040           2 :                     status = nc_put_vara_ulonglong(
    7041             :                         new_cdfid, nDstVarId, anStart, anCount,
    7042             :                         static_cast<unsigned long long *>(pBuffer));
    7043           2 :                 break;
    7044           0 :             default:
    7045           0 :                 status = NC_EBADTYPE;
    7046             :         }
    7047             : 
    7048         119 :         NCDF_ERR(status);
    7049         119 :         if (status != NC_NOERR)
    7050             :         {
    7051           0 :             VSIFree(pBuffer);
    7052           0 :             return false;
    7053             :         }
    7054             :     }
    7055             : 
    7056         121 :     VSIFree(pBuffer);
    7057         121 :     return true;
    7058             : }
    7059             : 
    7060             : /************************************************************************/
    7061             : /*                         NCDFIsUnlimitedDim()                         */
    7062             : /************************************************************************/
    7063             : 
    7064          58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
    7065             : {
    7066          58 :     if (bIsNC4)
    7067             :     {
    7068          16 :         int nUnlimitedDims = 0;
    7069          16 :         nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
    7070          16 :         bool bFound = false;
    7071          16 :         if (nUnlimitedDims)
    7072             :         {
    7073             :             int *panUnlimitedDimIds =
    7074          16 :                 static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
    7075          16 :             nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
    7076          30 :             for (int i = 0; i < nUnlimitedDims; i++)
    7077             :             {
    7078          22 :                 if (panUnlimitedDimIds[i] == nDimId)
    7079             :                 {
    7080           8 :                     bFound = true;
    7081           8 :                     break;
    7082             :                 }
    7083             :             }
    7084          16 :             CPLFree(panUnlimitedDimIds);
    7085             :         }
    7086          16 :         return bFound;
    7087             :     }
    7088             :     else
    7089             :     {
    7090          42 :         int nUnlimitedDimId = -1;
    7091          42 :         nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
    7092          42 :         return nDimId == nUnlimitedDimId;
    7093             :     }
    7094             : }
    7095             : 
    7096             : /************************************************************************/
    7097             : /*                              CloneGrp()                              */
    7098             : /************************************************************************/
    7099             : 
    7100          16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
    7101             :                              int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7102             : {
    7103             :     // Clone dimensions
    7104          16 :     int nDimCount = -1;
    7105          16 :     int status = nc_inq_ndims(nOldGrpId, &nDimCount);
    7106          16 :     NCDF_ERR(status);
    7107          16 :     if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
    7108           0 :         return false;
    7109             :     int anDimIds[NC_MAX_DIMS];
    7110          16 :     int nUnlimiDimID = -1;
    7111          16 :     status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
    7112          16 :     NCDF_ERR(status);
    7113          16 :     if (bIsNC4)
    7114             :     {
    7115             :         // In NC4, the dimension ids of a group are not necessarily in
    7116             :         // [0,nDimCount-1] range
    7117           8 :         int nDimCount2 = -1;
    7118           8 :         status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
    7119           8 :         NCDF_ERR(status);
    7120           8 :         CPLAssert(nDimCount == nDimCount2);
    7121             :     }
    7122             :     else
    7123             :     {
    7124          36 :         for (int i = 0; i < nDimCount; i++)
    7125          28 :             anDimIds[i] = i;
    7126             :     }
    7127          60 :     for (int i = 0; i < nDimCount; i++)
    7128             :     {
    7129             :         char szDimName[NC_MAX_NAME + 1];
    7130          44 :         szDimName[0] = 0;
    7131          44 :         size_t nLen = 0;
    7132          44 :         const int nDimId = anDimIds[i];
    7133          44 :         status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
    7134          44 :         NCDF_ERR(status);
    7135          44 :         if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
    7136          16 :             nLen = NC_UNLIMITED;
    7137          28 :         else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
    7138          13 :             nLen = nNewSize;
    7139          44 :         int nNewDimId = -1;
    7140          44 :         status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
    7141          44 :         NCDF_ERR(status);
    7142          44 :         CPLAssert(nDimId == nNewDimId);
    7143          44 :         if (status != NC_NOERR)
    7144             :         {
    7145           0 :             return false;
    7146             :         }
    7147             :     }
    7148             : 
    7149             :     // Clone main attributes
    7150          16 :     if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
    7151             :     {
    7152           0 :         return false;
    7153             :     }
    7154             : 
    7155             :     // Clone variable definitions
    7156          16 :     int nVarCount = -1;
    7157          16 :     status = nc_inq_nvars(nOldGrpId, &nVarCount);
    7158          16 :     NCDF_ERR(status);
    7159             : 
    7160         137 :     for (int i = 0; i < nVarCount; i++)
    7161             :     {
    7162             :         char szVarName[NC_MAX_NAME + 1];
    7163         121 :         szVarName[0] = 0;
    7164         121 :         status = nc_inq_varname(nOldGrpId, i, szVarName);
    7165         121 :         NCDF_ERR(status);
    7166         121 :         nc_type nc_datatype = NC_NAT;
    7167         121 :         status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
    7168         121 :         NCDF_ERR(status);
    7169         121 :         int nVarDimCount = -1;
    7170         121 :         status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
    7171         121 :         NCDF_ERR(status);
    7172         121 :         status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
    7173         121 :         NCDF_ERR(status);
    7174         121 :         int nNewVarId = -1;
    7175         121 :         status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
    7176             :                             anDimIds, &nNewVarId);
    7177         121 :         NCDF_ERR(status);
    7178         121 :         CPLAssert(i == nNewVarId);
    7179         121 :         if (status != NC_NOERR)
    7180             :         {
    7181           0 :             return false;
    7182             :         }
    7183             : 
    7184         121 :         if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
    7185             :         {
    7186           0 :             return false;
    7187             :         }
    7188             :     }
    7189             : 
    7190          16 :     status = nc_enddef(nNewGrpId);
    7191          16 :     NCDF_ERR(status);
    7192          16 :     if (status != NC_NOERR)
    7193             :     {
    7194           0 :         return false;
    7195             :     }
    7196             : 
    7197             :     // Clone variable content
    7198         137 :     for (int i = 0; i < nVarCount; i++)
    7199             :     {
    7200         121 :         if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
    7201             :         {
    7202           0 :             return false;
    7203             :         }
    7204             :     }
    7205             : 
    7206          16 :     return true;
    7207             : }
    7208             : 
    7209             : /************************************************************************/
    7210             : /*                              GrowDim()                               */
    7211             : /************************************************************************/
    7212             : 
    7213          13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7214             : {
    7215             :     int nCreationMode;
    7216             :     // Set nCreationMode based on eFormat.
    7217          13 :     switch (eFormat)
    7218             :     {
    7219             : #ifdef NETCDF_HAS_NC2
    7220           0 :         case NCDF_FORMAT_NC2:
    7221           0 :             nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
    7222           0 :             break;
    7223             : #endif
    7224           5 :         case NCDF_FORMAT_NC4:
    7225           5 :             nCreationMode = NC_CLOBBER | NC_NETCDF4;
    7226           5 :             break;
    7227           0 :         case NCDF_FORMAT_NC4C:
    7228           0 :             nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
    7229           0 :             break;
    7230           8 :         case NCDF_FORMAT_NC:
    7231             :         default:
    7232           8 :             nCreationMode = NC_CLOBBER;
    7233           8 :             break;
    7234             :     }
    7235             : 
    7236          13 :     int new_cdfid = -1;
    7237          26 :     CPLString osTmpFilename(osFilename + ".tmp");
    7238          26 :     CPLString osFilenameForNCCreate(osTmpFilename);
    7239             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7240             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7241             :     {
    7242             :         char *pszTemp =
    7243             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    7244             :         osFilenameForNCCreate = pszTemp;
    7245             :         CPLFree(pszTemp);
    7246             :     }
    7247             : #endif
    7248          13 :     int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
    7249          13 :     NCDF_ERR(status);
    7250          13 :     if (status != NC_NOERR)
    7251           0 :         return false;
    7252             : 
    7253          13 :     if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
    7254             :                   nDimIdToGrow, nNewSize))
    7255             :     {
    7256           0 :         GDAL_nc_close(new_cdfid);
    7257           0 :         return false;
    7258             :     }
    7259             : 
    7260          13 :     int nGroupCount = 0;
    7261          26 :     std::vector<CPLString> oListGrpName;
    7262          31 :     if (eFormat == NCDF_FORMAT_NC4 &&
    7263          18 :         nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
    7264           5 :         nGroupCount > 0)
    7265             :     {
    7266             :         int *panGroupIds =
    7267           2 :             static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
    7268           2 :         status = nc_inq_grps(cdfid, nullptr, panGroupIds);
    7269           2 :         NCDF_ERR(status);
    7270           5 :         for (int i = 0; i < nGroupCount; i++)
    7271             :         {
    7272             :             char szGroupName[NC_MAX_NAME + 1];
    7273           3 :             szGroupName[0] = 0;
    7274           3 :             NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
    7275           3 :             int nNewGrpId = -1;
    7276           3 :             status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
    7277           3 :             NCDF_ERR(status);
    7278           3 :             if (status != NC_NOERR)
    7279             :             {
    7280           0 :                 CPLFree(panGroupIds);
    7281           0 :                 GDAL_nc_close(new_cdfid);
    7282           0 :                 return false;
    7283             :             }
    7284           3 :             if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
    7285             :                           nDimIdToGrow, nNewSize))
    7286             :             {
    7287           0 :                 CPLFree(panGroupIds);
    7288           0 :                 GDAL_nc_close(new_cdfid);
    7289           0 :                 return false;
    7290             :             }
    7291             :         }
    7292           2 :         CPLFree(panGroupIds);
    7293             : 
    7294           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7295             :         {
    7296           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7297           3 :             if (poLayer)
    7298             :             {
    7299             :                 char szGroupName[NC_MAX_NAME + 1];
    7300           3 :                 szGroupName[0] = 0;
    7301           3 :                 status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
    7302           3 :                 NCDF_ERR(status);
    7303           3 :                 oListGrpName.push_back(szGroupName);
    7304             :             }
    7305             :         }
    7306             :     }
    7307             : 
    7308          13 :     GDAL_nc_close(cdfid);
    7309          13 :     cdfid = -1;
    7310          13 :     GDAL_nc_close(new_cdfid);
    7311             : 
    7312          26 :     CPLString osOriFilename(osFilename + ".ori");
    7313          26 :     if (VSIRename(osFilename, osOriFilename) != 0 ||
    7314          13 :         VSIRename(osTmpFilename, osFilename) != 0)
    7315             :     {
    7316           0 :         CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
    7317           0 :         return false;
    7318             :     }
    7319          13 :     VSIUnlink(osOriFilename);
    7320             : 
    7321          26 :     CPLString osFilenameForNCOpen(osFilename);
    7322             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7323             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7324             :     {
    7325             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    7326             :         osFilenameForNCOpen = pszTemp;
    7327             :         CPLFree(pszTemp);
    7328             :     }
    7329             : #endif
    7330          13 :     status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
    7331          13 :     NCDF_ERR(status);
    7332          13 :     if (status != NC_NOERR)
    7333           0 :         return false;
    7334          13 :     bDefineMode = false;
    7335             : 
    7336          13 :     if (!oListGrpName.empty())
    7337             :     {
    7338           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7339             :         {
    7340           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7341           3 :             if (poLayer)
    7342             :             {
    7343           3 :                 int nNewLayerCDFID = -1;
    7344           3 :                 status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
    7345             :                                      &nNewLayerCDFID);
    7346           3 :                 NCDF_ERR(status);
    7347           3 :                 poLayer->SetCDFID(nNewLayerCDFID);
    7348             :             }
    7349             :         }
    7350             :     }
    7351             :     else
    7352             :     {
    7353          22 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7354             :         {
    7355          11 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7356          11 :             if (poLayer)
    7357          11 :                 poLayer->SetCDFID(cdfid);
    7358             :         }
    7359             :     }
    7360             : 
    7361          13 :     return true;
    7362             : }
    7363             : 
    7364             : #ifdef ENABLE_NCDUMP
    7365             : 
    7366             : /************************************************************************/
    7367             : /*                    netCDFDatasetCreateTempFile()                     */
    7368             : /************************************************************************/
    7369             : 
    7370             : /* Create a netCDF file from a text dump (format of ncdump) */
    7371             : /* Mostly to easy fuzzing of the driver, while still generating valid */
    7372             : /* netCDF files. */
    7373             : /* Note: not all data types are supported ! */
    7374           4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
    7375             :                                  const char *pszTmpFilename, VSILFILE *fpSrc)
    7376             : {
    7377           4 :     CPL_IGNORE_RET_VAL(eFormat);
    7378           4 :     int nCreateMode = NC_CLOBBER;
    7379           4 :     if (eFormat == NCDF_FORMAT_NC4)
    7380           1 :         nCreateMode |= NC_NETCDF4;
    7381           3 :     else if (eFormat == NCDF_FORMAT_NC4C)
    7382           0 :         nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
    7383           4 :     int nCdfId = -1;
    7384           4 :     int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
    7385           4 :     if (status != NC_NOERR)
    7386             :     {
    7387           0 :         return false;
    7388             :     }
    7389           4 :     VSIFSeekL(fpSrc, 0, SEEK_SET);
    7390             :     const char *pszLine;
    7391           4 :     constexpr int SECTION_NONE = 0;
    7392           4 :     constexpr int SECTION_DIMENSIONS = 1;
    7393           4 :     constexpr int SECTION_VARIABLES = 2;
    7394           4 :     constexpr int SECTION_DATA = 3;
    7395           4 :     int nActiveSection = SECTION_NONE;
    7396           8 :     std::map<CPLString, int> oMapDimToId;
    7397           8 :     std::map<int, int> oMapDimIdToDimLen;
    7398           8 :     std::map<CPLString, int> oMapVarToId;
    7399           8 :     std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
    7400           8 :     std::map<int, int> oMapVarIdToType;
    7401           4 :     std::set<CPLString> oSetAttrDefined;
    7402           4 :     oMapVarToId[""] = -1;
    7403           4 :     size_t nTotalVarSize = 0;
    7404         208 :     while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
    7405             :     {
    7406         204 :         if (STARTS_WITH(pszLine, "dimensions:") &&
    7407             :             nActiveSection == SECTION_NONE)
    7408             :         {
    7409           4 :             nActiveSection = SECTION_DIMENSIONS;
    7410             :         }
    7411         200 :         else if (STARTS_WITH(pszLine, "variables:") &&
    7412             :                  nActiveSection == SECTION_DIMENSIONS)
    7413             :         {
    7414           4 :             nActiveSection = SECTION_VARIABLES;
    7415             :         }
    7416         196 :         else if (STARTS_WITH(pszLine, "data:") &&
    7417             :                  nActiveSection == SECTION_VARIABLES)
    7418             :         {
    7419           4 :             nActiveSection = SECTION_DATA;
    7420           4 :             status = nc_enddef(nCdfId);
    7421           4 :             if (status != NC_NOERR)
    7422             :             {
    7423           0 :                 CPLDebug("netCDF", "nc_enddef() failed: %s",
    7424             :                          nc_strerror(status));
    7425             :             }
    7426             :         }
    7427         192 :         else if (nActiveSection == SECTION_DIMENSIONS)
    7428             :         {
    7429             :             const CPLStringList aosTokens(
    7430           9 :                 CSLTokenizeString2(pszLine, " \t=;", 0));
    7431           9 :             if (aosTokens.size() == 2)
    7432             :             {
    7433           9 :                 const char *pszDimName = aosTokens[0];
    7434           9 :                 bool bValidName = true;
    7435           9 :                 if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
    7436             :                 {
    7437             :                     // This is an internal netcdf prefix. Using it may
    7438             :                     // cause memory leaks.
    7439           0 :                     bValidName = false;
    7440             :                 }
    7441           9 :                 if (!bValidName)
    7442             :                 {
    7443           0 :                     CPLDebug("netCDF",
    7444             :                              "nc_def_dim(%s) failed: invalid name found",
    7445             :                              pszDimName);
    7446           0 :                     continue;
    7447             :                 }
    7448             : 
    7449             :                 const bool bIsASCII =
    7450           9 :                     CPLIsASCII(pszDimName, static_cast<size_t>(-1));
    7451           9 :                 if (!bIsASCII)
    7452             :                 {
    7453             :                     // Workaround https://github.com/Unidata/netcdf-c/pull/450
    7454           0 :                     CPLDebug("netCDF",
    7455             :                              "nc_def_dim(%s) failed: rejected because "
    7456             :                              "of non-ASCII characters",
    7457             :                              pszDimName);
    7458           0 :                     continue;
    7459             :                 }
    7460           9 :                 int nDimSize = EQUAL(aosTokens[1], "UNLIMITED")
    7461             :                                    ? NC_UNLIMITED
    7462           9 :                                    : atoi(aosTokens[1]);
    7463           9 :                 if (nDimSize >= 1000)
    7464           1 :                     nDimSize = 1000;  // to avoid very long processing
    7465           9 :                 if (nDimSize >= 0)
    7466             :                 {
    7467           9 :                     int nDimId = -1;
    7468           9 :                     status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
    7469           9 :                     if (status != NC_NOERR)
    7470             :                     {
    7471           0 :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
    7472             :                                  pszDimName, nDimSize, nc_strerror(status));
    7473             :                     }
    7474             :                     else
    7475             :                     {
    7476             : #ifdef DEBUG_VERBOSE
    7477             :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
    7478             :                                  pszDimName, nDimSize, pszLine);
    7479             : #endif
    7480           9 :                         oMapDimToId[pszDimName] = nDimId;
    7481           9 :                         oMapDimIdToDimLen[nDimId] = nDimSize;
    7482             :                     }
    7483             :                 }
    7484             :             }
    7485             :         }
    7486         183 :         else if (nActiveSection == SECTION_VARIABLES)
    7487             :         {
    7488         390 :             while (*pszLine == ' ' || *pszLine == '\t')
    7489         249 :                 pszLine++;
    7490         141 :             const char *pszColumn = strchr(pszLine, ':');
    7491         141 :             const char *pszEqual = strchr(pszLine, '=');
    7492         141 :             if (pszColumn == nullptr)
    7493             :             {
    7494             :                 const CPLStringList aosTokens(
    7495          21 :                     CSLTokenizeString2(pszLine, " \t=(),;", 0));
    7496          21 :                 if (aosTokens.size() >= 2)
    7497             :                 {
    7498          17 :                     const char *pszVarName = aosTokens[1];
    7499          17 :                     bool bValidName = true;
    7500          17 :                     if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
    7501             :                     {
    7502             :                         // This is an internal netcdf prefix. Using it may
    7503             :                         // cause memory leaks.
    7504           0 :                         bValidName = false;
    7505             :                     }
    7506         138 :                     for (int i = 0; pszVarName[i]; i++)
    7507             :                     {
    7508         121 :                         if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
    7509          28 :                               (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
    7510           9 :                               (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
    7511           6 :                               pszVarName[i] == '_'))
    7512             :                         {
    7513           0 :                             bValidName = false;
    7514             :                         }
    7515             :                     }
    7516          17 :                     if (!bValidName)
    7517             :                     {
    7518           0 :                         CPLDebug(
    7519             :                             "netCDF",
    7520             :                             "nc_def_var(%s) failed: illegal character found",
    7521             :                             pszVarName);
    7522           0 :                         continue;
    7523             :                     }
    7524          17 :                     if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
    7525             :                     {
    7526           0 :                         CPLDebug("netCDF",
    7527             :                                  "nc_def_var(%s) failed: already defined",
    7528             :                                  pszVarName);
    7529           0 :                         continue;
    7530             :                     }
    7531          17 :                     const char *pszVarType = aosTokens[0];
    7532          17 :                     int nc_datatype = NC_BYTE;
    7533          17 :                     size_t nDataTypeSize = 1;
    7534          17 :                     if (EQUAL(pszVarType, "char"))
    7535             :                     {
    7536           6 :                         nc_datatype = NC_CHAR;
    7537           6 :                         nDataTypeSize = 1;
    7538             :                     }
    7539          11 :                     else if (EQUAL(pszVarType, "byte"))
    7540             :                     {
    7541           3 :                         nc_datatype = NC_BYTE;
    7542           3 :                         nDataTypeSize = 1;
    7543             :                     }
    7544           8 :                     else if (EQUAL(pszVarType, "short"))
    7545             :                     {
    7546           0 :                         nc_datatype = NC_SHORT;
    7547           0 :                         nDataTypeSize = 2;
    7548             :                     }
    7549           8 :                     else if (EQUAL(pszVarType, "int"))
    7550             :                     {
    7551           0 :                         nc_datatype = NC_INT;
    7552           0 :                         nDataTypeSize = 4;
    7553             :                     }
    7554           8 :                     else if (EQUAL(pszVarType, "float"))
    7555             :                     {
    7556           0 :                         nc_datatype = NC_FLOAT;
    7557           0 :                         nDataTypeSize = 4;
    7558             :                     }
    7559           8 :                     else if (EQUAL(pszVarType, "double"))
    7560             :                     {
    7561           8 :                         nc_datatype = NC_DOUBLE;
    7562           8 :                         nDataTypeSize = 8;
    7563             :                     }
    7564           0 :                     else if (EQUAL(pszVarType, "ubyte"))
    7565             :                     {
    7566           0 :                         nc_datatype = NC_UBYTE;
    7567           0 :                         nDataTypeSize = 1;
    7568             :                     }
    7569           0 :                     else if (EQUAL(pszVarType, "ushort"))
    7570             :                     {
    7571           0 :                         nc_datatype = NC_USHORT;
    7572           0 :                         nDataTypeSize = 2;
    7573             :                     }
    7574           0 :                     else if (EQUAL(pszVarType, "uint"))
    7575             :                     {
    7576           0 :                         nc_datatype = NC_UINT;
    7577           0 :                         nDataTypeSize = 4;
    7578             :                     }
    7579           0 :                     else if (EQUAL(pszVarType, "int64"))
    7580             :                     {
    7581           0 :                         nc_datatype = NC_INT64;
    7582           0 :                         nDataTypeSize = 8;
    7583             :                     }
    7584           0 :                     else if (EQUAL(pszVarType, "uint64"))
    7585             :                     {
    7586           0 :                         nc_datatype = NC_UINT64;
    7587           0 :                         nDataTypeSize = 8;
    7588             :                     }
    7589             : 
    7590          17 :                     int nDims = aosTokens.size() - 2;
    7591          17 :                     if (nDims >= 32)
    7592             :                     {
    7593             :                         // The number of dimensions in a netCDFv4 file is
    7594             :                         // limited by #define H5S_MAX_RANK    32
    7595             :                         // but libnetcdf doesn't check that...
    7596           0 :                         CPLDebug("netCDF",
    7597             :                                  "nc_def_var(%s) failed: too many dimensions",
    7598             :                                  pszVarName);
    7599           0 :                         continue;
    7600             :                     }
    7601          17 :                     std::vector<int> aoDimIds;
    7602          17 :                     bool bFailed = false;
    7603          17 :                     size_t nSize = 1;
    7604          35 :                     for (int i = 0; i < nDims; i++)
    7605             :                     {
    7606          18 :                         const char *pszDimName = aosTokens[2 + i];
    7607          18 :                         if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
    7608             :                         {
    7609           0 :                             bFailed = true;
    7610           0 :                             break;
    7611             :                         }
    7612          18 :                         const int nDimId = oMapDimToId[pszDimName];
    7613          18 :                         aoDimIds.push_back(nDimId);
    7614             : 
    7615          18 :                         const size_t nDimSize = oMapDimIdToDimLen[nDimId];
    7616          18 :                         if (nDimSize != 0)
    7617             :                         {
    7618          18 :                             if (nSize >
    7619          18 :                                 std::numeric_limits<size_t>::max() / nDimSize)
    7620             :                             {
    7621           0 :                                 bFailed = true;
    7622           0 :                                 break;
    7623             :                             }
    7624             :                             else
    7625             :                             {
    7626          18 :                                 nSize *= nDimSize;
    7627             :                             }
    7628             :                         }
    7629             :                     }
    7630          17 :                     if (bFailed)
    7631             :                     {
    7632           0 :                         CPLDebug("netCDF",
    7633             :                                  "nc_def_var(%s) failed: unknown dimension(s)",
    7634             :                                  pszVarName);
    7635           0 :                         continue;
    7636             :                     }
    7637          17 :                     if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
    7638             :                     {
    7639           0 :                         CPLDebug("netCDF",
    7640             :                                  "nc_def_var(%s) failed: too large data",
    7641             :                                  pszVarName);
    7642           0 :                         continue;
    7643             :                     }
    7644          17 :                     if (nTotalVarSize >
    7645          34 :                             std::numeric_limits<size_t>::max() - nSize ||
    7646          17 :                         nTotalVarSize + nSize > 100 * 1024 * 1024)
    7647             :                     {
    7648           0 :                         CPLDebug("netCDF",
    7649             :                                  "nc_def_var(%s) failed: too large data",
    7650             :                                  pszVarName);
    7651           0 :                         continue;
    7652             :                     }
    7653          17 :                     nTotalVarSize += nSize;
    7654             : 
    7655          17 :                     int nVarId = -1;
    7656             :                     status =
    7657          30 :                         nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
    7658          13 :                                    (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
    7659          17 :                     if (status != NC_NOERR)
    7660             :                     {
    7661           0 :                         CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
    7662             :                                  pszVarName, nc_strerror(status));
    7663             :                     }
    7664             :                     else
    7665             :                     {
    7666             : #ifdef DEBUG_VERBOSE
    7667             :                         CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
    7668             :                                  pszVarName, pszLine);
    7669             : #endif
    7670          17 :                         oMapVarToId[pszVarName] = nVarId;
    7671          17 :                         oMapVarIdToType[nVarId] = nc_datatype;
    7672          17 :                         oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
    7673             :                     }
    7674             :                 }
    7675             :             }
    7676         120 :             else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
    7677             :             {
    7678         116 :                 CPLString osVarName(pszLine, pszColumn - pszLine);
    7679         116 :                 CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
    7680         116 :                 osAttrName.Trim();
    7681         116 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7682             :                 {
    7683           0 :                     CPLDebug("netCDF",
    7684             :                              "nc_put_att(%s:%s) failed: "
    7685             :                              "no corresponding variable",
    7686             :                              osVarName.c_str(), osAttrName.c_str());
    7687           0 :                     continue;
    7688             :                 }
    7689         116 :                 bool bValidName = true;
    7690        1743 :                 for (size_t i = 0; i < osAttrName.size(); i++)
    7691             :                 {
    7692        1865 :                     if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
    7693         238 :                           (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
    7694         158 :                           (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
    7695         158 :                           osAttrName[i] == '_'))
    7696             :                     {
    7697           0 :                         bValidName = false;
    7698             :                     }
    7699             :                 }
    7700         116 :                 if (!bValidName)
    7701             :                 {
    7702           0 :                     CPLDebug(
    7703             :                         "netCDF",
    7704             :                         "nc_put_att(%s:%s) failed: illegal character found",
    7705             :                         osVarName.c_str(), osAttrName.c_str());
    7706           0 :                     continue;
    7707             :                 }
    7708         116 :                 if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
    7709         232 :                     oSetAttrDefined.end())
    7710             :                 {
    7711           0 :                     CPLDebug("netCDF",
    7712             :                              "nc_put_att(%s:%s) failed: already defined",
    7713             :                              osVarName.c_str(), osAttrName.c_str());
    7714           0 :                     continue;
    7715             :                 }
    7716             : 
    7717         116 :                 const int nVarId = oMapVarToId[osVarName];
    7718         116 :                 const char *pszValue = pszEqual + 1;
    7719         232 :                 while (*pszValue == ' ')
    7720         116 :                     pszValue++;
    7721             : 
    7722         116 :                 status = NC_EBADTYPE;
    7723         116 :                 if (*pszValue == '"')
    7724             :                 {
    7725             :                     // For _FillValue, the attribute type should match
    7726             :                     // the variable type. Leaks memory with NC4 otherwise
    7727          74 :                     if (osAttrName == "_FillValue")
    7728             :                     {
    7729           0 :                         CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7730             :                                  osVarName.c_str(), osAttrName.c_str(),
    7731             :                                  nc_strerror(status));
    7732           0 :                         continue;
    7733             :                     }
    7734             : 
    7735             :                     // Unquote and unescape string value
    7736          74 :                     CPLString osVal(pszValue + 1);
    7737         222 :                     while (!osVal.empty())
    7738             :                     {
    7739         222 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7740             :                         {
    7741         148 :                             osVal.pop_back();
    7742             :                         }
    7743          74 :                         else if (osVal.back() == '"')
    7744             :                         {
    7745          74 :                             osVal.pop_back();
    7746          74 :                             break;
    7747             :                         }
    7748             :                         else
    7749             :                         {
    7750           0 :                             break;
    7751             :                         }
    7752             :                     }
    7753          74 :                     osVal.replaceAll("\\\"", '"');
    7754          74 :                     status = nc_put_att_text(nCdfId, nVarId, osAttrName,
    7755             :                                              osVal.size(), osVal.c_str());
    7756             :                 }
    7757             :                 else
    7758             :                 {
    7759          84 :                     CPLString osVal(pszValue);
    7760         126 :                     while (!osVal.empty())
    7761             :                     {
    7762         126 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7763             :                         {
    7764          84 :                             osVal.pop_back();
    7765             :                         }
    7766             :                         else
    7767             :                         {
    7768          42 :                             break;
    7769             :                         }
    7770             :                     }
    7771          42 :                     int nc_datatype = -1;
    7772          42 :                     if (!osVal.empty() && osVal.back() == 'b')
    7773             :                     {
    7774           3 :                         nc_datatype = NC_BYTE;
    7775           3 :                         osVal.pop_back();
    7776             :                     }
    7777          39 :                     else if (!osVal.empty() && osVal.back() == 's')
    7778             :                     {
    7779           3 :                         nc_datatype = NC_SHORT;
    7780           3 :                         osVal.pop_back();
    7781             :                     }
    7782          42 :                     if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
    7783             :                     {
    7784           7 :                         if (nc_datatype < 0)
    7785           4 :                             nc_datatype = NC_INT;
    7786             :                     }
    7787          35 :                     else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
    7788             :                     {
    7789          32 :                         nc_datatype = NC_DOUBLE;
    7790             :                     }
    7791             :                     else
    7792             :                     {
    7793           3 :                         nc_datatype = -1;
    7794             :                     }
    7795             : 
    7796             :                     // For _FillValue, check that the attribute type matches
    7797             :                     // the variable type. Leaks memory with NC4 otherwise
    7798          42 :                     if (osAttrName == "_FillValue")
    7799             :                     {
    7800           6 :                         if (nVarId < 0 ||
    7801           3 :                             nc_datatype != oMapVarIdToType[nVarId])
    7802             :                         {
    7803           0 :                             nc_datatype = -1;
    7804             :                         }
    7805             :                     }
    7806             : 
    7807          42 :                     if (nc_datatype == NC_BYTE)
    7808             :                     {
    7809             :                         signed char chVal =
    7810           3 :                             static_cast<signed char>(atoi(osVal));
    7811           3 :                         status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
    7812             :                                                   NC_BYTE, 1, &chVal);
    7813             :                     }
    7814          39 :                     else if (nc_datatype == NC_SHORT)
    7815             :                     {
    7816           0 :                         short nVal = static_cast<short>(atoi(osVal));
    7817           0 :                         status = nc_put_att_short(nCdfId, nVarId, osAttrName,
    7818             :                                                   NC_SHORT, 1, &nVal);
    7819             :                     }
    7820          39 :                     else if (nc_datatype == NC_INT)
    7821             :                     {
    7822           4 :                         int nVal = static_cast<int>(atoi(osVal));
    7823           4 :                         status = nc_put_att_int(nCdfId, nVarId, osAttrName,
    7824             :                                                 NC_INT, 1, &nVal);
    7825             :                     }
    7826          35 :                     else if (nc_datatype == NC_DOUBLE)
    7827             :                     {
    7828          32 :                         double dfVal = CPLAtof(osVal);
    7829          32 :                         status = nc_put_att_double(nCdfId, nVarId, osAttrName,
    7830             :                                                    NC_DOUBLE, 1, &dfVal);
    7831             :                     }
    7832             :                 }
    7833         116 :                 if (status != NC_NOERR)
    7834             :                 {
    7835           3 :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7836             :                              osVarName.c_str(), osAttrName.c_str(),
    7837             :                              nc_strerror(status));
    7838             :                 }
    7839             :                 else
    7840             :                 {
    7841         113 :                     oSetAttrDefined.insert(osVarName + ":" + osAttrName);
    7842             : #ifdef DEBUG_VERBOSE
    7843             :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
    7844             :                              osVarName.c_str(), osAttrName.c_str(), pszLine);
    7845             : #endif
    7846             :                 }
    7847             :             }
    7848             :         }
    7849          42 :         else if (nActiveSection == SECTION_DATA)
    7850             :         {
    7851          55 :             while (*pszLine == ' ' || *pszLine == '\t')
    7852          17 :                 pszLine++;
    7853          38 :             const char *pszEqual = strchr(pszLine, '=');
    7854          38 :             if (pszEqual)
    7855             :             {
    7856          17 :                 CPLString osVarName(pszLine, pszEqual - pszLine);
    7857          17 :                 osVarName.Trim();
    7858          17 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7859           0 :                     continue;
    7860          17 :                 const int nVarId = oMapVarToId[osVarName];
    7861          17 :                 CPLString osAccVal(pszEqual + 1);
    7862          17 :                 osAccVal.Trim();
    7863         153 :                 while (osAccVal.empty() || osAccVal.back() != ';')
    7864             :                 {
    7865         136 :                     pszLine = CPLReadLineL(fpSrc);
    7866         136 :                     if (pszLine == nullptr)
    7867           0 :                         break;
    7868         272 :                     CPLString osVal(pszLine);
    7869         136 :                     osVal.Trim();
    7870         136 :                     osAccVal += osVal;
    7871             :                 }
    7872          17 :                 if (pszLine == nullptr)
    7873           0 :                     break;
    7874          17 :                 osAccVal.pop_back();
    7875             : 
    7876             :                 const std::vector<int> aoDimIds =
    7877          34 :                     oMapVarIdToVectorOfDimId[nVarId];
    7878          17 :                 size_t nSize = 1;
    7879          34 :                 std::vector<size_t> aoStart, aoEdge;
    7880          17 :                 aoStart.resize(aoDimIds.size());
    7881          17 :                 aoEdge.resize(aoDimIds.size());
    7882          35 :                 for (size_t i = 0; i < aoDimIds.size(); ++i)
    7883             :                 {
    7884          18 :                     const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
    7885          36 :                     if (nDimSize != 0 &&
    7886          18 :                         nSize > std::numeric_limits<size_t>::max() / nDimSize)
    7887             :                     {
    7888           0 :                         nSize = 0;
    7889             :                     }
    7890             :                     else
    7891             :                     {
    7892          18 :                         nSize *= nDimSize;
    7893             :                     }
    7894          18 :                     aoStart[i] = 0;
    7895          18 :                     aoEdge[i] = nDimSize;
    7896             :                 }
    7897             : 
    7898          17 :                 status = NC_EBADTYPE;
    7899          17 :                 if (nSize == 0)
    7900             :                 {
    7901             :                     // Might happen with an unlimited dimension
    7902             :                 }
    7903          17 :                 else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
    7904             :                 {
    7905           8 :                     if (!aoStart.empty())
    7906             :                     {
    7907             :                         const CPLStringList aosTokens(
    7908          16 :                             CSLTokenizeString2(osAccVal, " ,;", 0));
    7909           8 :                         size_t nTokens = aosTokens.size();
    7910           8 :                         if (nTokens >= nSize)
    7911             :                         {
    7912             :                             double *padfVals = static_cast<double *>(
    7913           8 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
    7914           8 :                             if (padfVals)
    7915             :                             {
    7916         132 :                                 for (size_t i = 0; i < nSize; i++)
    7917             :                                 {
    7918         124 :                                     padfVals[i] = CPLAtof(aosTokens[i]);
    7919             :                                 }
    7920           8 :                                 status = nc_put_vara_double(
    7921           8 :                                     nCdfId, nVarId, &aoStart[0], &aoEdge[0],
    7922             :                                     padfVals);
    7923           8 :                                 VSIFree(padfVals);
    7924             :                             }
    7925             :                         }
    7926             :                     }
    7927             :                 }
    7928           9 :                 else if (oMapVarIdToType[nVarId] == NC_BYTE)
    7929             :                 {
    7930           3 :                     if (!aoStart.empty())
    7931             :                     {
    7932             :                         const CPLStringList aosTokens(
    7933           6 :                             CSLTokenizeString2(osAccVal, " ,;", 0));
    7934           3 :                         size_t nTokens = aosTokens.size();
    7935           3 :                         if (nTokens >= nSize)
    7936             :                         {
    7937             :                             signed char *panVals = static_cast<signed char *>(
    7938           3 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
    7939           3 :                             if (panVals)
    7940             :                             {
    7941        1203 :                                 for (size_t i = 0; i < nSize; i++)
    7942             :                                 {
    7943        1200 :                                     panVals[i] = static_cast<signed char>(
    7944        1200 :                                         atoi(aosTokens[i]));
    7945             :                                 }
    7946           3 :                                 status = nc_put_vara_schar(nCdfId, nVarId,
    7947           3 :                                                            &aoStart[0],
    7948           3 :                                                            &aoEdge[0], panVals);
    7949           3 :                                 VSIFree(panVals);
    7950             :                             }
    7951             :                         }
    7952             :                     }
    7953             :                 }
    7954           6 :                 else if (oMapVarIdToType[nVarId] == NC_CHAR)
    7955             :                 {
    7956           6 :                     if (aoStart.size() == 2)
    7957             :                     {
    7958           4 :                         std::vector<CPLString> aoStrings;
    7959           2 :                         bool bInString = false;
    7960           4 :                         CPLString osCurString;
    7961         935 :                         for (size_t i = 0; i < osAccVal.size();)
    7962             :                         {
    7963         933 :                             if (!bInString)
    7964             :                             {
    7965           8 :                                 if (osAccVal[i] == '"')
    7966             :                                 {
    7967           4 :                                     bInString = true;
    7968           4 :                                     osCurString.clear();
    7969             :                                 }
    7970           8 :                                 i++;
    7971             :                             }
    7972         926 :                             else if (osAccVal[i] == '\\' &&
    7973         926 :                                      i + 1 < osAccVal.size() &&
    7974           1 :                                      osAccVal[i + 1] == '"')
    7975             :                             {
    7976           1 :                                 osCurString += '"';
    7977           1 :                                 i += 2;
    7978             :                             }
    7979         924 :                             else if (osAccVal[i] == '"')
    7980             :                             {
    7981           4 :                                 aoStrings.push_back(osCurString);
    7982           4 :                                 osCurString.clear();
    7983           4 :                                 bInString = false;
    7984           4 :                                 i++;
    7985             :                             }
    7986             :                             else
    7987             :                             {
    7988         920 :                                 osCurString += osAccVal[i];
    7989         920 :                                 i++;
    7990             :                             }
    7991             :                         }
    7992           2 :                         const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
    7993           2 :                         const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
    7994           2 :                         size_t nIters = aoStrings.size();
    7995           2 :                         if (nIters > nRecords)
    7996           0 :                             nIters = nRecords;
    7997           6 :                         for (size_t i = 0; i < nIters; i++)
    7998             :                         {
    7999             :                             size_t anIndex[2];
    8000           4 :                             anIndex[0] = i;
    8001           4 :                             anIndex[1] = 0;
    8002             :                             size_t anCount[2];
    8003           4 :                             anCount[0] = 1;
    8004           4 :                             anCount[1] = aoStrings[i].size();
    8005           4 :                             if (anCount[1] > nWidth)
    8006           0 :                                 anCount[1] = nWidth;
    8007             :                             status =
    8008           4 :                                 nc_put_vara_text(nCdfId, nVarId, anIndex,
    8009           4 :                                                  anCount, aoStrings[i].c_str());
    8010           4 :                             if (status != NC_NOERR)
    8011           0 :                                 break;
    8012             :                         }
    8013             :                     }
    8014             :                 }
    8015          17 :                 if (status != NC_NOERR)
    8016             :                 {
    8017           4 :                     CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
    8018             :                              osVarName.c_str(), nc_strerror(status));
    8019             :                 }
    8020             :             }
    8021             :         }
    8022             :     }
    8023             : 
    8024           4 :     GDAL_nc_close(nCdfId);
    8025           4 :     return true;
    8026             : }
    8027             : 
    8028             : #endif  // ENABLE_NCDUMP
    8029             : 
    8030             : /************************************************************************/
    8031             : /*                                Open()                                */
    8032             : /************************************************************************/
    8033             : 
    8034         798 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
    8035             : 
    8036             : {
    8037             : #ifdef NCDF_DEBUG
    8038             :     CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
    8039             :              poOpenInfo->pszFilename);
    8040             : #endif
    8041             : 
    8042             :     // Does this appear to be a netcdf file?
    8043         798 :     NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
    8044         798 :     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8045             :     {
    8046         734 :         eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
    8047             : #ifdef NCDF_DEBUG
    8048             :         CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
    8049             : #endif
    8050             :         // Note: not calling Identify() directly, because we want the file type.
    8051             :         // Only support NCDF_FORMAT* formats.
    8052         734 :         if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
    8053           2 :             NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
    8054             :         {
    8055             :             // ok
    8056             :         }
    8057           2 :         else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
    8058           0 :                  poOpenInfo->IsSingleAllowedDriver("netCDF"))
    8059             :         {
    8060             :             // ok
    8061             :         }
    8062             :         else
    8063             :         {
    8064           2 :             return nullptr;
    8065             :         }
    8066             :     }
    8067             :     else
    8068             :     {
    8069             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    8070             :         // We don't necessarily want to catch bugs in libnetcdf ...
    8071             :         if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
    8072             :         {
    8073             :             return nullptr;
    8074             :         }
    8075             : #endif
    8076             :     }
    8077             : 
    8078         796 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
    8079             :     {
    8080         281 :         return OpenMultiDim(poOpenInfo);
    8081             :     }
    8082             : 
    8083        1030 :     CPLMutexHolderD(&hNCMutex);
    8084             : 
    8085         515 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    8086             :     // GDALDataset own mutex.
    8087         515 :     netCDFDataset *poDS = new netCDFDataset();
    8088         515 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    8089         515 :     CPLAcquireMutex(hNCMutex, 1000.0);
    8090             : 
    8091         515 :     poDS->SetDescription(poOpenInfo->pszFilename);
    8092             : 
    8093             :     // Check if filename start with NETCDF: tag.
    8094         515 :     bool bTreatAsSubdataset = false;
    8095        1030 :     CPLString osSubdatasetName;
    8096             : 
    8097             : #ifdef ENABLE_NCDUMP
    8098         515 :     const char *pszHeader =
    8099             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    8100         515 :     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
    8101           3 :         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
    8102             :     {
    8103             :         // By default create a temporary file that will be destroyed,
    8104             :         // unless NETCDF_TMP_FILE is defined. Can be useful to see which
    8105             :         // netCDF file has been generated from a potential fuzzed input.
    8106           3 :         poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
    8107           3 :         if (poDS->osFilename.empty())
    8108             :         {
    8109           3 :             poDS->bFileToDestroyAtClosing = true;
    8110           3 :             poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
    8111             :         }
    8112           3 :         if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
    8113             :                                          poOpenInfo->fpL))
    8114             :         {
    8115           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8116             :             // deadlock with GDALDataset own mutex.
    8117           0 :             delete poDS;
    8118           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8119           0 :             return nullptr;
    8120             :         }
    8121           3 :         bTreatAsSubdataset = false;
    8122           3 :         poDS->eFormat = eTmpFormat;
    8123             :     }
    8124             :     else
    8125             : #endif
    8126             : 
    8127         512 :         if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8128             :     {
    8129             :         GDALSubdatasetInfoH hInfo =
    8130          64 :             GDALGetSubdatasetInfo(poOpenInfo->pszFilename);
    8131          64 :         if (hInfo)
    8132             :         {
    8133          64 :             char *pszPath = GDALSubdatasetInfoGetPathComponent(hInfo);
    8134          64 :             poDS->osFilename = pszPath;
    8135          64 :             CPLFree(pszPath);
    8136             : 
    8137             :             char *pszSubdataset =
    8138          64 :                 GDALSubdatasetInfoGetSubdatasetComponent(hInfo);
    8139          64 :             if (pszSubdataset && pszSubdataset[0] != '\0')
    8140             :             {
    8141          64 :                 osSubdatasetName = pszSubdataset;
    8142          64 :                 bTreatAsSubdataset = true;
    8143             :             }
    8144             :             else
    8145             :             {
    8146           0 :                 osSubdatasetName = "";
    8147           0 :                 bTreatAsSubdataset = false;
    8148             :             }
    8149          64 :             CPLFree(pszSubdataset);
    8150             : 
    8151          64 :             GDALDestroySubdatasetInfo(hInfo);
    8152             :         }
    8153             : 
    8154         128 :         if (!STARTS_WITH(poDS->osFilename, "http://") &&
    8155          64 :             !STARTS_WITH(poDS->osFilename, "https://"))
    8156             :         {
    8157             :             // Identify Format from real file, with bCheckExt=FALSE.
    8158             :             auto poOpenInfo2 = std::make_unique<GDALOpenInfo>(
    8159          64 :                 poDS->osFilename.c_str(), GA_ReadOnly);
    8160          64 :             poDS->eFormat = netCDFIdentifyFormat(poOpenInfo2.get(),
    8161             :                                                  /* bCheckExt = */ false);
    8162          64 :             if (NCDF_FORMAT_NONE == poDS->eFormat ||
    8163          64 :                 NCDF_FORMAT_UNKNOWN == poDS->eFormat)
    8164             :             {
    8165           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8166             :                 // deadlock with GDALDataset own mutex.
    8167           0 :                 delete poDS;
    8168           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    8169           0 :                 return nullptr;
    8170             :             }
    8171             :         }
    8172             :     }
    8173             :     else
    8174             :     {
    8175         448 :         poDS->osFilename = poOpenInfo->pszFilename;
    8176         448 :         bTreatAsSubdataset = false;
    8177         448 :         poDS->eFormat = eTmpFormat;
    8178             :     }
    8179             : 
    8180             : // Try opening the dataset.
    8181             : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
    8182             :     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
    8183             :              poDS->osFilename.c_str());
    8184             : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
    8185             :     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
    8186             : #endif
    8187         515 :     int cdfid = -1;
    8188         515 :     const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
    8189             :                           ? NC_WRITE
    8190             :                           : NC_NOWRITE;
    8191        1030 :     CPLString osFilenameForNCOpen(poDS->osFilename);
    8192             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    8193             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    8194             :     {
    8195             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    8196             :         osFilenameForNCOpen = pszTemp;
    8197             :         CPLFree(pszTemp);
    8198             :     }
    8199             : #endif
    8200         515 :     int status2 = -1;
    8201             : 
    8202             : #ifdef ENABLE_UFFD
    8203         515 :     cpl_uffd_context *pCtx = nullptr;
    8204             : #endif
    8205             : 
    8206         530 :     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
    8207          15 :         poOpenInfo->eAccess == GA_ReadOnly)
    8208             :     {
    8209          15 :         vsi_l_offset nLength = 0;
    8210          15 :         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
    8211          15 :         if (poDS->fpVSIMEM)
    8212             :         {
    8213             :             // We assume that the file will not be modified. If it is, then
    8214             :             // pabyBuffer might become invalid.
    8215             :             GByte *pabyBuffer =
    8216          15 :                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
    8217          15 :             if (pabyBuffer)
    8218             :             {
    8219          15 :                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
    8220             :                                       nMode, static_cast<size_t>(nLength),
    8221             :                                       pabyBuffer, &cdfid);
    8222             :             }
    8223             :         }
    8224             :     }
    8225             :     else
    8226             :     {
    8227             :         const bool bVsiFile =
    8228         500 :             !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
    8229             : #ifdef ENABLE_UFFD
    8230         500 :         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
    8231         500 :         void *pVma = nullptr;
    8232         500 :         uint64_t nVmaSize = 0;
    8233             : 
    8234         500 :         if (bVsiFile)
    8235             :         {
    8236           2 :             if (bReadOnly)
    8237             :             {
    8238           2 :                 if (CPLIsUserFaultMappingSupported())
    8239             :                 {
    8240           2 :                     pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
    8241             :                                                      &nVmaSize);
    8242             :                 }
    8243             :                 else
    8244             :                 {
    8245           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    8246             :                              "Opening a /vsi file with the netCDF driver "
    8247             :                              "requires Linux userfaultfd to be available. "
    8248             :                              "If running from Docker, "
    8249             :                              "--security-opt seccomp=unconfined might be "
    8250             :                              "needed.%s",
    8251           0 :                              ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8252           0 :                                poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8253           0 :                               GDALGetDriverByName("HDF5"))
    8254             :                                  ? " Or you may set the GDAL_SKIP=netCDF "
    8255             :                                    "configuration option to force the use of "
    8256             :                                    "the HDF5 driver."
    8257             :                                  : "");
    8258             :                 }
    8259             :             }
    8260             :             else
    8261             :             {
    8262           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    8263             :                          "Opening a /vsi file with the netCDF driver is only "
    8264             :                          "supported in read-only mode");
    8265             :             }
    8266             :         }
    8267         500 :         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
    8268             :         {
    8269             :             // netCDF code, at least for netCDF 4.7.0, is confused by filenames
    8270             :             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
    8271             :             // final part
    8272           2 :             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
    8273             :                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);
    8274             :         }
    8275             :         else
    8276         498 :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8277             : #else
    8278             :         if (bVsiFile)
    8279             :         {
    8280             :             CPLError(
    8281             :                 CE_Failure, CPLE_AppDefined,
    8282             :                 "Opening a /vsi file with the netCDF driver requires Linux "
    8283             :                 "userfaultfd to be available.%s",
    8284             :                 ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8285             :                   poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8286             :                  GDALGetDriverByName("HDF5"))
    8287             :                     ? " Or you may set the GDAL_SKIP=netCDF "
    8288             :                       "configuration option to force the use of the HDF5 "
    8289             :                       "driver."
    8290             :                     : "");
    8291             :             status2 = NC_EIO;
    8292             :         }
    8293             :         else
    8294             :         {
    8295             :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8296             :         }
    8297             : #endif
    8298             :     }
    8299         515 :     if (status2 != NC_NOERR)
    8300             :     {
    8301             : #ifdef NCDF_DEBUG
    8302             :         CPLDebug("GDAL_netCDF", "error opening");
    8303             : #endif
    8304           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8305             :         // with GDALDataset own mutex.
    8306           0 :         delete poDS;
    8307           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8308           0 :         return nullptr;
    8309             :     }
    8310             : #ifdef NCDF_DEBUG
    8311             :     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
    8312             : #endif
    8313             : 
    8314             : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
    8315             :     // Try to destroy the temporary file right now on Unix
    8316         515 :     if (poDS->bFileToDestroyAtClosing)
    8317             :     {
    8318           3 :         if (VSIUnlink(poDS->osFilename) == 0)
    8319             :         {
    8320           3 :             poDS->bFileToDestroyAtClosing = false;
    8321             :         }
    8322             :     }
    8323             : #endif
    8324             : 
    8325             :     // Is this a real netCDF file?
    8326             :     int ndims;
    8327             :     int ngatts;
    8328             :     int nvars;
    8329             :     int unlimdimid;
    8330         515 :     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
    8331         515 :     if (status != NC_NOERR)
    8332             :     {
    8333           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8334             :         // with GDALDataset own mutex.
    8335           0 :         delete poDS;
    8336           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8337           0 :         return nullptr;
    8338             :     }
    8339             : 
    8340             :     // Get file type from netcdf.
    8341         515 :     int nTmpFormat = NCDF_FORMAT_NONE;
    8342         515 :     status = nc_inq_format(cdfid, &nTmpFormat);
    8343         515 :     if (status != NC_NOERR)
    8344             :     {
    8345           0 :         NCDF_ERR(status);
    8346             :     }
    8347             :     else
    8348             :     {
    8349         515 :         CPLDebug("GDAL_netCDF",
    8350             :                  "driver detected file type=%d, libnetcdf detected type=%d",
    8351         515 :                  poDS->eFormat, nTmpFormat);
    8352         515 :         if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
    8353             :         {
    8354             :             // Warn if file detection conflicts with that from libnetcdf
    8355             :             // except for NC4C, which we have no way of detecting initially.
    8356          26 :             if (nTmpFormat != NCDF_FORMAT_NC4C &&
    8357          13 :                 !STARTS_WITH(poDS->osFilename, "http://") &&
    8358           0 :                 !STARTS_WITH(poDS->osFilename, "https://"))
    8359             :             {
    8360           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    8361             :                          "NetCDF driver detected file type=%d, but libnetcdf "
    8362             :                          "detected type=%d",
    8363           0 :                          poDS->eFormat, nTmpFormat);
    8364             :             }
    8365          13 :             CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
    8366          13 :                      nTmpFormat, poDS->eFormat);
    8367          13 :             poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
    8368             :         }
    8369             :     }
    8370             : 
    8371             :     // Does the request variable exist?
    8372         515 :     if (bTreatAsSubdataset)
    8373             :     {
    8374             :         int dummy;
    8375          64 :         if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
    8376          64 :                                &dummy) != CE_None)
    8377             :         {
    8378           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    8379             :                      "%s is a netCDF file, but %s is not a variable.",
    8380             :                      poOpenInfo->pszFilename, osSubdatasetName.c_str());
    8381             : 
    8382           0 :             GDAL_nc_close(cdfid);
    8383             : #ifdef ENABLE_UFFD
    8384           0 :             NETCDF_UFFD_UNMAP(pCtx);
    8385             : #endif
    8386           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8387             :             // deadlock with GDALDataset own mutex.
    8388           0 :             delete poDS;
    8389           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8390           0 :             return nullptr;
    8391             :         }
    8392             :     }
    8393             : 
    8394             :     // Figure out whether or not the listed dataset has support for simple
    8395             :     // geometries (CF-1.8)
    8396         515 :     poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
    8397         515 :     bool bHasSimpleGeometries = false;  // but not necessarily valid
    8398         515 :     if (poDS->nCFVersion >= 1.8)
    8399             :     {
    8400          75 :         bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
    8401          75 :         if (bHasSimpleGeometries)
    8402             :         {
    8403          67 :             poDS->bSGSupport = true;
    8404          67 :             poDS->vcdf.enableFullVirtualMode();
    8405             :         }
    8406             :     }
    8407             : 
    8408        1030 :     std::string osConventions;
    8409         515 :     if (NCDFGetAttr(cdfid, NC_GLOBAL, "Conventions", osConventions) != CE_None)
    8410             :     {
    8411          59 :         CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
    8412             :         // Note that 'Conventions' is always capital 'C' in CF spec.
    8413             :     }
    8414             : 
    8415             :     // Create band information objects.
    8416         515 :     CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
    8417             : 
    8418             :     // Create a corresponding GDALDataset.
    8419             :     // Create Netcdf Subdataset if filename as NETCDF tag.
    8420         515 :     poDS->cdfid = cdfid;
    8421             : #ifdef ENABLE_UFFD
    8422         515 :     poDS->pCtx = pCtx;
    8423             : #endif
    8424         515 :     poDS->eAccess = poOpenInfo->eAccess;
    8425         515 :     poDS->bDefineMode = false;
    8426             : 
    8427         515 :     poDS->ReadAttributes(cdfid, NC_GLOBAL);
    8428             : 
    8429             :     // Identify coordinate and boundary variables that we should
    8430             :     // ignore as Raster Bands.
    8431        1030 :     CPLStringList aosIgnoreVars;
    8432         515 :     NCDFGetCoordAndBoundVarFullNames(cdfid, aosIgnoreVars);
    8433             :     // Filter variables to keep only valid 2+D raster bands and vector fields.
    8434         515 :     int nRasterVars = 0;
    8435         515 :     int nIgnoredVars = 0;
    8436         515 :     int nGroupID = -1;
    8437         515 :     int nVarID = -1;
    8438             : 
    8439             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
    8440        1030 :         oMap2DDimsToGroupAndVar;
    8441        1188 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8442         158 :         STARTS_WITH(
    8443             :             poDS->aosMetadata.FetchNameValueDef("NC_GLOBAL#mission_name", ""),
    8444           1 :             "Sentinel 3") &&
    8445           1 :         EQUAL(poDS->aosMetadata.FetchNameValueDef(
    8446             :                   "NC_GLOBAL#altimeter_sensor_name", ""),
    8447         673 :               "SRAL") &&
    8448           1 :         EQUAL(poDS->aosMetadata.FetchNameValueDef(
    8449             :                   "NC_GLOBAL#radiometer_sensor_name", ""),
    8450             :               "MWR"))
    8451             :     {
    8452           1 :         if (poDS->eAccess == GA_Update)
    8453             :         {
    8454           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8455             :             // deadlock with GDALDataset own mutex.
    8456           0 :             delete poDS;
    8457           0 :             return nullptr;
    8458             :         }
    8459           1 :         poDS->ProcessSentinel3_SRAL_MWR();
    8460             :     }
    8461             :     else
    8462             :     {
    8463         514 :         poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
    8464         671 :                          (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8465         157 :                              !bHasSimpleGeometries,
    8466             :                          aosIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
    8467             :                          &nIgnoredVars, oMap2DDimsToGroupAndVar);
    8468             :     }
    8469             : 
    8470         515 :     const bool bListAllArrays = CPLTestBool(
    8471         515 :         CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    8472             : 
    8473             :     // Case where there is no raster variable
    8474         515 :     if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
    8475             :     {
    8476         119 :         poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
    8477         119 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8478             :         // with GDALDataset own mutex.
    8479         119 :         poDS->TryLoadXML();
    8480             :         // If the dataset has been opened in raster mode only, exit
    8481         119 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    8482           9 :             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    8483             :         {
    8484           4 :             delete poDS;
    8485           4 :             poDS = nullptr;
    8486             :         }
    8487             :         // Otherwise if the dataset is opened in vector mode, that there is
    8488             :         // no vector layer and we are in read-only, exit too.
    8489         115 :         else if (poDS->GetLayerCount() == 0 &&
    8490         123 :                  (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8491           8 :                  poOpenInfo->eAccess == GA_ReadOnly)
    8492             :         {
    8493           8 :             delete poDS;
    8494           8 :             poDS = nullptr;
    8495             :         }
    8496         119 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8497         119 :         return poDS;
    8498             :     }
    8499             : 
    8500             :     // We have more than one variable with 2 dimensions in the
    8501             :     // file, then treat this as a subdataset container dataset.
    8502         396 :     bool bSeveralVariablesAsBands = false;
    8503         396 :     if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
    8504             :     {
    8505          30 :         if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
    8506          36 :                          false) &&
    8507           6 :             oMap2DDimsToGroupAndVar.size() == 1)
    8508             :         {
    8509           6 :             std::tie(nGroupID, nVarID) =
    8510          12 :                 oMap2DDimsToGroupAndVar.begin()->second.front();
    8511           6 :             bSeveralVariablesAsBands = true;
    8512             :         }
    8513             :         else
    8514             :         {
    8515          24 :             poDS->CreateSubDatasetList(cdfid);
    8516          24 :             poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
    8517          24 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8518             :             // deadlock with GDALDataset own mutex.
    8519          24 :             poDS->TryLoadXML();
    8520          24 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8521          24 :             return poDS;
    8522             :         }
    8523             :     }
    8524             : 
    8525             :     // If we are not treating things as a subdataset, then capture
    8526             :     // the name of the single available variable as the subdataset.
    8527         372 :     if (!bTreatAsSubdataset)
    8528             :     {
    8529         308 :         NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, osSubdatasetName));
    8530             :     }
    8531             : 
    8532             :     // We have ignored at least one variable, so we should report them
    8533             :     // as subdatasets for reference.
    8534         372 :     if (nIgnoredVars > 0 && !bTreatAsSubdataset)
    8535             :     {
    8536          29 :         CPLDebug("GDAL_netCDF",
    8537             :                  "As %d variables were ignored, creating subdataset list "
    8538             :                  "for reference. Variable #%d [%s] is the main variable",
    8539             :                  nIgnoredVars, nVarID, osSubdatasetName.c_str());
    8540          29 :         poDS->CreateSubDatasetList(cdfid);
    8541             :     }
    8542             : 
    8543             :     // Open the NETCDF subdataset NETCDF:"filename":subdataset.
    8544         372 :     int var = -1;
    8545         372 :     NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
    8546             :     // Now we can forget the root cdfid and only use the selected group.
    8547         372 :     cdfid = nGroupID;
    8548         372 :     int nd = 0;
    8549         372 :     nc_inq_varndims(cdfid, var, &nd);
    8550             : 
    8551         372 :     poDS->m_anDimIds.resize(nd);
    8552             : 
    8553             :     // X, Y, Z position in array
    8554         744 :     std::vector<int> anBandDimPos(nd);
    8555             : 
    8556         372 :     nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
    8557             : 
    8558             :     // Check if somebody tried to pass a variable with less than 1D.
    8559         372 :     if (nd < 1)
    8560             :     {
    8561           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8562             :                  "Variable has %d dimension(s) - not supported.", nd);
    8563           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8564             :         // with GDALDataset own mutex.
    8565           0 :         delete poDS;
    8566           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8567           0 :         return nullptr;
    8568             :     }
    8569             : 
    8570             :     // CF-1 Convention
    8571             :     //
    8572             :     // Dimensions to appear in the relative order T, then Z, then Y,
    8573             :     // then X  to the file. All other dimensions should, whenever
    8574             :     // possible, be placed to the left of the spatiotemporal
    8575             :     // dimensions.
    8576             : 
    8577             :     // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
    8578             :     // Ideally we should detect for other ordering and act accordingly
    8579             :     // Only done if file has Conventions=CF-* and only prints warning
    8580             :     // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
    8581             :     // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
    8582             :     const bool bCheckDims =
    8583         744 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
    8584         372 :         STARTS_WITH_CI(osConventions.c_str(), "CF");
    8585             : 
    8586         372 :     bool bYXBandOrder = false;
    8587         372 :     if (nd == 3)
    8588             :     {
    8589             :         // If there's a coordinates attributes, and the variable it points to
    8590             :         // are 2D variables indexed by the same first and second dimension than
    8591             :         // our variable of interest, then it is Y,X,Band order.
    8592          46 :         char *pszCoordinates = nullptr;
    8593          46 :         if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
    8594          63 :                 CE_None &&
    8595          17 :             pszCoordinates)
    8596             :         {
    8597             :             const CPLStringList aosCoordinates(
    8598          34 :                 NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    8599          17 :             if (aosCoordinates.size() == 2)
    8600             :             {
    8601             :                 // Test that each variable is longitude/latitude.
    8602          13 :                 for (int i = 0; i < aosCoordinates.size(); i++)
    8603             :                 {
    8604          13 :                     if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
    8605           4 :                         NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
    8606             :                     {
    8607           9 :                         int nOtherGroupId = -1;
    8608           9 :                         int nOtherVarId = -1;
    8609           9 :                         if (NCDFResolveVar(cdfid, aosCoordinates[i],
    8610             :                                            &nOtherGroupId,
    8611           9 :                                            &nOtherVarId) == CE_None)
    8612             :                         {
    8613           9 :                             int coordDimCount = 0;
    8614           9 :                             nc_inq_varndims(nOtherGroupId, nOtherVarId,
    8615             :                                             &coordDimCount);
    8616           9 :                             if (coordDimCount == 2)
    8617             :                             {
    8618           3 :                                 int coordDimIds[2] = {0, 0};
    8619           3 :                                 nc_inq_vardimid(nOtherGroupId, nOtherVarId,
    8620             :                                                 coordDimIds);
    8621           4 :                                 if (coordDimIds[0] == poDS->m_anDimIds[0] &&
    8622           1 :                                     coordDimIds[1] == poDS->m_anDimIds[1])
    8623             :                                 {
    8624           1 :                                     bYXBandOrder = true;
    8625           1 :                                     break;
    8626             :                                 }
    8627             :                             }
    8628             :                         }
    8629             :                     }
    8630             :                 }
    8631             :             }
    8632             :         }
    8633          46 :         CPLFree(pszCoordinates);
    8634             : 
    8635          46 :         if (!bYXBandOrder)
    8636             :         {
    8637          45 :             char szDim0Name[NC_MAX_NAME + 1] = {};
    8638          45 :             char szDim1Name[NC_MAX_NAME + 1] = {};
    8639          45 :             status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
    8640          45 :             NCDF_ERR(status);
    8641          45 :             status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
    8642          45 :             NCDF_ERR(status);
    8643             : 
    8644          45 :             if (strcmp(szDim0Name, "number_of_lines") == 0 &&
    8645           1 :                 strcmp(szDim1Name, "pixels_per_line") == 0)
    8646             :             {
    8647             :                 // Like in PACE OCI products
    8648           1 :                 bYXBandOrder = true;
    8649             :             }
    8650             :             else
    8651             :             {
    8652             :                 // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
    8653             :                 // dimension order is downtrack, crosstrack, bands
    8654          44 :                 char szDim2Name[NC_MAX_NAME + 1] = {};
    8655          44 :                 status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
    8656          44 :                 NCDF_ERR(status);
    8657          86 :                 bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
    8658          42 :                                strcmp(szDim2Name, "band") == 0;
    8659             :             }
    8660             :         }
    8661             :     }
    8662             : 
    8663         372 :     if (nd >= 2 && bCheckDims && !bYXBandOrder)
    8664             :     {
    8665         285 :         char szDimName1[NC_MAX_NAME + 1] = {};
    8666         285 :         char szDimName2[NC_MAX_NAME + 1] = {};
    8667         285 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
    8668         285 :         NCDF_ERR(status);
    8669         285 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
    8670         285 :         NCDF_ERR(status);
    8671         461 :         if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
    8672         176 :             NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
    8673             :         {
    8674           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8675             :                      "dimension #%d (%s) is not a Longitude/X dimension.",
    8676             :                      nd - 1, szDimName1);
    8677             :         }
    8678         461 :         if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
    8679         176 :             NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
    8680             :         {
    8681           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8682             :                      "dimension #%d (%s) is not a Latitude/Y dimension.",
    8683             :                      nd - 2, szDimName2);
    8684             :         }
    8685         285 :         if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
    8686         287 :              NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
    8687           2 :             (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
    8688           0 :              NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
    8689             :         {
    8690           2 :             poDS->bSwitchedXY = true;
    8691             :         }
    8692         285 :         if (nd >= 3)
    8693             :         {
    8694          52 :             char szDimName3[NC_MAX_NAME + 1] = {};
    8695             :             status =
    8696          52 :                 nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
    8697          52 :             NCDF_ERR(status);
    8698          52 :             if (nd >= 4)
    8699             :             {
    8700          13 :                 char szDimName4[NC_MAX_NAME + 1] = {};
    8701             :                 status =
    8702          13 :                     nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
    8703          13 :                 NCDF_ERR(status);
    8704          13 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
    8705             :                 {
    8706           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8707             :                              "dimension #%d (%s) is not a Vertical dimension.",
    8708             :                              nd - 3, szDimName3);
    8709             :                 }
    8710          13 :                 if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
    8711             :                 {
    8712           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8713             :                              "dimension #%d (%s) is not a Time dimension.",
    8714             :                              nd - 4, szDimName4);
    8715             :                 }
    8716             :             }
    8717             :             else
    8718             :             {
    8719          75 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
    8720          36 :                     NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
    8721             :                 {
    8722           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8723             :                              "dimension #%d (%s) is not a "
    8724             :                              "Time or Vertical dimension.",
    8725             :                              nd - 3, szDimName3);
    8726             :                 }
    8727             :             }
    8728             :         }
    8729             :     }
    8730             : 
    8731             :     // Get X dimensions information.
    8732             :     size_t xdim;
    8733         372 :     poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
    8734         372 :     nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
    8735             : 
    8736             :     // Get Y dimension information.
    8737             :     size_t ydim;
    8738         372 :     if (nd >= 2)
    8739             :     {
    8740         368 :         poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
    8741         368 :         nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
    8742             :     }
    8743             :     else
    8744             :     {
    8745           4 :         poDS->nYDimID = -1;
    8746           4 :         ydim = 1;
    8747             :     }
    8748             : 
    8749         372 :     if (xdim > INT_MAX || ydim > INT_MAX)
    8750             :     {
    8751           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8752             :                  "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
    8753             :                  static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
    8754           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8755             :         // with GDALDataset own mutex.
    8756           0 :         delete poDS;
    8757           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8758           0 :         return nullptr;
    8759             :     }
    8760             : 
    8761         372 :     poDS->nRasterXSize = static_cast<int>(xdim);
    8762         372 :     poDS->nRasterYSize = static_cast<int>(ydim);
    8763             : 
    8764         372 :     unsigned int k = 0;
    8765        1193 :     for (int j = 0; j < nd; j++)
    8766             :     {
    8767         821 :         if (poDS->m_anDimIds[j] == poDS->nXDimID)
    8768             :         {
    8769         372 :             anBandDimPos[0] = j;  // Save Position of XDim
    8770         372 :             k++;
    8771             :         }
    8772         821 :         if (poDS->m_anDimIds[j] == poDS->nYDimID)
    8773             :         {
    8774         368 :             anBandDimPos[1] = j;  // Save Position of YDim
    8775         368 :             k++;
    8776             :         }
    8777             :     }
    8778             :     // X and Y Dimension Ids were not found!
    8779         372 :     if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
    8780             :     {
    8781           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8782             :         // with GDALDataset own mutex.
    8783           0 :         delete poDS;
    8784           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8785           0 :         return nullptr;
    8786             :     }
    8787             : 
    8788             :     // Read Metadata for this variable.
    8789             : 
    8790             :     // Should disable as is also done at band level, except driver needs the
    8791             :     // variables as metadata (e.g. projection).
    8792         372 :     poDS->ReadAttributes(cdfid, var);
    8793             : 
    8794             :     // Read Metadata for each dimension.
    8795         372 :     int *panDimIds = nullptr;
    8796         372 :     NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
    8797             :     // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
    8798             :     // in NetCDF-3 because we see only the dimensions of the selected group
    8799             :     // and its parents.
    8800             :     // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
    8801             :     // [0..max(panDimIds)], but they are not all useful so we fill names
    8802             :     // of useless dims with empty string.
    8803         372 :     if (panDimIds)
    8804             :     {
    8805         372 :         const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
    8806         372 :         std::set<int> oSetExistingDimIds;
    8807        1233 :         for (int i = 0; i < ndims; i++)
    8808             :         {
    8809         861 :             oSetExistingDimIds.insert(panDimIds[i]);
    8810             :         }
    8811         372 :         std::set<int> oSetDimIdsUsedByVar;
    8812        1193 :         for (int i = 0; i < nd; i++)
    8813             :         {
    8814         821 :             oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
    8815             :         }
    8816        1235 :         for (int j = 0; j <= nMaxDimId; j++)
    8817             :         {
    8818             :             // Is j dim used?
    8819         863 :             if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
    8820             :             {
    8821             :                 // Useful dim.
    8822         861 :                 char szTemp[NC_MAX_NAME + 1] = {};
    8823         861 :                 status = nc_inq_dimname(cdfid, j, szTemp);
    8824         861 :                 if (status != NC_NOERR)
    8825             :                 {
    8826           0 :                     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8827             :                     // deadlock with GDALDataset own
    8828             :                     // mutex.
    8829           0 :                     delete poDS;
    8830           0 :                     CPLAcquireMutex(hNCMutex, 1000.0);
    8831           0 :                     return nullptr;
    8832             :                 }
    8833         861 :                 poDS->papszDimName.AddString(szTemp);
    8834             : 
    8835         861 :                 if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
    8836             :                 {
    8837         821 :                     int nDimGroupId = -1;
    8838         821 :                     int nDimVarId = -1;
    8839         821 :                     if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
    8840         821 :                                        &nDimGroupId, &nDimVarId) == CE_None)
    8841             :                     {
    8842         593 :                         poDS->ReadAttributes(nDimGroupId, nDimVarId);
    8843             :                     }
    8844             :                 }
    8845             :             }
    8846             :             else
    8847             :             {
    8848             :                 // Useless dim.
    8849           2 :                 poDS->papszDimName.AddString("");
    8850             :             }
    8851             :         }
    8852         372 :         CPLFree(panDimIds);
    8853             :     }
    8854             : 
    8855             :     // Set projection info.
    8856         744 :     std::vector<std::string> aosRemovedMDItems;
    8857         372 :     if (nd > 1)
    8858             :     {
    8859         368 :         poDS->SetProjectionFromVar(cdfid, var,
    8860             :                                    /*bReadSRSOnly=*/false,
    8861             :                                    /* pszGivenGM = */ nullptr,
    8862             :                                    /* returnProjStr = */ nullptr,
    8863             :                                    /* sg = */ nullptr, &aosRemovedMDItems);
    8864             :     }
    8865             : 
    8866             :     // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
    8867         372 :     const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
    8868         372 :     if (pszValue)
    8869             :     {
    8870          24 :         poDS->bBottomUp = CPLTestBool(pszValue);
    8871          24 :         CPLDebug("GDAL_netCDF",
    8872             :                  "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
    8873          24 :                  static_cast<int>(poDS->bBottomUp), pszValue);
    8874             :     }
    8875             : 
    8876             :     // Save non-spatial dimension info.
    8877             : 
    8878         372 :     int *panBandZLev = nullptr;
    8879         372 :     int nDim = (nd >= 2) ? 2 : 1;
    8880             :     size_t lev_count;
    8881         372 :     size_t nTotLevCount = 1;
    8882         372 :     nc_type nType = NC_NAT;
    8883             : 
    8884         372 :     if (nd > 2)
    8885             :     {
    8886          62 :         nDim = 2;
    8887          62 :         panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
    8888             : 
    8889         124 :         CPLString osExtraDimNames = "{";
    8890             : 
    8891          62 :         char szDimName[NC_MAX_NAME + 1] = {};
    8892             : 
    8893          62 :         bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
    8894         267 :         for (int j = 0; j < nd; j++)
    8895             :         {
    8896         348 :             if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
    8897         143 :                 (poDS->m_anDimIds[j] != poDS->nYDimID))
    8898             :             {
    8899          81 :                 nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
    8900          81 :                 nTotLevCount *= lev_count;
    8901          81 :                 panBandZLev[nDim - 2] = static_cast<int>(lev_count);
    8902          81 :                 anBandDimPos[nDim] = j;  // Save Position of ZDim
    8903             :                 // Save non-spatial dimension names.
    8904          81 :                 if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
    8905             :                     NC_NOERR)
    8906             :                 {
    8907          81 :                     osExtraDimNames += szDimName;
    8908          81 :                     if (j < nd - 3)
    8909             :                     {
    8910          19 :                         osExtraDimNames += ",";
    8911             :                     }
    8912             : 
    8913          81 :                     int nIdxGroupID = -1;
    8914          81 :                     int nIdxVarID = Get1DVariableIndexedByDimension(
    8915          81 :                         cdfid, poDS->m_anDimIds[j], szDimName, true,
    8916          81 :                         &nIdxGroupID);
    8917          81 :                     poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
    8918          81 :                     poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
    8919             : 
    8920          81 :                     if (nIdxVarID >= 0)
    8921             :                     {
    8922          72 :                         nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
    8923             :                         char szExtraDimDef[NC_MAX_NAME + 1];
    8924          72 :                         snprintf(szExtraDimDef, sizeof(szExtraDimDef),
    8925             :                                  "{%ld,%d}", (long)lev_count, nType);
    8926             :                         char szTemp[NC_MAX_NAME + 32 + 1];
    8927          72 :                         snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    8928             :                                  szDimName);
    8929          72 :                         poDS->aosMetadata.SetNameValue(szTemp, szExtraDimDef);
    8930             : 
    8931             :                         // Retrieving data for unlimited dimensions might be
    8932             :                         // costly on network storage, so don't do it.
    8933             :                         // Each band will capture the value along the extra
    8934             :                         // dimension in its NETCDF_DIM_xxxx band metadata item
    8935             :                         // Addresses use case of
    8936             :                         // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
    8937             :                         const bool bIsLocal =
    8938          72 :                             VSIIsLocal(osFilenameForNCOpen.c_str());
    8939             :                         bool bListDimValues =
    8940          73 :                             bIsLocal || lev_count == 1 ||
    8941           1 :                             !NCDFIsUnlimitedDim(poDS->eFormat ==
    8942             :                                                     NCDF_FORMAT_NC4,
    8943           1 :                                                 cdfid, poDS->m_anDimIds[j]);
    8944             :                         const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
    8945          72 :                             CPLGetConfigOption(
    8946             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
    8947          72 :                         if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
    8948             :                         {
    8949           2 :                             bListDimValues = CPLTestBool(
    8950             :                                 pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
    8951             :                         }
    8952          70 :                         else if (!bListDimValues && !bIsLocal &&
    8953           1 :                                  !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
    8954             :                         {
    8955           1 :                             bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
    8956           1 :                             CPLDebug(
    8957             :                                 "GDAL_netCDF",
    8958             :                                 "Listing extra dimension values is skipped "
    8959             :                                 "because this dataset is hosted on a network "
    8960             :                                 "file system, and such an operation could be "
    8961             :                                 "slow. If you still want to proceed, set the "
    8962             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
    8963             :                                 "configuration option to YES");
    8964             :                         }
    8965          72 :                         if (bListDimValues)
    8966             :                         {
    8967          70 :                             char *pszTemp = nullptr;
    8968          70 :                             if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
    8969          70 :                                              &pszTemp) == CE_None)
    8970             :                             {
    8971          70 :                                 snprintf(szTemp, sizeof(szTemp),
    8972             :                                          "NETCDF_DIM_%s_VALUES", szDimName);
    8973          70 :                                 poDS->aosMetadata.SetNameValue(szTemp, pszTemp);
    8974          70 :                                 CPLFree(pszTemp);
    8975             :                             }
    8976             :                         }
    8977             :                     }
    8978             :                 }
    8979             :                 else
    8980             :                 {
    8981           0 :                     poDS->m_anExtraDimGroupIds.push_back(-1);
    8982           0 :                     poDS->m_anExtraDimVarIds.push_back(-1);
    8983             :                 }
    8984             : 
    8985          81 :                 nDim++;
    8986             :             }
    8987             :         }
    8988          62 :         osExtraDimNames += "}";
    8989          62 :         poDS->aosMetadata.SetNameValue("NETCDF_DIM_EXTRA", osExtraDimNames);
    8990             :     }
    8991             : 
    8992             :     // Store Metadata.
    8993         382 :     for (const auto &osStr : aosRemovedMDItems)
    8994          10 :         poDS->aosMetadata.SetNameValue(osStr.c_str(), nullptr);
    8995             : 
    8996         372 :     poDS->GDALPamDataset::SetMetadata(poDS->aosMetadata);
    8997             : 
    8998             :     // Create bands.
    8999             : 
    9000             :     // Arbitrary threshold.
    9001             :     int nMaxBandCount =
    9002         372 :         atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
    9003         372 :     if (nMaxBandCount <= 0)
    9004           0 :         nMaxBandCount = 32768;
    9005         372 :     if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
    9006             :     {
    9007           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9008             :                  "Limiting number of bands to %d instead of %u", nMaxBandCount,
    9009             :                  static_cast<unsigned int>(nTotLevCount));
    9010           0 :         nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
    9011             :     }
    9012         372 :     if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
    9013             :     {
    9014           0 :         poDS->nRasterXSize = 0;
    9015           0 :         poDS->nRasterYSize = 0;
    9016           0 :         nTotLevCount = 0;
    9017           0 :         if (poDS->GetLayerCount() == 0)
    9018             :         {
    9019           0 :             CPLFree(panBandZLev);
    9020           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9021             :             // deadlock with GDALDataset own mutex.
    9022           0 :             delete poDS;
    9023           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9024           0 :             return nullptr;
    9025             :         }
    9026             :     }
    9027         372 :     if (bSeveralVariablesAsBands)
    9028             :     {
    9029           6 :         const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
    9030          24 :         for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
    9031             :              ++iBand)
    9032             :         {
    9033          18 :             int bandVarGroupId = listVariables[iBand].first;
    9034          18 :             int bandVarId = listVariables[iBand].second;
    9035             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    9036           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
    9037          18 :                 bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
    9038          18 :             poDS->SetBand(iBand + 1, poBand);
    9039             :         }
    9040             :     }
    9041             :     else
    9042             :     {
    9043         846 :         for (unsigned int lev = 0; lev < nTotLevCount; lev++)
    9044             :         {
    9045             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    9046           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
    9047         480 :                 lev, panBandZLev, anBandDimPos.data(), lev + 1);
    9048         480 :             poDS->SetBand(lev + 1, poBand);
    9049             :         }
    9050             :     }
    9051             : 
    9052         372 :     if (panBandZLev)
    9053          62 :         CPLFree(panBandZLev);
    9054             :     // Handle angular geographic coordinates here
    9055             : 
    9056             :     // Initialize any PAM information.
    9057         372 :     if (bTreatAsSubdataset)
    9058             :     {
    9059          64 :         poDS->SetPhysicalFilename(poDS->osFilename);
    9060          64 :         poDS->SetSubdatasetName(osSubdatasetName);
    9061             :     }
    9062             : 
    9063         372 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9064             :     // GDALDataset own mutex.
    9065         372 :     poDS->TryLoadXML();
    9066             : 
    9067         372 :     if (bTreatAsSubdataset)
    9068          64 :         poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
    9069             :     else
    9070         308 :         poDS->oOvManager.Initialize(poDS, poDS->osFilename);
    9071             : 
    9072         372 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9073             : 
    9074         372 :     return poDS;
    9075             : }
    9076             : 
    9077             : /************************************************************************/
    9078             : /*                            CopyMetadata()                            */
    9079             : /*                                                                      */
    9080             : /*      Create a copy of metadata for NC_GLOBAL or a variable           */
    9081             : /************************************************************************/
    9082             : 
    9083         169 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
    9084             :                          GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
    9085             :                          const char *pszPrefix)
    9086             : {
    9087             :     // Remove the following band meta but set them later from band data.
    9088         169 :     const char *const papszIgnoreBand[] = {
    9089             :         CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    9090             :         NCDF_FillValue, "coordinates",   nullptr};
    9091         169 :     const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
    9092             : 
    9093         169 :     CSLConstList papszMetadata = nullptr;
    9094         169 :     if (poSrcDS)
    9095             :     {
    9096          72 :         papszMetadata = poSrcDS->GetMetadata();
    9097             :     }
    9098          97 :     else if (poSrcBand)
    9099             :     {
    9100          97 :         papszMetadata = poSrcBand->GetMetadata();
    9101             :     }
    9102             : 
    9103         655 :     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
    9104             :     {
    9105             : #ifdef NCDF_DEBUG
    9106             :         CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
    9107             : #endif
    9108             : 
    9109         486 :         CPLString osMetaName(pszKey);
    9110             : 
    9111             :         // Check for items that match pszPrefix if applicable.
    9112         486 :         if (pszPrefix && !EQUAL(pszPrefix, ""))
    9113             :         {
    9114             :             // Remove prefix.
    9115         115 :             if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
    9116             :             {
    9117          17 :                 osMetaName = osMetaName.substr(strlen(pszPrefix));
    9118             :             }
    9119             :             // Only copy items that match prefix.
    9120             :             else
    9121             :             {
    9122          98 :                 continue;
    9123             :             }
    9124             :         }
    9125             : 
    9126             :         // Fix various issues with metadata translation.
    9127         388 :         if (CDFVarID == NC_GLOBAL)
    9128             :         {
    9129             :             // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
    9130         493 :             if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
    9131         244 :                 (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
    9132          21 :                 continue;
    9133             :             // Remove NC_GLOBAL prefix for netcdf global Metadata.
    9134         228 :             else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
    9135             :             {
    9136          33 :                 osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
    9137             :             }
    9138             :             // GDAL Metadata renamed as GDAL-[meta].
    9139         195 :             else if (strstr(osMetaName, "#") == nullptr)
    9140             :             {
    9141          22 :                 osMetaName = "GDAL_" + osMetaName;
    9142             :             }
    9143             :             // Keep time, lev and depth information for safe-keeping.
    9144             :             // Time and vertical coordinate handling need improvements.
    9145             :             /*
    9146             :             else if( STARTS_WITH(szMetaName, "time#") )
    9147             :             {
    9148             :                 szMetaName[4] = '-';
    9149             :             }
    9150             :             else if( STARTS_WITH(szMetaName, "lev#") )
    9151             :             {
    9152             :                 szMetaName[3] = '-';
    9153             :             }
    9154             :             else if( STARTS_WITH(szMetaName, "depth#") )
    9155             :             {
    9156             :                 szMetaName[5] = '-';
    9157             :             }
    9158             :             */
    9159             :             // Only copy data without # (previously all data was copied).
    9160         228 :             if (strstr(osMetaName, "#") != nullptr)
    9161         173 :                 continue;
    9162             :             // netCDF attributes do not like the '#' character.
    9163             :             // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
    9164             :             //     if( szMetaName[h] == '#') szMetaName[h] = '-';
    9165             :             // }
    9166             :         }
    9167             :         else
    9168             :         {
    9169             :             // Do not copy varname, stats, NETCDF_DIM_*, nodata
    9170             :             // and items in papszIgnoreBand.
    9171         139 :             if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
    9172         107 :                 STARTS_WITH(osMetaName, "STATISTICS_") ||
    9173         107 :                 STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
    9174          74 :                 STARTS_WITH(osMetaName, "missing_value") ||
    9175         293 :                 STARTS_WITH(osMetaName, "_FillValue") ||
    9176          47 :                 CSLFindString(papszIgnoreBand, osMetaName) != -1)
    9177          97 :                 continue;
    9178             :         }
    9179             : 
    9180             : #ifdef NCDF_DEBUG
    9181             :         CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
    9182             :                  pszValue);
    9183             : #endif
    9184          97 :         if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
    9185             :         {
    9186           0 :             CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
    9187             :                      nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
    9188             :         }
    9189             :     }
    9190             : 
    9191             :     // Set add_offset and scale_factor here if present.
    9192         169 :     if (poSrcBand && poDstBand)
    9193             :     {
    9194             : 
    9195          97 :         int bGotAddOffset = FALSE;
    9196          97 :         const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
    9197          97 :         int bGotScale = FALSE;
    9198          97 :         const double dfScale = poSrcBand->GetScale(&bGotScale);
    9199             : 
    9200          97 :         if (bGotAddOffset && dfAddOffset != 0.0)
    9201           1 :             poDstBand->SetOffset(dfAddOffset);
    9202          97 :         if (bGotScale && dfScale != 1.0)
    9203           1 :             poDstBand->SetScale(dfScale);
    9204             :     }
    9205         169 : }
    9206             : 
    9207             : /************************************************************************/
    9208             : /*                            CreateLL()                                */
    9209             : /*                                                                      */
    9210             : /*      Shared functionality between netCDFDataset::Create() and        */
    9211             : /*      netCDF::CreateCopy() for creating netcdf file based on a set of */
    9212             : /*      options and a configuration.                                    */
    9213             : /************************************************************************/
    9214             : 
    9215         205 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
    9216             :                                        int nYSize, int nBandsIn,
    9217             :                                        CSLConstList papszOptions)
    9218             : {
    9219         205 :     if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
    9220         132 :           (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
    9221             :     {
    9222           1 :         return nullptr;
    9223             :     }
    9224             : 
    9225         204 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9226             :     // GDALDataset own mutex.
    9227         204 :     netCDFDataset *poDS = new netCDFDataset();
    9228         204 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9229             : 
    9230         204 :     poDS->nRasterXSize = nXSize;
    9231         204 :     poDS->nRasterYSize = nYSize;
    9232         204 :     poDS->eAccess = GA_Update;
    9233         204 :     poDS->osFilename = pszFilename;
    9234             : 
    9235             :     // From gtiff driver, is this ok?
    9236             :     /*
    9237             :     poDS->nBlockXSize = nXSize;
    9238             :     poDS->nBlockYSize = 1;
    9239             :     poDS->nBlocksPerBand =
    9240             :         DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
    9241             :         * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
    9242             :         */
    9243             : 
    9244             :     // process options.
    9245         204 :     poDS->aosCreationOptions = CSLDuplicate(papszOptions);
    9246         204 :     poDS->ProcessCreationOptions();
    9247             : 
    9248         204 :     if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
    9249             :     {
    9250             :         VSIStatBuf sStat;
    9251           3 :         if (VSIStat(pszFilename, &sStat) == 0)
    9252             :         {
    9253           0 :             if (!VSI_ISDIR(sStat.st_mode))
    9254             :             {
    9255           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    9256             :                          "%s is an existing file, but not a directory",
    9257             :                          pszFilename);
    9258           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9259             :                 // deadlock with GDALDataset own
    9260             :                 // mutex.
    9261           0 :                 delete poDS;
    9262           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    9263           0 :                 return nullptr;
    9264             :             }
    9265             :         }
    9266           3 :         else if (VSIMkdir(pszFilename, 0755) != 0)
    9267             :         {
    9268           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
    9269             :                      pszFilename);
    9270           1 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9271             :             // deadlock with GDALDataset own mutex.
    9272           1 :             delete poDS;
    9273           1 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9274           1 :             return nullptr;
    9275             :         }
    9276             : 
    9277           2 :         return poDS;
    9278             :     }
    9279             :     // Create the dataset.
    9280         402 :     CPLString osFilenameForNCCreate(pszFilename);
    9281             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    9282             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    9283             :     {
    9284             :         char *pszTemp =
    9285             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    9286             :         osFilenameForNCCreate = pszTemp;
    9287             :         CPLFree(pszTemp);
    9288             :     }
    9289             : #endif
    9290             : 
    9291             : #if defined(_WIN32)
    9292             :     {
    9293             :         // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
    9294             :         // crashes
    9295             :         VSIStatBuf sStat;
    9296             :         const std::string osDirname =
    9297             :             CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
    9298             :         if (VSIStat(osDirname.c_str(), &sStat) != 0)
    9299             :         {
    9300             :             CPLError(CE_Failure, CPLE_OpenFailed,
    9301             :                      "Unable to create netCDF file %s: non existing output "
    9302             :                      "directory",
    9303             :                      pszFilename);
    9304             :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9305             :             // deadlock with GDALDataset own mutex.
    9306             :             delete poDS;
    9307             :             CPLAcquireMutex(hNCMutex, 1000.0);
    9308             :             return nullptr;
    9309             :         }
    9310             :     }
    9311             : #endif
    9312             : 
    9313             :     int status =
    9314         201 :         nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
    9315             : 
    9316             :     // Put into define mode.
    9317         201 :     poDS->SetDefineMode(true);
    9318             : 
    9319         201 :     if (status != NC_NOERR)
    9320             :     {
    9321          30 :         CPLError(CE_Failure, CPLE_OpenFailed,
    9322             :                  "Unable to create netCDF file %s (Error code %d): %s .",
    9323             :                  pszFilename, status, nc_strerror(status));
    9324          30 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    9325             :         // with GDALDataset own mutex.
    9326          30 :         delete poDS;
    9327          30 :         CPLAcquireMutex(hNCMutex, 1000.0);
    9328          30 :         return nullptr;
    9329             :     }
    9330             : 
    9331             :     // Define dimensions.
    9332         171 :     if (nXSize > 0 && nYSize > 0)
    9333             :     {
    9334         118 :         poDS->papszDimName.AddString(NCDF_DIMNAME_X);
    9335             :         status =
    9336         118 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
    9337         118 :         NCDF_ERR(status);
    9338         118 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9339             :                  poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
    9340             : 
    9341         118 :         poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
    9342             :         status =
    9343         118 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
    9344         118 :         NCDF_ERR(status);
    9345         118 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9346             :                  poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
    9347             :     }
    9348             : 
    9349         171 :     return poDS;
    9350             : }
    9351             : 
    9352             : /************************************************************************/
    9353             : /*                               Create()                               */
    9354             : /************************************************************************/
    9355             : 
    9356         127 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
    9357             :                                    int nYSize, int nBandsIn, GDALDataType eType,
    9358             :                                    CSLConstList papszOptions)
    9359             : {
    9360         127 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
    9361             :              pszFilename);
    9362             : 
    9363             :     const char *legacyCreationOp =
    9364         127 :         CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9365         254 :     std::string legacyCreationOp_s = std::string(legacyCreationOp);
    9366             : 
    9367             :     // Check legacy creation op FIRST
    9368             : 
    9369         127 :     bool legacyCreateMode = false;
    9370             : 
    9371         127 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
    9372             :     {
    9373          56 :         legacyCreateMode = true;
    9374             :     }
    9375          71 :     else if (legacyCreationOp_s == "CF_1.8")
    9376             :     {
    9377          54 :         legacyCreateMode = false;
    9378             :     }
    9379             : 
    9380          17 :     else if (legacyCreationOp_s == "WKT")
    9381             :     {
    9382          17 :         legacyCreateMode = true;
    9383             :     }
    9384             : 
    9385             :     else
    9386             :     {
    9387           0 :         CPLError(
    9388             :             CE_Failure, CPLE_NotSupported,
    9389             :             "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
    9390             :             legacyCreationOp_s.c_str());
    9391           0 :         return nullptr;
    9392             :     }
    9393             : 
    9394         254 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9395         240 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9396         113 :         (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
    9397             :          eType == GDT_Int64))
    9398             :     {
    9399          10 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9400          10 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9401             :     }
    9402             : 
    9403         254 :     CPLStringList aosBandNames;
    9404         127 :     if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
    9405             :     {
    9406             :         aosBandNames =
    9407           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9408             : 
    9409           2 :         if (aosBandNames.Count() != nBandsIn)
    9410             :         {
    9411           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9412             :                      "Attempted to create netCDF with %d bands but %d names "
    9413             :                      "provided in BAND_NAMES.",
    9414             :                      nBandsIn, aosBandNames.Count());
    9415             : 
    9416           1 :             return nullptr;
    9417             :         }
    9418             :     }
    9419             : 
    9420         252 :     CPLMutexHolderD(&hNCMutex);
    9421             : 
    9422         126 :     auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
    9423         126 :                                         aosOptions.List());
    9424             : 
    9425         126 :     if (!poDS)
    9426          19 :         return nullptr;
    9427             : 
    9428         107 :     if (!legacyCreateMode)
    9429             :     {
    9430          37 :         poDS->bSGSupport = true;
    9431          37 :         poDS->vcdf.enableFullVirtualMode();
    9432             :     }
    9433             : 
    9434             :     else
    9435             :     {
    9436          70 :         poDS->bSGSupport = false;
    9437             :     }
    9438             : 
    9439             :     // Should we write signed or unsigned byte?
    9440             :     // TODO should this only be done in Create()
    9441         107 :     poDS->bSignedData = true;
    9442         107 :     const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
    9443         107 :     if (eType == GDT_UInt8 && !EQUAL(pszValue, "SIGNEDBYTE"))
    9444          15 :         poDS->bSignedData = false;
    9445             : 
    9446             :     // Add Conventions, GDAL info and history.
    9447         107 :     if (poDS->cdfid >= 0)
    9448             :     {
    9449             :         const char *CF_Vector_Conv =
    9450         173 :             poDS->bSGSupport ||
    9451             :                     // Use of variable length strings require CF-1.8
    9452          68 :                     EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
    9453             :                 ? NCDF_CONVENTIONS_CF_V1_8
    9454         173 :                 : NCDF_CONVENTIONS_CF_V1_6;
    9455         105 :         poDS->bWriteGDALVersion = CPLTestBool(
    9456             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9457         105 :         poDS->bWriteGDALHistory = CPLTestBool(
    9458             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9459         105 :         NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
    9460         105 :                            poDS->bWriteGDALHistory, "", "Create",
    9461             :                            (nBandsIn == 0) ? CF_Vector_Conv
    9462             :                                            : GDAL_DEFAULT_NCDF_CONVENTIONS);
    9463             :     }
    9464             : 
    9465             :     // Define bands.
    9466         198 :     for (int iBand = 1; iBand <= nBandsIn; iBand++)
    9467             :     {
    9468             :         const char *pszBandName =
    9469          91 :             aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
    9470             : 
    9471          91 :         poDS->SetBand(iBand, new netCDFRasterBand(
    9472          91 :                                  netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
    9473          91 :                                  eType, iBand, poDS->bSignedData, pszBandName));
    9474             :     }
    9475             : 
    9476         107 :     CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
    9477             :     // Return same dataset.
    9478         107 :     return poDS;
    9479             : }
    9480             : 
    9481             : template <class T>
    9482          97 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
    9483             :                            int nXSize, int nYSize, GDALProgressFunc pfnProgress,
    9484             :                            void *pProgressData)
    9485             : {
    9486          97 :     const GDALDataType eDT = poSrcBand->GetRasterDataType();
    9487          97 :     T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
    9488          97 :     CPLErr eErr = patScanline ? CE_None : CE_Failure;
    9489             : 
    9490        6414 :     for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
    9491             :     {
    9492        6317 :         eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
    9493             :                                    nXSize, 1, eDT, 0, 0, nullptr);
    9494        6317 :         if (eErr != CE_None)
    9495             :         {
    9496           0 :             CPLDebug(
    9497             :                 "GDAL_netCDF",
    9498             :                 "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
    9499             :                 eErr);
    9500             :         }
    9501             :         else
    9502             :         {
    9503        6317 :             eErr =
    9504             :                 poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
    9505             :                                     nXSize, 1, eDT, 0, 0, nullptr);
    9506        6317 :             if (eErr != CE_None)
    9507           0 :                 CPLDebug("GDAL_netCDF",
    9508             :                          "NCDFCopyBand(), poDstBand->RasterIO() returned error "
    9509             :                          "code %d",
    9510             :                          eErr);
    9511             :         }
    9512             : 
    9513        6317 :         if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
    9514             :         {
    9515         317 :             if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
    9516             :             {
    9517           0 :                 eErr = CE_Failure;
    9518           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt,
    9519             :                          "User terminated CreateCopy()");
    9520             :             }
    9521             :         }
    9522             :     }
    9523             : 
    9524          97 :     CPLFree(patScanline);
    9525             : 
    9526          97 :     pfnProgress(1.0, nullptr, pProgressData);
    9527             : 
    9528          97 :     return eErr;
    9529             : }
    9530             : 
    9531             : /************************************************************************/
    9532             : /*                             CreateCopy()                             */
    9533             : /************************************************************************/
    9534             : 
    9535             : GDALDataset *
    9536          93 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
    9537             :                           CPL_UNUSED int bStrict, CSLConstList papszOptions,
    9538             :                           GDALProgressFunc pfnProgress, void *pProgressData)
    9539             : {
    9540         186 :     CPLMutexHolderD(&hNCMutex);
    9541             : 
    9542          93 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
    9543             :              pszFilename);
    9544             : 
    9545          93 :     if (poSrcDS->GetRootGroup())
    9546             :     {
    9547          10 :         auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
    9548          10 :         if (poDrv)
    9549             :         {
    9550          10 :             return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    9551             :                                             papszOptions, pfnProgress,
    9552          10 :                                             pProgressData);
    9553             :         }
    9554             :     }
    9555             : 
    9556          83 :     const int nBands = poSrcDS->GetRasterCount();
    9557          83 :     const int nXSize = poSrcDS->GetRasterXSize();
    9558          83 :     const int nYSize = poSrcDS->GetRasterYSize();
    9559          83 :     const char *pszWKT = poSrcDS->GetProjectionRef();
    9560             : 
    9561             :     // Check input bands for errors.
    9562          83 :     if (nBands == 0)
    9563             :     {
    9564           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    9565             :                  "NetCDF driver does not support "
    9566             :                  "source datasets with zero bands.");
    9567           1 :         return nullptr;
    9568             :     }
    9569             : 
    9570          82 :     GDALDataType eDT = GDT_Unknown;
    9571          82 :     GDALRasterBand *poSrcBand = nullptr;
    9572         193 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9573             :     {
    9574         115 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9575         115 :         eDT = poSrcBand->GetRasterDataType();
    9576         115 :         if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
    9577             :         {
    9578           4 :             CPLError(CE_Failure, CPLE_NotSupported,
    9579             :                      "NetCDF driver does not support source dataset with band "
    9580             :                      "of complex type.");
    9581           4 :             return nullptr;
    9582             :         }
    9583             :     }
    9584             : 
    9585         156 :     CPLStringList aosBandNames;
    9586          78 :     if (const char *pszBandNames =
    9587          78 :             CSLFetchNameValue(papszOptions, "BAND_NAMES"))
    9588             :     {
    9589             :         aosBandNames =
    9590           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9591             : 
    9592           2 :         if (aosBandNames.Count() != nBands)
    9593             :         {
    9594           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9595             :                      "Attempted to create netCDF with %d bands but %d names "
    9596             :                      "provided in BAND_NAMES.",
    9597             :                      nBands, aosBandNames.Count());
    9598             : 
    9599           1 :             return nullptr;
    9600             :         }
    9601             :     }
    9602             : 
    9603          77 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    9604           0 :         return nullptr;
    9605             : 
    9606             :     // Same as in Create().
    9607         154 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9608         145 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9609          68 :         (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
    9610             :          eDT == GDT_Int64))
    9611             :     {
    9612           6 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9613           6 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9614             :     }
    9615          77 :     netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
    9616          77 :                                                   nBands, aosOptions.List());
    9617          77 :     if (!poDS)
    9618          13 :         return nullptr;
    9619             : 
    9620             :     // Copy global metadata.
    9621             :     // Add Conventions, GDAL info and history.
    9622          64 :     CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
    9623          64 :     const bool bWriteGDALVersion = CPLTestBool(
    9624             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9625          64 :     const bool bWriteGDALHistory = CPLTestBool(
    9626             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9627          64 :     NCDFAddGDALHistory(
    9628             :         poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
    9629          64 :         poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
    9630          64 :         poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
    9631             : 
    9632          64 :     pfnProgress(0.1, nullptr, pProgressData);
    9633             : 
    9634             :     // Check for extra dimensions.
    9635          64 :     int nDim = 2;
    9636             :     CPLStringList aosExtraDimNames =
    9637         128 :         NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9638             : 
    9639          64 :     if (!aosExtraDimNames.empty())
    9640             :     {
    9641           5 :         size_t nDimSizeTot = 1;
    9642             :         // first make sure dimensions lengths compatible with band count
    9643             :         // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
    9644          13 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9645             :         {
    9646             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9647           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9648             :                      aosExtraDimNames[i]);
    9649             :             const CPLStringList aosExtraDimValues =
    9650           8 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9651           8 :             const size_t nDimSize = atol(aosExtraDimValues[0]);
    9652           8 :             nDimSizeTot *= nDimSize;
    9653             :         }
    9654           5 :         if (nDimSizeTot == (size_t)nBands)
    9655             :         {
    9656           5 :             nDim = 2 + aosExtraDimNames.size();
    9657             :         }
    9658             :         else
    9659             :         {
    9660             :             // if nBands != #bands computed raise a warning
    9661             :             // just issue a debug message, because it was probably intentional
    9662           0 :             CPLDebug("GDAL_netCDF",
    9663             :                      "Warning: Number of bands (%d) is not compatible with "
    9664             :                      "dimensions "
    9665             :                      "(total=%ld names=%s)",
    9666             :                      nBands, (long)nDimSizeTot,
    9667           0 :                      poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9668           0 :             aosExtraDimNames.clear();
    9669             :         }
    9670             :     }
    9671             : 
    9672          64 :     int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9673          64 :     int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9674             : 
    9675             :     nc_type nVarType;
    9676          64 :     int *panBandZLev = nullptr;
    9677          64 :     int *panDimVarIds = nullptr;
    9678             : 
    9679          64 :     if (nDim > 2)
    9680             :     {
    9681           5 :         panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9682           5 :         panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9683             : 
    9684             :         // Define all dims.
    9685          13 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9686             :         {
    9687           8 :             poDS->papszDimName.AddString(aosExtraDimNames[i]);
    9688             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9689           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9690             :                      aosExtraDimNames[i]);
    9691             :             const CPLStringList aosExtraDimValues =
    9692          16 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9693             :             const int nDimSize =
    9694           8 :                 aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
    9695             :             // nc_type is an enum in netcdf-3, needs casting.
    9696           0 :             nVarType = static_cast<nc_type>(
    9697           8 :                 aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
    9698           8 :             panBandZLev[i] = nDimSize;
    9699           8 :             panBandDimPos[i + 2] = i;  // Save Position of ZDim.
    9700             : 
    9701             :             // Define dim.
    9702           8 :             int status = nc_def_dim(poDS->cdfid, aosExtraDimNames[i], nDimSize,
    9703           8 :                                     &(panDimIds[i]));
    9704           8 :             NCDF_ERR(status);
    9705             : 
    9706             :             // Define dim var.
    9707           8 :             int anDim[1] = {panDimIds[i]};
    9708           8 :             status = nc_def_var(poDS->cdfid, aosExtraDimNames[i], nVarType, 1,
    9709           8 :                                 anDim, &(panDimVarIds[i]));
    9710           8 :             NCDF_ERR(status);
    9711             : 
    9712             :             // Add dim metadata, using global var# items.
    9713           8 :             snprintf(szTemp, sizeof(szTemp), "%s#", aosExtraDimNames[i]);
    9714           8 :             CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
    9715           8 :                          panDimVarIds[i], szTemp);
    9716             :         }
    9717             :     }
    9718             : 
    9719             :     // Copy GeoTransform and Projection.
    9720             : 
    9721             :     // Copy geolocation info.
    9722          64 :     CSLConstList papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
    9723          64 :     if (papszGeolocationInfo != nullptr)
    9724           5 :         poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
    9725             : 
    9726             :     // Copy geotransform.
    9727          64 :     bool bGotGeoTransform = false;
    9728          64 :     GDALGeoTransform gt;
    9729          64 :     CPLErr eErr = poSrcDS->GetGeoTransform(gt);
    9730          64 :     if (eErr == CE_None)
    9731             :     {
    9732          46 :         poDS->SetGeoTransform(gt);
    9733             :         // Disable AddProjectionVars() from being called.
    9734          46 :         bGotGeoTransform = true;
    9735          46 :         poDS->m_bHasGeoTransform = false;
    9736             :     }
    9737             : 
    9738             :     // Copy projection.
    9739          64 :     void *pScaledProgress = nullptr;
    9740          64 :     if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
    9741             :     {
    9742          47 :         poDS->SetProjection(pszWKT ? pszWKT : "");
    9743             : 
    9744             :         // Now we can call AddProjectionVars() directly.
    9745          47 :         poDS->m_bHasGeoTransform = bGotGeoTransform;
    9746          47 :         poDS->AddProjectionVars(true, nullptr, nullptr);
    9747             :         pScaledProgress =
    9748          47 :             GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
    9749          47 :         poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
    9750          47 :         GDALDestroyScaledProgress(pScaledProgress);
    9751             :     }
    9752             :     else
    9753             :     {
    9754          17 :         poDS->bBottomUp =
    9755          17 :             CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
    9756          17 :         if (papszGeolocationInfo)
    9757             :         {
    9758           4 :             poDS->AddProjectionVars(true, nullptr, nullptr);
    9759           4 :             poDS->AddProjectionVars(false, nullptr, nullptr);
    9760             :         }
    9761             :     }
    9762             : 
    9763             :     // Save X,Y dim positions.
    9764          64 :     panDimIds[nDim - 1] = poDS->nXDimID;
    9765          64 :     panBandDimPos[0] = nDim - 1;
    9766          64 :     panDimIds[nDim - 2] = poDS->nYDimID;
    9767          64 :     panBandDimPos[1] = nDim - 2;
    9768             : 
    9769             :     // Write extra dim values - after projection for optimization.
    9770          64 :     if (nDim > 2)
    9771             :     {
    9772             :         // Make sure we are in data mode.
    9773           5 :         poDS->SetDefineMode(false);
    9774          13 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9775             :         {
    9776             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9777           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
    9778             :                      aosExtraDimNames[i]);
    9779           8 :             if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
    9780             :             {
    9781           8 :                 NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
    9782           8 :                              poSrcDS->GetMetadataItem(szTemp));
    9783             :             }
    9784             :         }
    9785             :     }
    9786             : 
    9787          64 :     pfnProgress(0.25, nullptr, pProgressData);
    9788             : 
    9789             :     // Define Bands.
    9790          64 :     netCDFRasterBand *poBand = nullptr;
    9791          64 :     int nBandID = -1;
    9792             : 
    9793         161 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9794             :     {
    9795          97 :         CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
    9796             :                  nBands, nDim);
    9797             : 
    9798          97 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9799          97 :         eDT = poSrcBand->GetRasterDataType();
    9800             : 
    9801             :         // Get var name from NETCDF_VARNAME.
    9802             :         const char *pszNETCDF_VARNAME =
    9803          97 :             poSrcBand->GetMetadataItem("NETCDF_VARNAME");
    9804             :         char szBandName[NC_MAX_NAME + 1];
    9805          97 :         if (!aosBandNames.empty())
    9806             :         {
    9807           2 :             snprintf(szBandName, sizeof(szBandName), "%s",
    9808             :                      aosBandNames[iBand - 1]);
    9809             :         }
    9810          95 :         else if (pszNETCDF_VARNAME)
    9811             :         {
    9812          32 :             if (nBands > 1 && aosExtraDimNames.empty())
    9813           0 :                 snprintf(szBandName, sizeof(szBandName), "%s%d",
    9814             :                          pszNETCDF_VARNAME, iBand);
    9815             :             else
    9816          32 :                 snprintf(szBandName, sizeof(szBandName), "%s",
    9817             :                          pszNETCDF_VARNAME);
    9818             :         }
    9819             :         else
    9820             :         {
    9821          63 :             szBandName[0] = '\0';
    9822             :         }
    9823             : 
    9824             :         // Get long_name from <var>#long_name.
    9825          97 :         const char *pszLongName = "";
    9826          97 :         if (pszNETCDF_VARNAME)
    9827             :         {
    9828             :             pszLongName =
    9829          64 :                 poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
    9830          32 :                                              .append("#")
    9831          32 :                                              .append(CF_LNG_NAME)
    9832          32 :                                              .c_str());
    9833          32 :             if (!pszLongName)
    9834          25 :                 pszLongName = "";
    9835             :         }
    9836             : 
    9837          97 :         constexpr bool bSignedData = false;
    9838             : 
    9839          97 :         if (nDim > 2)
    9840          27 :             poBand = new netCDFRasterBand(
    9841          27 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9842             :                 bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
    9843          27 :                 panBandZLev, panBandDimPos, panDimIds);
    9844             :         else
    9845          70 :             poBand = new netCDFRasterBand(
    9846          70 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9847          70 :                 bSignedData, szBandName, pszLongName);
    9848             : 
    9849          97 :         poDS->SetBand(iBand, poBand);
    9850             : 
    9851             :         // Set nodata value, if any.
    9852          97 :         GDALCopyNoDataValue(poBand, poSrcBand);
    9853             : 
    9854             :         // Copy Metadata for band.
    9855          97 :         CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
    9856             :                      poDS->cdfid, poBand->nZId);
    9857             : 
    9858             :         // If more than 2D pass the first band's netcdf var ID to subsequent
    9859             :         // bands.
    9860          97 :         if (nDim > 2)
    9861          27 :             nBandID = poBand->nZId;
    9862             :     }
    9863             : 
    9864             :     // Write projection variable to band variable.
    9865          64 :     poDS->AddGridMappingRef();
    9866             : 
    9867          64 :     pfnProgress(0.5, nullptr, pProgressData);
    9868             : 
    9869             :     // Write bands.
    9870             : 
    9871             :     // Make sure we are in data mode.
    9872          64 :     poDS->SetDefineMode(false);
    9873             : 
    9874          64 :     double dfTemp = 0.5;
    9875             : 
    9876          64 :     eErr = CE_None;
    9877             : 
    9878         161 :     for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
    9879             :     {
    9880          97 :         const double dfTemp2 = dfTemp + 0.4 / nBands;
    9881          97 :         pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
    9882             :                                                    pProgressData);
    9883          97 :         dfTemp = dfTemp2;
    9884             : 
    9885          97 :         CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
    9886             : 
    9887          97 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9888          97 :         eDT = poSrcBand->GetRasterDataType();
    9889             : 
    9890          97 :         GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
    9891             : 
    9892             :         // Copy band data.
    9893          97 :         if (eDT == GDT_UInt8)
    9894             :         {
    9895          57 :             CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
    9896          57 :             eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
    9897             :                                        GDALScaledProgress, pScaledProgress);
    9898             :         }
    9899          40 :         else if (eDT == GDT_Int8)
    9900             :         {
    9901           1 :             CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
    9902           1 :             eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
    9903             :                                        GDALScaledProgress, pScaledProgress);
    9904             :         }
    9905          39 :         else if (eDT == GDT_UInt16)
    9906             :         {
    9907           2 :             CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
    9908           2 :             eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9909             :                                         GDALScaledProgress, pScaledProgress);
    9910             :         }
    9911          37 :         else if (eDT == GDT_Int16)
    9912             :         {
    9913           5 :             CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
    9914           5 :             eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9915             :                                          GDALScaledProgress, pScaledProgress);
    9916             :         }
    9917          32 :         else if (eDT == GDT_UInt32)
    9918             :         {
    9919           2 :             CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
    9920           2 :             eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9921             :                                          GDALScaledProgress, pScaledProgress);
    9922             :         }
    9923          30 :         else if (eDT == GDT_Int32)
    9924             :         {
    9925          18 :             CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
    9926          18 :             eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9927             :                                         GDALScaledProgress, pScaledProgress);
    9928             :         }
    9929          12 :         else if (eDT == GDT_UInt64)
    9930             :         {
    9931           2 :             CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
    9932           2 :             eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
    9933             :                                                nYSize, GDALScaledProgress,
    9934             :                                                pScaledProgress);
    9935             :         }
    9936          10 :         else if (eDT == GDT_Int64)
    9937             :         {
    9938           2 :             CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
    9939             :             eErr =
    9940           2 :                 NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
    9941             :                                            GDALScaledProgress, pScaledProgress);
    9942             :         }
    9943           8 :         else if (eDT == GDT_Float32)
    9944             :         {
    9945           6 :             CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
    9946           6 :             eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
    9947             :                                        GDALScaledProgress, pScaledProgress);
    9948             :         }
    9949           2 :         else if (eDT == GDT_Float64)
    9950             :         {
    9951           2 :             CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
    9952           2 :             eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
    9953             :                                         GDALScaledProgress, pScaledProgress);
    9954             :         }
    9955             :         else
    9956             :         {
    9957           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9958             :                      "The NetCDF driver does not support GDAL data type %d",
    9959             :                      eDT);
    9960             :         }
    9961             : 
    9962          97 :         GDALDestroyScaledProgress(pScaledProgress);
    9963             :     }
    9964             : 
    9965          64 :     delete (poDS);
    9966             : 
    9967          64 :     CPLFree(panDimIds);
    9968          64 :     CPLFree(panBandDimPos);
    9969          64 :     CPLFree(panBandZLev);
    9970          64 :     CPLFree(panDimVarIds);
    9971             : 
    9972          64 :     if (eErr != CE_None)
    9973           0 :         return nullptr;
    9974             : 
    9975          64 :     pfnProgress(0.95, nullptr, pProgressData);
    9976             : 
    9977             :     // Re-open dataset so we can return it.
    9978         128 :     CPLStringList aosOpenOptions;
    9979          64 :     aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
    9980          64 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
    9981          64 :     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
    9982          64 :     oOpenInfo.papszOpenOptions = aosOpenOptions.List();
    9983          64 :     auto poRetDS = Open(&oOpenInfo);
    9984             : 
    9985             :     // PAM cloning is disabled. See bug #4244.
    9986             :     // if( poDS )
    9987             :     //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    9988             : 
    9989          64 :     pfnProgress(1.0, nullptr, pProgressData);
    9990             : 
    9991          64 :     return poRetDS;
    9992             : }
    9993             : 
    9994             : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
    9995             : // May not be known when Create() is called, see AddProjectionVars().
    9996         314 : void netCDFDataset::ProcessCreationOptions()
    9997             : {
    9998         314 :     const char *pszConfig = aosCreationOptions.FetchNameValue("CONFIG_FILE");
    9999         314 :     if (pszConfig != nullptr)
   10000             :     {
   10001           4 :         if (oWriterConfig.Parse(pszConfig))
   10002             :         {
   10003             :             // Override dataset creation options from the config file
   10004           2 :             for (const auto &[osName, osValue] :
   10005           4 :                  oWriterConfig.m_oDatasetCreationOptions)
   10006             :             {
   10007           1 :                 aosCreationOptions.SetNameValue(osName, osValue);
   10008             :             }
   10009             :         }
   10010             :     }
   10011             : 
   10012             :     // File format.
   10013         314 :     eFormat = NCDF_FORMAT_NC;
   10014         314 :     const char *pszValue = aosCreationOptions.FetchNameValue("FORMAT");
   10015         314 :     if (pszValue != nullptr)
   10016             :     {
   10017         146 :         if (EQUAL(pszValue, "NC"))
   10018             :         {
   10019           3 :             eFormat = NCDF_FORMAT_NC;
   10020             :         }
   10021             : #ifdef NETCDF_HAS_NC2
   10022         143 :         else if (EQUAL(pszValue, "NC2"))
   10023             :         {
   10024           1 :             eFormat = NCDF_FORMAT_NC2;
   10025             :         }
   10026             : #endif
   10027         142 :         else if (EQUAL(pszValue, "NC4"))
   10028             :         {
   10029         138 :             eFormat = NCDF_FORMAT_NC4;
   10030             :         }
   10031           4 :         else if (EQUAL(pszValue, "NC4C"))
   10032             :         {
   10033           4 :             eFormat = NCDF_FORMAT_NC4C;
   10034             :         }
   10035             :         else
   10036             :         {
   10037           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10038             :                      "FORMAT=%s in not supported, using the default NC format.",
   10039             :                      pszValue);
   10040             :         }
   10041             :     }
   10042             : 
   10043             :     // COMPRESS option.
   10044         314 :     pszValue = aosCreationOptions.FetchNameValue("COMPRESS");
   10045         314 :     if (pszValue != nullptr)
   10046             :     {
   10047           3 :         if (EQUAL(pszValue, "NONE"))
   10048             :         {
   10049           1 :             eCompress = NCDF_COMPRESS_NONE;
   10050             :         }
   10051           2 :         else if (EQUAL(pszValue, "DEFLATE"))
   10052             :         {
   10053           2 :             eCompress = NCDF_COMPRESS_DEFLATE;
   10054           2 :             if (!((eFormat == NCDF_FORMAT_NC4) ||
   10055           2 :                   (eFormat == NCDF_FORMAT_NC4C)))
   10056             :             {
   10057           1 :                 CPLError(CE_Warning, CPLE_IllegalArg,
   10058             :                          "NOTICE: Format set to NC4C because compression is "
   10059             :                          "set to DEFLATE.");
   10060           1 :                 eFormat = NCDF_FORMAT_NC4C;
   10061             :             }
   10062             :         }
   10063             :         else
   10064             :         {
   10065           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10066             :                      "COMPRESS=%s is not supported.", pszValue);
   10067             :         }
   10068             :     }
   10069             : 
   10070             :     // ZLEVEL option.
   10071         314 :     pszValue = aosCreationOptions.FetchNameValue("ZLEVEL");
   10072         314 :     if (pszValue != nullptr)
   10073             :     {
   10074           1 :         nZLevel = atoi(pszValue);
   10075           1 :         if (!(nZLevel >= 1 && nZLevel <= 9))
   10076             :         {
   10077           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10078             :                      "ZLEVEL=%s value not recognised, ignoring.", pszValue);
   10079           0 :             nZLevel = NCDF_DEFLATE_LEVEL;
   10080             :         }
   10081             :     }
   10082             : 
   10083             :     // CHUNKING option.
   10084         314 :     bChunking = aosCreationOptions.FetchBool("CHUNKING", true);
   10085             : 
   10086             :     // MULTIPLE_LAYERS option.
   10087             :     const char *pszMultipleLayerBehavior =
   10088         314 :         aosCreationOptions.FetchNameValueDef("MULTIPLE_LAYERS", "NO");
   10089             :     const char *pszGeometryEnc =
   10090         314 :         aosCreationOptions.FetchNameValueDef("GEOMETRY_ENCODING", "CF_1.8");
   10091         314 :     if (EQUAL(pszMultipleLayerBehavior, "NO") ||
   10092           4 :         EQUAL(pszGeometryEnc, "CF_1.8"))
   10093             :     {
   10094         310 :         eMultipleLayerBehavior = SINGLE_LAYER;
   10095             :     }
   10096           4 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
   10097             :     {
   10098           3 :         eMultipleLayerBehavior = SEPARATE_FILES;
   10099             :     }
   10100           1 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
   10101             :     {
   10102           1 :         if (eFormat == NCDF_FORMAT_NC4)
   10103             :         {
   10104           1 :             eMultipleLayerBehavior = SEPARATE_GROUPS;
   10105             :         }
   10106             :         else
   10107             :         {
   10108           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10109             :                      "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
   10110             :                      pszMultipleLayerBehavior);
   10111             :         }
   10112             :     }
   10113             :     else
   10114             :     {
   10115           0 :         CPLError(CE_Warning, CPLE_IllegalArg,
   10116             :                  "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
   10117             :     }
   10118             : 
   10119             :     // Set nCreateMode based on eFormat.
   10120         314 :     switch (eFormat)
   10121             :     {
   10122             : #ifdef NETCDF_HAS_NC2
   10123           1 :         case NCDF_FORMAT_NC2:
   10124           1 :             nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
   10125           1 :             break;
   10126             : #endif
   10127         138 :         case NCDF_FORMAT_NC4:
   10128         138 :             nCreateMode = NC_CLOBBER | NC_NETCDF4;
   10129         138 :             break;
   10130           5 :         case NCDF_FORMAT_NC4C:
   10131           5 :             nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
   10132           5 :             break;
   10133         170 :         case NCDF_FORMAT_NC:
   10134             :         default:
   10135         170 :             nCreateMode = NC_CLOBBER;
   10136         170 :             break;
   10137             :     }
   10138             : 
   10139         314 :     CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
   10140         314 :              eFormat, eCompress, nZLevel);
   10141         314 : }
   10142             : 
   10143         288 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) const
   10144             : {
   10145         288 :     if (eCompress == NCDF_COMPRESS_DEFLATE)
   10146             :     {
   10147             :         // Must set chunk size to avoid huge performance hit (set
   10148             :         // bChunkingArg=TRUE)
   10149             :         // perhaps another solution it to change the chunk cache?
   10150             :         // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
   10151             :         // TODO: make sure this is okay.
   10152           2 :         CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
   10153           2 :                  static_cast<int>(bChunkingArg), nZLevel);
   10154             : 
   10155           2 :         int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
   10156           2 :         NCDF_ERR(status);
   10157             : 
   10158           2 :         if (status == NC_NOERR && bChunkingArg && bChunking)
   10159             :         {
   10160             :             // set chunking to be 1 for all dims, except X dim
   10161             :             // size_t chunksize[] = { 1, (size_t)nRasterXSize };
   10162             :             size_t chunksize[MAX_NC_DIMS];
   10163             :             int nd;
   10164           2 :             nc_inq_varndims(cdfid, nVarId, &nd);
   10165           2 :             chunksize[0] = (size_t)1;
   10166           2 :             chunksize[1] = (size_t)1;
   10167           2 :             for (int i = 2; i < nd; i++)
   10168           0 :                 chunksize[i] = (size_t)1;
   10169           2 :             chunksize[nd - 1] = (size_t)nRasterXSize;
   10170             : 
   10171             :             // Config options just for testing purposes
   10172             :             const char *pszBlockXSize =
   10173           2 :                 CPLGetConfigOption("BLOCKXSIZE", nullptr);
   10174           2 :             if (pszBlockXSize)
   10175           0 :                 chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
   10176             : 
   10177             :             const char *pszBlockYSize =
   10178           2 :                 CPLGetConfigOption("BLOCKYSIZE", nullptr);
   10179           2 :             if (nd >= 2 && pszBlockYSize)
   10180           0 :                 chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
   10181             : 
   10182           2 :             CPLDebug("GDAL_netCDF",
   10183             :                      "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
   10184           2 :                      (long)chunksize[0], (long)chunksize[1],
   10185           2 :                      (long)chunksize[nd - 1], nd);
   10186             : #ifdef NCDF_DEBUG
   10187             :             for (int i = 0; i < nd; i++)
   10188             :                 CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
   10189             :                          chunksize[i]);
   10190             : #endif
   10191             : 
   10192           2 :             status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
   10193           2 :             NCDF_ERR(status);
   10194             :         }
   10195             :         else
   10196             :         {
   10197           0 :             CPLDebug("GDAL_netCDF", "chunksize not set");
   10198             :         }
   10199           2 :         return status;
   10200             :     }
   10201         286 :     return NC_NOERR;
   10202             : }
   10203             : 
   10204             : /************************************************************************/
   10205             : /*                          NCDFUnloadDriver()                          */
   10206             : /************************************************************************/
   10207             : 
   10208           9 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
   10209             : {
   10210           9 :     if (hNCMutex != nullptr)
   10211           5 :         CPLDestroyMutex(hNCMutex);
   10212           9 :     hNCMutex = nullptr;
   10213           9 : }
   10214             : 
   10215             : /************************************************************************/
   10216             : /*                        GDALRegister_netCDF()                         */
   10217             : /************************************************************************/
   10218             : 
   10219             : class GDALnetCDFDriver final : public GDALDriver
   10220             : {
   10221             :   public:
   10222          19 :     GDALnetCDFDriver() = default;
   10223             : 
   10224             :     const char *GetMetadataItem(const char *pszName,
   10225             :                                 const char *pszDomain) override;
   10226             : 
   10227          93 :     CSLConstList GetMetadata(const char *pszDomain) override
   10228             :     {
   10229         186 :         std::lock_guard oLock(m_oMutex);
   10230          93 :         InitializeDCAPVirtualIO();
   10231         186 :         return GDALDriver::GetMetadata(pszDomain);
   10232             :     }
   10233             : 
   10234             :   private:
   10235             :     std::recursive_mutex m_oMutex{};
   10236             :     bool m_bInitialized = false;
   10237             : 
   10238         106 :     void InitializeDCAPVirtualIO()
   10239             :     {
   10240         106 :         if (!m_bInitialized)
   10241             :         {
   10242          12 :             m_bInitialized = true;
   10243             : 
   10244             : #ifdef ENABLE_UFFD
   10245          12 :             if (CPLIsUserFaultMappingSupported())
   10246             :             {
   10247          12 :                 SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
   10248             :             }
   10249             : #endif
   10250             :         }
   10251         106 :     }
   10252             : };
   10253             : 
   10254        1412 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
   10255             :                                               const char *pszDomain)
   10256             : {
   10257        2824 :     std::lock_guard oLock(m_oMutex);
   10258        1412 :     if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
   10259             :     {
   10260          13 :         InitializeDCAPVirtualIO();
   10261             :     }
   10262        2824 :     return GDALDriver::GetMetadataItem(pszName, pszDomain);
   10263             : }
   10264             : 
   10265          19 : void GDALRegister_netCDF()
   10266             : 
   10267             : {
   10268          19 :     if (!GDAL_CHECK_VERSION("netCDF driver"))
   10269           0 :         return;
   10270             : 
   10271          19 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
   10272           0 :         return;
   10273             : 
   10274          19 :     GDALDriver *poDriver = new GDALnetCDFDriver();
   10275          19 :     netCDFDriverSetCommonMetadata(poDriver);
   10276             : 
   10277          19 :     poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
   10278          19 :                               GDAL_DEFAULT_NCDF_CONVENTIONS);
   10279          19 :     poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
   10280             : 
   10281             :     // Set pfns and register driver.
   10282          19 :     poDriver->pfnOpen = netCDFDataset::Open;
   10283          19 :     poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
   10284          19 :     poDriver->pfnCreate = netCDFDataset::Create;
   10285          19 :     poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
   10286          19 :     poDriver->pfnUnloadDriver = NCDFUnloadDriver;
   10287             : 
   10288          19 :     GetGDALDriverManager()->RegisterDriver(poDriver);
   10289             : }
   10290             : 
   10291             : /************************************************************************/
   10292             : /*                            New functions                             */
   10293             : /************************************************************************/
   10294             : 
   10295             : /* Test for GDAL version string >= target */
   10296         257 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
   10297             : {
   10298             : 
   10299             :     // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
   10300         257 :     if (pszVersion == nullptr || EQUAL(pszVersion, ""))
   10301           0 :         return false;
   10302         257 :     else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
   10303           0 :         return false;
   10304             :     // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
   10305         257 :     else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
   10306           0 :         return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
   10307         257 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
   10308           2 :         return nTarget <= 1900;
   10309         255 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
   10310           0 :         return nTarget <= 1800;
   10311             : 
   10312         255 :     const CPLStringList aosTokens(CSLTokenizeString2(pszVersion + 5, ".", 0));
   10313             : 
   10314         255 :     int nVersions[] = {0, 0, 0, 0};
   10315        1020 :     for (int iToken = 0; iToken < std::min(4, aosTokens.size()); iToken++)
   10316             :     {
   10317         765 :         nVersions[iToken] = atoi(aosTokens[iToken]);
   10318         765 :         if (nVersions[iToken] < 0)
   10319           0 :             nVersions[iToken] = 0;
   10320         765 :         else if (nVersions[iToken] > 99)
   10321           0 :             nVersions[iToken] = 99;
   10322             :     }
   10323             : 
   10324         255 :     int nVersion = 0;
   10325         255 :     if (nVersions[0] > 1 || nVersions[1] >= 10)
   10326         255 :         nVersion =
   10327         255 :             GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
   10328             :     else
   10329           0 :         nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
   10330           0 :                    nVersions[2] * 10 + nVersions[3];
   10331             : 
   10332         255 :     return nTarget <= nVersion;
   10333             : }
   10334             : 
   10335             : // Add Conventions, GDAL version and history.
   10336         173 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
   10337             :                                bool bWriteGDALVersion, bool bWriteGDALHistory,
   10338             :                                const char *pszOldHist,
   10339             :                                const char *pszFunctionName,
   10340             :                                const char *pszCFVersion)
   10341             : {
   10342         173 :     if (pszCFVersion == nullptr)
   10343             :     {
   10344          48 :         pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
   10345             :     }
   10346         173 :     int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
   10347             :                                  strlen(pszCFVersion), pszCFVersion);
   10348         173 :     NCDF_ERR(status);
   10349             : 
   10350         173 :     if (bWriteGDALVersion)
   10351             :     {
   10352         171 :         const char *pszNCDF_GDAL = GDALVersionInfo("--version");
   10353         171 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
   10354             :                                  strlen(pszNCDF_GDAL), pszNCDF_GDAL);
   10355         171 :         NCDF_ERR(status);
   10356             :     }
   10357             : 
   10358         173 :     if (bWriteGDALHistory)
   10359             :     {
   10360             :         // Add history.
   10361         342 :         CPLString osTmp;
   10362             : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
   10363             :         if (!EQUAL(GDALGetCmdLine(), ""))
   10364             :             osTmp = GDALGetCmdLine();
   10365             :         else
   10366             :             osTmp =
   10367             :                 CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10368             : #else
   10369         171 :         osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10370             : #endif
   10371             : 
   10372         171 :         NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
   10373             :     }
   10374           2 :     else if (pszOldHist != nullptr)
   10375             :     {
   10376           0 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10377             :                                  strlen(pszOldHist), pszOldHist);
   10378           0 :         NCDF_ERR(status);
   10379             :     }
   10380         173 : }
   10381             : 
   10382             : // Code taken from cdo and libcdi, used for writing the history attribute.
   10383             : 
   10384             : // void cdoDefHistory(int fileID, char *histstring)
   10385         171 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
   10386             :                            const char *pszOldHist)
   10387             : {
   10388             :     // Check pszOldHist - as if there was no previous history, it will be
   10389             :     // a null pointer - if so set as empty.
   10390         171 :     if (nullptr == pszOldHist)
   10391             :     {
   10392          59 :         pszOldHist = "";
   10393             :     }
   10394             : 
   10395             :     char strtime[32];
   10396         171 :     strtime[0] = '\0';
   10397             : 
   10398         171 :     time_t tp = time(nullptr);
   10399         171 :     if (tp != -1)
   10400             :     {
   10401             :         struct tm ltime;
   10402         171 :         VSILocalTime(&tp, &ltime);
   10403         171 :         (void)strftime(strtime, sizeof(strtime),
   10404             :                        "%a %b %d %H:%M:%S %Y: ", &ltime);
   10405             :     }
   10406             : 
   10407             :     // status = nc_get_att_text(fpImage, NC_GLOBAL,
   10408             :     //                           "history", pszOldHist);
   10409             :     // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
   10410             : 
   10411         171 :     size_t nNewHistSize =
   10412         171 :         strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
   10413             :     char *pszNewHist =
   10414         171 :         static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
   10415             : 
   10416         171 :     strcpy(pszNewHist, strtime);
   10417         171 :     strcat(pszNewHist, pszAddHist);
   10418             : 
   10419             :     // int disableHistory = FALSE;
   10420             :     // if( !disableHistory )
   10421             :     {
   10422         171 :         if (!EQUAL(pszOldHist, ""))
   10423           3 :             strcat(pszNewHist, "\n");
   10424         171 :         strcat(pszNewHist, pszOldHist);
   10425             :     }
   10426             : 
   10427         171 :     const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10428             :                                        strlen(pszNewHist), pszNewHist);
   10429         171 :     NCDF_ERR(status);
   10430             : 
   10431         171 :     CPLFree(pszNewHist);
   10432         171 : }
   10433             : 
   10434        6640 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
   10435             :                              size_t *nDestSize)
   10436             : {
   10437             :     /* Reallocate the data string until the content fits */
   10438        6640 :     while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
   10439             :     {
   10440         413 :         (*nDestSize) *= 2;
   10441         413 :         *ppszDest = static_cast<char *>(
   10442         413 :             CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
   10443             : #ifdef NCDF_DEBUG
   10444             :         CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
   10445             :                  (*nDestSize) / 2, *nDestSize);
   10446             : #endif
   10447             :     }
   10448        6227 :     strcat(*ppszDest, pszSrc);
   10449             : 
   10450        6227 :     return CE_None;
   10451             : }
   10452             : 
   10453             : /* helper function for NCDFGetAttr() */
   10454             : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
   10455             : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
   10456             : /* *ppszValue is the responsibility of the caller and must be freed */
   10457       68566 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
   10458             :                            double *pdfValue, char **ppszValue)
   10459             : {
   10460       68566 :     nc_type nAttrType = NC_NAT;
   10461       68566 :     size_t nAttrLen = 0;
   10462             : 
   10463       68566 :     if (ppszValue)
   10464       67404 :         *ppszValue = nullptr;
   10465             : 
   10466       68566 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10467       68566 :     if (status != NC_NOERR)
   10468       36699 :         return CE_Failure;
   10469             : 
   10470             : #ifdef NCDF_DEBUG
   10471             :     CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
   10472             :              nAttrLen, nAttrType);
   10473             : #endif
   10474       31867 :     if (nAttrLen == 0 && nAttrType != NC_CHAR)
   10475           1 :         return CE_Failure;
   10476             : 
   10477             :     /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
   10478       31866 :     size_t nAttrValueSize = nAttrLen + 1;
   10479       31866 :     if (nAttrType != NC_CHAR && nAttrValueSize < 10)
   10480        3574 :         nAttrValueSize = 10;
   10481       31866 :     if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
   10482        1722 :         nAttrValueSize = 20;
   10483       31866 :     if (nAttrType == NC_INT64 && nAttrValueSize < 20)
   10484          22 :         nAttrValueSize = 22;
   10485             :     char *pszAttrValue =
   10486       31866 :         static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
   10487       31866 :     *pszAttrValue = '\0';
   10488             : 
   10489       31866 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10490         638 :         NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
   10491             : 
   10492       31866 :     double dfValue = 0.0;
   10493       31866 :     size_t m = 0;
   10494             :     char szTemp[256];
   10495       31866 :     bool bSetDoubleFromStr = false;
   10496             : 
   10497       31866 :     switch (nAttrType)
   10498             :     {
   10499       28290 :         case NC_CHAR:
   10500       28290 :             CPL_IGNORE_RET_VAL(
   10501       28290 :                 nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
   10502       28290 :             pszAttrValue[nAttrLen] = '\0';
   10503       28290 :             bSetDoubleFromStr = true;
   10504       28290 :             dfValue = 0.0;
   10505       28290 :             break;
   10506          94 :         case NC_BYTE:
   10507             :         {
   10508             :             signed char *pscTemp = static_cast<signed char *>(
   10509          94 :                 CPLCalloc(nAttrLen, sizeof(signed char)));
   10510          94 :             nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
   10511          94 :             dfValue = static_cast<double>(pscTemp[0]);
   10512          94 :             if (nAttrLen > 1)
   10513             :             {
   10514          24 :                 for (m = 0; m < nAttrLen - 1; m++)
   10515             :                 {
   10516          13 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10517          13 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10518             :                 }
   10519             :             }
   10520          94 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10521          94 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10522          94 :             CPLFree(pscTemp);
   10523          94 :             break;
   10524             :         }
   10525         523 :         case NC_SHORT:
   10526             :         {
   10527             :             short *psTemp =
   10528         523 :                 static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
   10529         523 :             nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
   10530         523 :             dfValue = static_cast<double>(psTemp[0]);
   10531         523 :             if (nAttrLen > 1)
   10532             :             {
   10533         840 :                 for (m = 0; m < nAttrLen - 1; m++)
   10534             :                 {
   10535         420 :                     snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10536         420 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10537             :                 }
   10538             :             }
   10539         523 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10540         523 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10541         523 :             CPLFree(psTemp);
   10542         523 :             break;
   10543             :         }
   10544         530 :         case NC_INT:
   10545             :         {
   10546         530 :             int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10547         530 :             nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
   10548         530 :             dfValue = static_cast<double>(pnTemp[0]);
   10549         530 :             if (nAttrLen > 1)
   10550             :             {
   10551         218 :                 for (m = 0; m < nAttrLen - 1; m++)
   10552             :                 {
   10553         139 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10554         139 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10555             :                 }
   10556             :             }
   10557         530 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10558         530 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10559         530 :             CPLFree(pnTemp);
   10560         530 :             break;
   10561             :         }
   10562         395 :         case NC_FLOAT:
   10563             :         {
   10564             :             float *pfTemp =
   10565         395 :                 static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10566         395 :             nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
   10567         395 :             dfValue = static_cast<double>(pfTemp[0]);
   10568         395 :             if (nAttrLen > 1)
   10569             :             {
   10570          60 :                 for (m = 0; m < nAttrLen - 1; m++)
   10571             :                 {
   10572          30 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10573          30 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10574             :                 }
   10575             :             }
   10576         395 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10577         395 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10578         395 :             CPLFree(pfTemp);
   10579         395 :             break;
   10580             :         }
   10581        1722 :         case NC_DOUBLE:
   10582             :         {
   10583             :             double *pdfTemp =
   10584        1722 :                 static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10585        1722 :             nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
   10586        1722 :             dfValue = pdfTemp[0];
   10587        1722 :             if (nAttrLen > 1)
   10588             :             {
   10589         166 :                 for (m = 0; m < nAttrLen - 1; m++)
   10590             :                 {
   10591          90 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10592          90 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10593             :                 }
   10594             :             }
   10595        1722 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10596        1722 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10597        1722 :             CPLFree(pdfTemp);
   10598        1722 :             break;
   10599             :         }
   10600         167 :         case NC_STRING:
   10601             :         {
   10602             :             char **ppszTemp =
   10603         167 :                 static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
   10604         167 :             nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
   10605         167 :             bSetDoubleFromStr = true;
   10606         167 :             dfValue = 0.0;
   10607         167 :             if (nAttrLen > 1)
   10608             :             {
   10609          19 :                 for (m = 0; m < nAttrLen - 1; m++)
   10610             :                 {
   10611          12 :                     NCDFSafeStrcat(&pszAttrValue,
   10612          12 :                                    ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10613             :                                    &nAttrValueSize);
   10614          12 :                     NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
   10615             :                 }
   10616             :             }
   10617         167 :             NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10618             :                            &nAttrValueSize);
   10619         167 :             nc_free_string(nAttrLen, ppszTemp);
   10620         167 :             CPLFree(ppszTemp);
   10621         167 :             break;
   10622             :         }
   10623          28 :         case NC_UBYTE:
   10624             :         {
   10625             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   10626          28 :                 CPLCalloc(nAttrLen, sizeof(unsigned char)));
   10627          28 :             nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
   10628          28 :             dfValue = static_cast<double>(pucTemp[0]);
   10629          28 :             if (nAttrLen > 1)
   10630             :             {
   10631           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10632             :                 {
   10633           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10634           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10635             :                 }
   10636             :             }
   10637          28 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10638          28 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10639          28 :             CPLFree(pucTemp);
   10640          28 :             break;
   10641             :         }
   10642          26 :         case NC_USHORT:
   10643             :         {
   10644             :             unsigned short *pusTemp = static_cast<unsigned short *>(
   10645          26 :                 CPLCalloc(nAttrLen, sizeof(unsigned short)));
   10646          26 :             nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
   10647          26 :             dfValue = static_cast<double>(pusTemp[0]);
   10648          26 :             if (nAttrLen > 1)
   10649             :             {
   10650          10 :                 for (m = 0; m < nAttrLen - 1; m++)
   10651             :                 {
   10652           5 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10653           5 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10654             :                 }
   10655             :             }
   10656          26 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10657          26 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10658          26 :             CPLFree(pusTemp);
   10659          26 :             break;
   10660             :         }
   10661          21 :         case NC_UINT:
   10662             :         {
   10663             :             unsigned int *punTemp =
   10664          21 :                 static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10665          21 :             nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
   10666          21 :             dfValue = static_cast<double>(punTemp[0]);
   10667          21 :             if (nAttrLen > 1)
   10668             :             {
   10669           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10670             :                 {
   10671           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   10672           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10673             :                 }
   10674             :             }
   10675          21 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   10676          21 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10677          21 :             CPLFree(punTemp);
   10678          21 :             break;
   10679             :         }
   10680          22 :         case NC_INT64:
   10681             :         {
   10682             :             GIntBig *panTemp =
   10683          22 :                 static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
   10684          22 :             nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
   10685          22 :             dfValue = static_cast<double>(panTemp[0]);
   10686          22 :             if (nAttrLen > 1)
   10687             :             {
   10688           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10689             :                 {
   10690           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
   10691           0 :                                 panTemp[m]);
   10692           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10693             :                 }
   10694             :             }
   10695          22 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
   10696          22 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10697          22 :             CPLFree(panTemp);
   10698          22 :             break;
   10699             :         }
   10700          22 :         case NC_UINT64:
   10701             :         {
   10702             :             GUIntBig *panTemp =
   10703          22 :                 static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
   10704          22 :             nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
   10705          22 :             dfValue = static_cast<double>(panTemp[0]);
   10706          22 :             if (nAttrLen > 1)
   10707             :             {
   10708           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10709             :                 {
   10710           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
   10711           0 :                                 panTemp[m]);
   10712           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10713             :                 }
   10714             :             }
   10715          22 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
   10716          22 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10717          22 :             CPLFree(panTemp);
   10718          22 :             break;
   10719             :         }
   10720          26 :         default:
   10721          26 :             CPLDebug("GDAL_netCDF",
   10722             :                      "NCDFGetAttr unsupported type %d for attribute %s",
   10723             :                      nAttrType, pszAttrName);
   10724          26 :             break;
   10725             :     }
   10726             : 
   10727       31866 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10728         638 :         NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
   10729             : 
   10730       31866 :     if (bSetDoubleFromStr)
   10731             :     {
   10732       28457 :         if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
   10733             :         {
   10734       28275 :             if (ppszValue == nullptr && pdfValue != nullptr)
   10735             :             {
   10736           1 :                 CPLFree(pszAttrValue);
   10737           1 :                 return CE_Failure;
   10738             :             }
   10739             :         }
   10740       28456 :         dfValue = CPLAtof(pszAttrValue);
   10741             :     }
   10742             : 
   10743             :     /* set return values */
   10744       31865 :     if (ppszValue)
   10745       31552 :         *ppszValue = pszAttrValue;
   10746             :     else
   10747         313 :         CPLFree(pszAttrValue);
   10748             : 
   10749       31865 :     if (pdfValue)
   10750         313 :         *pdfValue = dfValue;
   10751             : 
   10752       31865 :     return CE_None;
   10753             : }
   10754             : 
   10755             : /* sets pdfValue to first value found */
   10756        1162 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10757             :                    double *pdfValue)
   10758             : {
   10759        1162 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
   10760             : }
   10761             : 
   10762             : /* pszValue is the responsibility of the caller and must be freed */
   10763       67404 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10764             :                    char **pszValue)
   10765             : {
   10766       67404 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
   10767             : }
   10768             : 
   10769        3113 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10770             :                    std::string &osValue)
   10771             : {
   10772        3113 :     nc_type nAttrType = NC_NAT;
   10773        3113 :     size_t nAttrLen = 0;
   10774             : 
   10775        3113 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10776        3113 :     if (status != NC_NOERR)
   10777        1412 :         return CE_Failure;
   10778             : 
   10779        1701 :     if (nAttrType != NC_CHAR)
   10780           0 :         return CE_Failure;
   10781             : 
   10782             :     try
   10783             :     {
   10784        1701 :         osValue.resize(nAttrLen, 0);
   10785             :     }
   10786           0 :     catch (const std::exception &)
   10787             :     {
   10788           0 :         return CE_Failure;
   10789             :     }
   10790             : 
   10791        1701 :     const auto nErr = nc_get_att_text(nCdfId, nVarId, pszAttrName,
   10792        1701 :                                       osValue.data()) != NC_NOERR;
   10793        1701 :     NCDF_ERR_RET(nErr);
   10794             : 
   10795        1701 :     return CE_None;
   10796             : }
   10797             : 
   10798             : /* By default write NC_CHAR, but detect for int/float/double and */
   10799             : /* NC4 string arrays */
   10800         112 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10801             :                           const char *pszValue)
   10802             : {
   10803         112 :     int status = 0;
   10804         112 :     char *pszTemp = nullptr;
   10805             : 
   10806             :     /* get the attribute values as tokens */
   10807         224 :     CPLStringList aosValues = NCDFTokenizeArray(pszValue);
   10808         112 :     if (aosValues.empty())
   10809           0 :         return CE_Failure;
   10810             : 
   10811         112 :     size_t nAttrLen = aosValues.size();
   10812             : 
   10813             :     /* first detect type */
   10814         112 :     nc_type nAttrType = NC_CHAR;
   10815         112 :     nc_type nTmpAttrType = NC_CHAR;
   10816         237 :     for (size_t i = 0; i < nAttrLen; i++)
   10817             :     {
   10818         125 :         nTmpAttrType = NC_CHAR;
   10819         125 :         bool bFoundType = false;
   10820         125 :         errno = 0;
   10821         125 :         int nValue = static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   10822             :         /* test for int */
   10823             :         /* TODO test for Byte and short - can this be done safely? */
   10824         125 :         if (errno == 0 && aosValues[i] != pszTemp && *pszTemp == 0)
   10825             :         {
   10826             :             char szTemp[256];
   10827          19 :             CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
   10828          19 :             if (EQUAL(szTemp, aosValues[i]))
   10829             :             {
   10830          19 :                 bFoundType = true;
   10831          19 :                 nTmpAttrType = NC_INT;
   10832             :             }
   10833             :             else
   10834             :             {
   10835             :                 unsigned int unValue = static_cast<unsigned int>(
   10836           0 :                     strtoul(aosValues[i], &pszTemp, 10));
   10837           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
   10838           0 :                 if (EQUAL(szTemp, aosValues[i]))
   10839             :                 {
   10840           0 :                     bFoundType = true;
   10841           0 :                     nTmpAttrType = NC_UINT;
   10842             :                 }
   10843             :             }
   10844             :         }
   10845         125 :         if (!bFoundType)
   10846             :         {
   10847             :             /* test for double */
   10848         106 :             errno = 0;
   10849         106 :             double dfValue = CPLStrtod(aosValues[i], &pszTemp);
   10850         106 :             if ((errno == 0) && (aosValues[i] != pszTemp) && (*pszTemp == 0))
   10851             :             {
   10852             :                 // Test for float instead of double.
   10853             :                 // strtof() is C89, which is not available in MSVC.
   10854             :                 // See if we lose precision if we cast to float and write to
   10855             :                 // char*.
   10856          14 :                 float fValue = float(dfValue);
   10857             :                 char szTemp[256];
   10858          14 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
   10859          14 :                 if (EQUAL(szTemp, aosValues[i]))
   10860           8 :                     nTmpAttrType = NC_FLOAT;
   10861             :                 else
   10862           6 :                     nTmpAttrType = NC_DOUBLE;
   10863             :             }
   10864             :         }
   10865         125 :         if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
   10866         105 :              nTmpAttrType > nAttrType) ||
   10867         105 :             (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
   10868           5 :             (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
   10869          20 :             nAttrType = nTmpAttrType;
   10870             :     }
   10871             : 
   10872             : #ifdef DEBUG
   10873         112 :     if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
   10874             :     {
   10875           0 :         nAttrType = NC_DOUBLE;
   10876           0 :         nAttrLen = 0;
   10877             :     }
   10878             : #endif
   10879             : 
   10880             :     /* now write the data */
   10881         112 :     if (nAttrType == NC_CHAR)
   10882             :     {
   10883          92 :         int nTmpFormat = 0;
   10884          92 :         if (nAttrLen > 1)
   10885             :         {
   10886           0 :             status = nc_inq_format(nCdfId, &nTmpFormat);
   10887           0 :             NCDF_ERR(status);
   10888             :         }
   10889          92 :         if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
   10890           0 :             status =
   10891           0 :                 nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
   10892           0 :                                   const_cast<const char **>(aosValues.List()));
   10893             :         else
   10894          92 :             status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
   10895             :                                      strlen(pszValue), pszValue);
   10896          92 :         NCDF_ERR(status);
   10897             :     }
   10898             :     else
   10899             :     {
   10900          20 :         switch (nAttrType)
   10901             :         {
   10902          11 :             case NC_INT:
   10903             :             {
   10904             :                 int *pnTemp =
   10905          11 :                     static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10906          30 :                 for (size_t i = 0; i < nAttrLen; i++)
   10907             :                 {
   10908          19 :                     pnTemp[i] =
   10909          19 :                         static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   10910             :                 }
   10911          11 :                 status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
   10912             :                                         nAttrLen, pnTemp);
   10913          11 :                 NCDF_ERR(status);
   10914          11 :                 CPLFree(pnTemp);
   10915          11 :                 break;
   10916             :             }
   10917           0 :             case NC_UINT:
   10918             :             {
   10919             :                 unsigned int *punTemp = static_cast<unsigned int *>(
   10920           0 :                     CPLCalloc(nAttrLen, sizeof(unsigned int)));
   10921           0 :                 for (size_t i = 0; i < nAttrLen; i++)
   10922             :                 {
   10923           0 :                     punTemp[i] = static_cast<unsigned int>(
   10924           0 :                         strtol(aosValues[i], &pszTemp, 10));
   10925             :                 }
   10926           0 :                 status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
   10927             :                                          nAttrLen, punTemp);
   10928           0 :                 NCDF_ERR(status);
   10929           0 :                 CPLFree(punTemp);
   10930           0 :                 break;
   10931             :             }
   10932           6 :             case NC_FLOAT:
   10933             :             {
   10934             :                 float *pfTemp =
   10935           6 :                     static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10936          14 :                 for (size_t i = 0; i < nAttrLen; i++)
   10937             :                 {
   10938           8 :                     pfTemp[i] =
   10939           8 :                         static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
   10940             :                 }
   10941           6 :                 status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
   10942             :                                           nAttrLen, pfTemp);
   10943           6 :                 NCDF_ERR(status);
   10944           6 :                 CPLFree(pfTemp);
   10945           6 :                 break;
   10946             :             }
   10947           3 :             case NC_DOUBLE:
   10948             :             {
   10949             :                 double *pdfTemp =
   10950           3 :                     static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10951           9 :                 for (size_t i = 0; i < nAttrLen; i++)
   10952             :                 {
   10953           6 :                     pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
   10954             :                 }
   10955           3 :                 status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
   10956             :                                            NC_DOUBLE, nAttrLen, pdfTemp);
   10957           3 :                 NCDF_ERR(status);
   10958           3 :                 CPLFree(pdfTemp);
   10959           3 :                 break;
   10960             :             }
   10961           0 :             default:
   10962           0 :                 return CE_Failure;
   10963             :         }
   10964             :     }
   10965             : 
   10966         112 :     return CE_None;
   10967             : }
   10968             : 
   10969          78 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
   10970             : {
   10971             :     /* get var information */
   10972          78 :     int nVarDimId = -1;
   10973          78 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   10974          78 :     if (status != NC_NOERR || nVarDimId != 1)
   10975           0 :         return CE_Failure;
   10976             : 
   10977          78 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   10978          78 :     if (status != NC_NOERR)
   10979           0 :         return CE_Failure;
   10980             : 
   10981          78 :     nc_type nVarType = NC_NAT;
   10982          78 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   10983          78 :     if (status != NC_NOERR)
   10984           0 :         return CE_Failure;
   10985             : 
   10986          78 :     size_t nVarLen = 0;
   10987          78 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   10988          78 :     if (status != NC_NOERR)
   10989           0 :         return CE_Failure;
   10990             : 
   10991          78 :     size_t start[1] = {0};
   10992          78 :     size_t count[1] = {nVarLen};
   10993             : 
   10994             :     /* Allocate guaranteed minimum size */
   10995          78 :     size_t nVarValueSize = NCDF_MAX_STR_LEN;
   10996             :     char *pszVarValue =
   10997          78 :         static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
   10998          78 :     *pszVarValue = '\0';
   10999             : 
   11000          78 :     if (nVarLen == 0)
   11001             :     {
   11002             :         /* set return values */
   11003           1 :         *pszValue = pszVarValue;
   11004             : 
   11005           1 :         return CE_None;
   11006             :     }
   11007             : 
   11008          77 :     if (nVarLen > 1 && nVarType != NC_CHAR)
   11009          42 :         NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
   11010             : 
   11011          77 :     switch (nVarType)
   11012             :     {
   11013           0 :         case NC_CHAR:
   11014           0 :             nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
   11015           0 :             pszVarValue[nVarLen] = '\0';
   11016           0 :             break;
   11017           0 :         case NC_BYTE:
   11018             :         {
   11019             :             signed char *pscTemp = static_cast<signed char *>(
   11020           0 :                 CPLCalloc(nVarLen, sizeof(signed char)));
   11021           0 :             nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11022             :             char szTemp[256];
   11023           0 :             size_t m = 0;
   11024           0 :             for (; m < nVarLen - 1; m++)
   11025             :             {
   11026           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   11027           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11028             :             }
   11029           0 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   11030           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11031           0 :             CPLFree(pscTemp);
   11032           0 :             break;
   11033             :         }
   11034           0 :         case NC_SHORT:
   11035             :         {
   11036             :             short *psTemp =
   11037           0 :                 static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11038           0 :             nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
   11039             :             char szTemp[256];
   11040           0 :             size_t m = 0;
   11041           0 :             for (; m < nVarLen - 1; m++)
   11042             :             {
   11043           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   11044           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11045             :             }
   11046           0 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   11047           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11048           0 :             CPLFree(psTemp);
   11049           0 :             break;
   11050             :         }
   11051          21 :         case NC_INT:
   11052             :         {
   11053          21 :             int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11054          21 :             nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11055             :             char szTemp[256];
   11056          21 :             size_t m = 0;
   11057          44 :             for (; m < nVarLen - 1; m++)
   11058             :             {
   11059          23 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   11060          23 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11061             :             }
   11062          21 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   11063          21 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11064          21 :             CPLFree(pnTemp);
   11065          21 :             break;
   11066             :         }
   11067           8 :         case NC_FLOAT:
   11068             :         {
   11069             :             float *pfTemp =
   11070           8 :                 static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11071           8 :             nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11072             :             char szTemp[256];
   11073           8 :             size_t m = 0;
   11074         325 :             for (; m < nVarLen - 1; m++)
   11075             :             {
   11076         317 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   11077         317 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11078             :             }
   11079           8 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   11080           8 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11081           8 :             CPLFree(pfTemp);
   11082           8 :             break;
   11083             :         }
   11084          47 :         case NC_DOUBLE:
   11085             :         {
   11086             :             double *pdfTemp =
   11087          47 :                 static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11088          47 :             nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11089             :             char szTemp[256];
   11090          47 :             size_t m = 0;
   11091         225 :             for (; m < nVarLen - 1; m++)
   11092             :             {
   11093         178 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   11094         178 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11095             :             }
   11096          47 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   11097          47 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11098          47 :             CPLFree(pdfTemp);
   11099          47 :             break;
   11100             :         }
   11101           0 :         case NC_STRING:
   11102             :         {
   11103             :             char **ppszTemp =
   11104           0 :                 static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
   11105           0 :             nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
   11106           0 :             size_t m = 0;
   11107           0 :             for (; m < nVarLen - 1; m++)
   11108             :             {
   11109           0 :                 NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11110           0 :                 NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
   11111             :             }
   11112           0 :             NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11113           0 :             nc_free_string(nVarLen, ppszTemp);
   11114           0 :             CPLFree(ppszTemp);
   11115           0 :             break;
   11116             :         }
   11117           0 :         case NC_UBYTE:
   11118             :         {
   11119             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   11120           0 :                 CPLCalloc(nVarLen, sizeof(unsigned char)));
   11121           0 :             nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
   11122             :             char szTemp[256];
   11123           0 :             size_t m = 0;
   11124           0 :             for (; m < nVarLen - 1; m++)
   11125             :             {
   11126           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   11127           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11128             :             }
   11129           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   11130           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11131           0 :             CPLFree(pucTemp);
   11132           0 :             break;
   11133             :         }
   11134           0 :         case NC_USHORT:
   11135             :         {
   11136             :             unsigned short *pusTemp = static_cast<unsigned short *>(
   11137           0 :                 CPLCalloc(nVarLen, sizeof(unsigned short)));
   11138           0 :             nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
   11139             :             char szTemp[256];
   11140           0 :             size_t m = 0;
   11141           0 :             for (; m < nVarLen - 1; m++)
   11142             :             {
   11143           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   11144           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11145             :             }
   11146           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   11147           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11148           0 :             CPLFree(pusTemp);
   11149           0 :             break;
   11150             :         }
   11151           0 :         case NC_UINT:
   11152             :         {
   11153             :             unsigned int *punTemp = static_cast<unsigned int *>(
   11154           0 :                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11155           0 :             nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
   11156             :             char szTemp[256];
   11157           0 :             size_t m = 0;
   11158           0 :             for (; m < nVarLen - 1; m++)
   11159             :             {
   11160           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   11161           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11162             :             }
   11163           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   11164           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11165           0 :             CPLFree(punTemp);
   11166           0 :             break;
   11167             :         }
   11168           1 :         case NC_INT64:
   11169             :         {
   11170             :             long long *pnTemp =
   11171           1 :                 static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
   11172           1 :             nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
   11173             :             char szTemp[256];
   11174           1 :             size_t m = 0;
   11175           2 :             for (; m < nVarLen - 1; m++)
   11176             :             {
   11177           1 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
   11178           1 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11179             :             }
   11180           1 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
   11181           1 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11182           1 :             CPLFree(pnTemp);
   11183           1 :             break;
   11184             :         }
   11185           0 :         case NC_UINT64:
   11186             :         {
   11187             :             unsigned long long *pnTemp = static_cast<unsigned long long *>(
   11188           0 :                 CPLCalloc(nVarLen, sizeof(unsigned long long)));
   11189           0 :             nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
   11190             :             char szTemp[256];
   11191           0 :             size_t m = 0;
   11192           0 :             for (; m < nVarLen - 1; m++)
   11193             :             {
   11194           0 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
   11195           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11196             :             }
   11197           0 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
   11198           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11199           0 :             CPLFree(pnTemp);
   11200           0 :             break;
   11201             :         }
   11202           0 :         default:
   11203           0 :             CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
   11204             :                      nVarType);
   11205           0 :             CPLFree(pszVarValue);
   11206           0 :             pszVarValue = nullptr;
   11207           0 :             break;
   11208             :     }
   11209             : 
   11210          77 :     if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
   11211          42 :         NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
   11212             : 
   11213             :     /* set return values */
   11214          77 :     *pszValue = pszVarValue;
   11215             : 
   11216          77 :     return CE_None;
   11217             : }
   11218             : 
   11219           8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
   11220             : {
   11221           8 :     if (EQUAL(pszValue, ""))
   11222           0 :         return CE_Failure;
   11223             : 
   11224             :     /* get var information */
   11225           8 :     int nVarDimId = -1;
   11226           8 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   11227           8 :     if (status != NC_NOERR || nVarDimId != 1)
   11228           0 :         return CE_Failure;
   11229             : 
   11230           8 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11231           8 :     if (status != NC_NOERR)
   11232           0 :         return CE_Failure;
   11233             : 
   11234           8 :     nc_type nVarType = NC_CHAR;
   11235           8 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11236           8 :     if (status != NC_NOERR)
   11237           0 :         return CE_Failure;
   11238             : 
   11239           8 :     size_t nVarLen = 0;
   11240           8 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11241           8 :     if (status != NC_NOERR)
   11242           0 :         return CE_Failure;
   11243             : 
   11244           8 :     size_t start[1] = {0};
   11245           8 :     size_t count[1] = {nVarLen};
   11246             : 
   11247             :     /* get the values as tokens */
   11248          16 :     CPLStringList aosValues = NCDFTokenizeArray(pszValue);
   11249           8 :     if (aosValues.empty())
   11250           0 :         return CE_Failure;
   11251             : 
   11252           8 :     nVarLen = aosValues.size();
   11253             : 
   11254             :     /* now write the data */
   11255           8 :     if (nVarType == NC_CHAR)
   11256             :     {
   11257           0 :         status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
   11258           0 :         NCDF_ERR(status);
   11259             :     }
   11260             :     else
   11261             :     {
   11262           8 :         switch (nVarType)
   11263             :         {
   11264           0 :             case NC_BYTE:
   11265             :             {
   11266             :                 signed char *pscTemp = static_cast<signed char *>(
   11267           0 :                     CPLCalloc(nVarLen, sizeof(signed char)));
   11268           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11269             :                 {
   11270           0 :                     char *pszTemp = nullptr;
   11271           0 :                     pscTemp[i] = static_cast<signed char>(
   11272           0 :                         strtol(aosValues[i], &pszTemp, 10));
   11273             :                 }
   11274             :                 status =
   11275           0 :                     nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11276           0 :                 NCDF_ERR(status);
   11277           0 :                 CPLFree(pscTemp);
   11278           0 :                 break;
   11279             :             }
   11280           0 :             case NC_SHORT:
   11281             :             {
   11282             :                 short *psTemp =
   11283           0 :                     static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11284           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11285             :                 {
   11286           0 :                     char *pszTemp = nullptr;
   11287           0 :                     psTemp[i] =
   11288           0 :                         static_cast<short>(strtol(aosValues[i], &pszTemp, 10));
   11289             :                 }
   11290             :                 status =
   11291           0 :                     nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
   11292           0 :                 NCDF_ERR(status);
   11293           0 :                 CPLFree(psTemp);
   11294           0 :                 break;
   11295             :             }
   11296           3 :             case NC_INT:
   11297             :             {
   11298             :                 int *pnTemp =
   11299           3 :                     static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11300          11 :                 for (size_t i = 0; i < nVarLen; i++)
   11301             :                 {
   11302           8 :                     char *pszTemp = nullptr;
   11303           8 :                     pnTemp[i] =
   11304           8 :                         static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   11305             :                 }
   11306           3 :                 status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11307           3 :                 NCDF_ERR(status);
   11308           3 :                 CPLFree(pnTemp);
   11309           3 :                 break;
   11310             :             }
   11311           0 :             case NC_FLOAT:
   11312             :             {
   11313             :                 float *pfTemp =
   11314           0 :                     static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11315           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11316             :                 {
   11317           0 :                     char *pszTemp = nullptr;
   11318           0 :                     pfTemp[i] =
   11319           0 :                         static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
   11320             :                 }
   11321             :                 status =
   11322           0 :                     nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11323           0 :                 NCDF_ERR(status);
   11324           0 :                 CPLFree(pfTemp);
   11325           0 :                 break;
   11326             :             }
   11327           5 :             case NC_DOUBLE:
   11328             :             {
   11329             :                 double *pdfTemp =
   11330           5 :                     static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11331          19 :                 for (size_t i = 0; i < nVarLen; i++)
   11332             :                 {
   11333          14 :                     char *pszTemp = nullptr;
   11334          14 :                     pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
   11335             :                 }
   11336             :                 status =
   11337           5 :                     nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11338           5 :                 NCDF_ERR(status);
   11339           5 :                 CPLFree(pdfTemp);
   11340           5 :                 break;
   11341             :             }
   11342           0 :             default:
   11343             :             {
   11344           0 :                 int nTmpFormat = 0;
   11345           0 :                 status = nc_inq_format(nCdfId, &nTmpFormat);
   11346           0 :                 NCDF_ERR(status);
   11347           0 :                 if (nTmpFormat == NCDF_FORMAT_NC4)
   11348             :                 {
   11349           0 :                     switch (nVarType)
   11350             :                     {
   11351           0 :                         case NC_STRING:
   11352             :                         {
   11353           0 :                             status = nc_put_vara_string(
   11354             :                                 nCdfId, nVarId, start, count,
   11355           0 :                                 const_cast<const char **>(aosValues.List()));
   11356           0 :                             NCDF_ERR(status);
   11357           0 :                             break;
   11358             :                         }
   11359           0 :                         case NC_UBYTE:
   11360             :                         {
   11361             :                             unsigned char *pucTemp =
   11362             :                                 static_cast<unsigned char *>(
   11363           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned char)));
   11364           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11365             :                             {
   11366           0 :                                 char *pszTemp = nullptr;
   11367           0 :                                 pucTemp[i] = static_cast<unsigned char>(
   11368           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11369             :                             }
   11370           0 :                             status = nc_put_vara_uchar(nCdfId, nVarId, start,
   11371             :                                                        count, pucTemp);
   11372           0 :                             NCDF_ERR(status);
   11373           0 :                             CPLFree(pucTemp);
   11374           0 :                             break;
   11375             :                         }
   11376           0 :                         case NC_USHORT:
   11377             :                         {
   11378             :                             unsigned short *pusTemp =
   11379             :                                 static_cast<unsigned short *>(
   11380           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned short)));
   11381           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11382             :                             {
   11383           0 :                                 char *pszTemp = nullptr;
   11384           0 :                                 pusTemp[i] = static_cast<unsigned short>(
   11385           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11386             :                             }
   11387           0 :                             status = nc_put_vara_ushort(nCdfId, nVarId, start,
   11388             :                                                         count, pusTemp);
   11389           0 :                             NCDF_ERR(status);
   11390           0 :                             CPLFree(pusTemp);
   11391           0 :                             break;
   11392             :                         }
   11393           0 :                         case NC_UINT:
   11394             :                         {
   11395             :                             unsigned int *punTemp = static_cast<unsigned int *>(
   11396           0 :                                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11397           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11398             :                             {
   11399           0 :                                 char *pszTemp = nullptr;
   11400           0 :                                 punTemp[i] = static_cast<unsigned int>(
   11401           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11402             :                             }
   11403           0 :                             status = nc_put_vara_uint(nCdfId, nVarId, start,
   11404             :                                                       count, punTemp);
   11405           0 :                             NCDF_ERR(status);
   11406           0 :                             CPLFree(punTemp);
   11407           0 :                             break;
   11408             :                         }
   11409           0 :                         default:
   11410           0 :                             return CE_Failure;
   11411             :                     }
   11412             :                 }
   11413           0 :                 break;
   11414             :             }
   11415             :         }
   11416             :     }
   11417             : 
   11418           8 :     return CE_None;
   11419             : }
   11420             : 
   11421             : /************************************************************************/
   11422             : /*                       GetDefaultNoDataValue()                        */
   11423             : /************************************************************************/
   11424             : 
   11425         200 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
   11426             :                                  bool &bGotNoData)
   11427             : 
   11428             : {
   11429         200 :     int nNoFill = 0;
   11430         200 :     double dfNoData = 0.0;
   11431             : 
   11432         200 :     switch (nVarType)
   11433             :     {
   11434           0 :         case NC_CHAR:
   11435             :         case NC_BYTE:
   11436             :         case NC_UBYTE:
   11437             :             // Don't do default fill-values for bytes, too risky.
   11438             :             // This function should not be called in those cases.
   11439           0 :             CPLAssert(false);
   11440             :             break;
   11441          24 :         case NC_SHORT:
   11442             :         {
   11443          24 :             short nFillVal = 0;
   11444          24 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11445             :                 NC_NOERR)
   11446             :             {
   11447          24 :                 if (!nNoFill)
   11448             :                 {
   11449          23 :                     bGotNoData = true;
   11450          23 :                     dfNoData = nFillVal;
   11451             :                 }
   11452             :             }
   11453             :             else
   11454           0 :                 dfNoData = NC_FILL_SHORT;
   11455          24 :             break;
   11456             :         }
   11457          26 :         case NC_INT:
   11458             :         {
   11459          26 :             int nFillVal = 0;
   11460          26 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11461             :                 NC_NOERR)
   11462             :             {
   11463          26 :                 if (!nNoFill)
   11464             :                 {
   11465          25 :                     bGotNoData = true;
   11466          25 :                     dfNoData = nFillVal;
   11467             :                 }
   11468             :             }
   11469             :             else
   11470           0 :                 dfNoData = NC_FILL_INT;
   11471          26 :             break;
   11472             :         }
   11473          83 :         case NC_FLOAT:
   11474             :         {
   11475          83 :             float fFillVal = 0;
   11476          83 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
   11477             :                 NC_NOERR)
   11478             :             {
   11479          83 :                 if (!nNoFill)
   11480             :                 {
   11481          79 :                     bGotNoData = true;
   11482          79 :                     dfNoData = fFillVal;
   11483             :                 }
   11484             :             }
   11485             :             else
   11486           0 :                 dfNoData = NC_FILL_FLOAT;
   11487          83 :             break;
   11488             :         }
   11489          34 :         case NC_DOUBLE:
   11490             :         {
   11491          34 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
   11492             :                 NC_NOERR)
   11493             :             {
   11494          34 :                 if (!nNoFill)
   11495             :                 {
   11496          34 :                     bGotNoData = true;
   11497             :                 }
   11498             :             }
   11499             :             else
   11500           0 :                 dfNoData = NC_FILL_DOUBLE;
   11501          34 :             break;
   11502             :         }
   11503           7 :         case NC_USHORT:
   11504             :         {
   11505           7 :             unsigned short nFillVal = 0;
   11506           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11507             :                 NC_NOERR)
   11508             :             {
   11509           7 :                 if (!nNoFill)
   11510             :                 {
   11511           7 :                     bGotNoData = true;
   11512           7 :                     dfNoData = nFillVal;
   11513             :                 }
   11514             :             }
   11515             :             else
   11516           0 :                 dfNoData = NC_FILL_USHORT;
   11517           7 :             break;
   11518             :         }
   11519           7 :         case NC_UINT:
   11520             :         {
   11521           7 :             unsigned int nFillVal = 0;
   11522           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11523             :                 NC_NOERR)
   11524             :             {
   11525           7 :                 if (!nNoFill)
   11526             :                 {
   11527           7 :                     bGotNoData = true;
   11528           7 :                     dfNoData = nFillVal;
   11529             :                 }
   11530             :             }
   11531             :             else
   11532           0 :                 dfNoData = NC_FILL_UINT;
   11533           7 :             break;
   11534             :         }
   11535          19 :         default:
   11536          19 :             dfNoData = 0.0;
   11537          19 :             break;
   11538             :     }
   11539             : 
   11540         200 :     return dfNoData;
   11541             : }
   11542             : 
   11543             : /************************************************************************/
   11544             : /*                  NCDFGetDefaultNoDataValueAsInt64()                  */
   11545             : /************************************************************************/
   11546             : 
   11547           2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
   11548             :                                          bool &bGotNoData)
   11549             : 
   11550             : {
   11551           2 :     int nNoFill = 0;
   11552           2 :     long long nFillVal = 0;
   11553           2 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11554             :     {
   11555           2 :         if (!nNoFill)
   11556             :         {
   11557           2 :             bGotNoData = true;
   11558           2 :             return static_cast<int64_t>(nFillVal);
   11559             :         }
   11560             :     }
   11561             :     else
   11562           0 :         return static_cast<int64_t>(NC_FILL_INT64);
   11563           0 :     return 0;
   11564             : }
   11565             : 
   11566             : /************************************************************************/
   11567             : /*                 NCDFGetDefaultNoDataValueAsUInt64()                  */
   11568             : /************************************************************************/
   11569             : 
   11570           1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
   11571             :                                            bool &bGotNoData)
   11572             : 
   11573             : {
   11574           1 :     int nNoFill = 0;
   11575           1 :     unsigned long long nFillVal = 0;
   11576           1 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11577             :     {
   11578           1 :         if (!nNoFill)
   11579             :         {
   11580           1 :             bGotNoData = true;
   11581           1 :             return static_cast<uint64_t>(nFillVal);
   11582             :         }
   11583             :     }
   11584             :     else
   11585           0 :         return static_cast<uint64_t>(NC_FILL_UINT64);
   11586           0 :     return 0;
   11587             : }
   11588             : 
   11589       12282 : static int NCDFDoesVarContainAttribVal(int nCdfId,
   11590             :                                        const char *const *papszAttribNames,
   11591             :                                        const char *const *papszAttribValues,
   11592             :                                        int nVarId, const char *pszVarName,
   11593             :                                        bool bStrict = true)
   11594             : {
   11595       12282 :     if (nVarId == -1 && pszVarName != nullptr)
   11596        8327 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11597             : 
   11598       12282 :     if (nVarId == -1)
   11599         950 :         return -1;
   11600             : 
   11601       11332 :     bool bFound = false;
   11602       52814 :     for (int i = 0; !bFound && papszAttribNames != nullptr &&
   11603       50321 :                     papszAttribNames[i] != nullptr;
   11604             :          i++)
   11605             :     {
   11606       41482 :         char *pszTemp = nullptr;
   11607       41482 :         if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
   11608       59596 :                 CE_None &&
   11609       18114 :             pszTemp != nullptr)
   11610             :         {
   11611       18114 :             if (bStrict)
   11612             :             {
   11613       18114 :                 if (EQUAL(pszTemp, papszAttribValues[i]))
   11614        2493 :                     bFound = true;
   11615             :             }
   11616             :             else
   11617             :             {
   11618           0 :                 if (EQUALN(pszTemp, papszAttribValues[i],
   11619             :                            strlen(papszAttribValues[i])))
   11620           0 :                     bFound = true;
   11621             :             }
   11622       18114 :             CPLFree(pszTemp);
   11623             :         }
   11624             :     }
   11625       11332 :     return bFound;
   11626             : }
   11627             : 
   11628        2139 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
   11629             :                                         const char *const *papszAttribValues,
   11630             :                                         int nVarId, const char *pszVarName,
   11631             :                                         int bStrict = true)
   11632             : {
   11633        2139 :     if (nVarId == -1 && pszVarName != nullptr)
   11634        1585 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11635             : 
   11636        2139 :     if (nVarId == -1)
   11637           0 :         return -1;
   11638             : 
   11639        2139 :     bool bFound = false;
   11640        2139 :     char *pszTemp = nullptr;
   11641        2518 :     if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
   11642         379 :         pszTemp == nullptr)
   11643        1760 :         return FALSE;
   11644             : 
   11645        7703 :     for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
   11646             :     {
   11647        7324 :         if (bStrict)
   11648             :         {
   11649        7296 :             if (EQUAL(pszTemp, papszAttribValues[i]))
   11650          31 :                 bFound = true;
   11651             :         }
   11652             :         else
   11653             :         {
   11654          28 :             if (EQUALN(pszTemp, papszAttribValues[i],
   11655             :                        strlen(papszAttribValues[i])))
   11656           0 :                 bFound = true;
   11657             :         }
   11658             :     }
   11659             : 
   11660         379 :     CPLFree(pszTemp);
   11661             : 
   11662         379 :     return bFound;
   11663             : }
   11664             : 
   11665         948 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
   11666             : {
   11667         948 :     if (papszName == nullptr || EQUAL(papszName, ""))
   11668           0 :         return false;
   11669             : 
   11670        2560 :     for (int i = 0; papszValues && papszValues[i]; ++i)
   11671             :     {
   11672        1756 :         if (EQUAL(papszName, papszValues[i]))
   11673         144 :             return true;
   11674             :     }
   11675             : 
   11676         804 :     return false;
   11677             : }
   11678             : 
   11679             : // Test that a variable is longitude/latitude coordinate,
   11680             : // following CF 4.1 and 4.2.
   11681        4132 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
   11682             : {
   11683             :     // Check for matching attributes.
   11684        4132 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
   11685             :                                            papszCFLongitudeAttribValues, nVarId,
   11686             :                                            pszVarName);
   11687             :     // If not found using attributes then check using var name
   11688             :     // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
   11689        4132 :     if (bVal == -1)
   11690             :     {
   11691         304 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11692             :                    "STRICT"))
   11693         304 :             bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
   11694             :         else
   11695           0 :             bVal = FALSE;
   11696             :     }
   11697        3828 :     else if (bVal)
   11698             :     {
   11699             :         // Check that the units is not 'm' or '1'. See #6759
   11700         805 :         char *pszTemp = nullptr;
   11701        1187 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11702         382 :             pszTemp != nullptr)
   11703             :         {
   11704         382 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11705          97 :                 bVal = false;
   11706         382 :             CPLFree(pszTemp);
   11707             :         }
   11708             :     }
   11709             : 
   11710        4132 :     return CPL_TO_BOOL(bVal);
   11711             : }
   11712             : 
   11713        2374 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
   11714             : {
   11715        2374 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
   11716             :                                            papszCFLatitudeAttribValues, nVarId,
   11717             :                                            pszVarName);
   11718        2374 :     if (bVal == -1)
   11719             :     {
   11720         175 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11721             :                    "STRICT"))
   11722         175 :             bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
   11723             :         else
   11724           0 :             bVal = FALSE;
   11725             :     }
   11726        2199 :     else if (bVal)
   11727             :     {
   11728             :         // Check that the units is not 'm' or '1'. See #6759
   11729         556 :         char *pszTemp = nullptr;
   11730         698 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11731         142 :             pszTemp != nullptr)
   11732             :         {
   11733         142 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11734          36 :                 bVal = false;
   11735         142 :             CPLFree(pszTemp);
   11736             :         }
   11737             :     }
   11738             : 
   11739        2374 :     return CPL_TO_BOOL(bVal);
   11740             : }
   11741             : 
   11742        2579 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
   11743             : {
   11744        2579 :     int bVal = NCDFDoesVarContainAttribVal(
   11745             :         nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
   11746             :         nVarId, pszVarName);
   11747        2579 :     if (bVal == -1)
   11748             :     {
   11749         298 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11750             :                    "STRICT"))
   11751         298 :             bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
   11752             :         else
   11753           0 :             bVal = FALSE;
   11754             :     }
   11755        2281 :     else if (bVal)
   11756             :     {
   11757             :         // Check that the units is not '1'
   11758         460 :         char *pszTemp = nullptr;
   11759         690 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11760         230 :             pszTemp != nullptr)
   11761             :         {
   11762         230 :             if (EQUAL(pszTemp, "1"))
   11763           5 :                 bVal = false;
   11764         230 :             CPLFree(pszTemp);
   11765             :         }
   11766             :     }
   11767             : 
   11768        2579 :     return CPL_TO_BOOL(bVal);
   11769             : }
   11770             : 
   11771        1817 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
   11772             : {
   11773        1817 :     int bVal = NCDFDoesVarContainAttribVal(
   11774             :         nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
   11775             :         nVarId, pszVarName);
   11776        1817 :     if (bVal == -1)
   11777             :     {
   11778         171 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11779             :                    "STRICT"))
   11780         171 :             bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
   11781             :         else
   11782           0 :             bVal = FALSE;
   11783             :     }
   11784        1646 :     else if (bVal)
   11785             :     {
   11786             :         // Check that the units is not '1'
   11787         454 :         char *pszTemp = nullptr;
   11788         679 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11789         225 :             pszTemp != nullptr)
   11790             :         {
   11791         225 :             if (EQUAL(pszTemp, "1"))
   11792           5 :                 bVal = false;
   11793         225 :             CPLFree(pszTemp);
   11794             :         }
   11795             :     }
   11796             : 
   11797        1817 :     return CPL_TO_BOOL(bVal);
   11798             : }
   11799             : 
   11800             : /* test that a variable is a vertical coordinate, following CF 4.3 */
   11801        1121 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
   11802             : {
   11803             :     /* check for matching attributes */
   11804        1121 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
   11805             :                                     papszCFVerticalAttribValues, nVarId,
   11806        1121 :                                     pszVarName))
   11807         111 :         return true;
   11808             :     /* check for matching units */
   11809        1010 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11810             :                                           papszCFVerticalUnitsValues, nVarId,
   11811        1010 :                                           pszVarName))
   11812          31 :         return true;
   11813             :     /* check for matching standard name */
   11814         979 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
   11815             :                                           papszCFVerticalStandardNameValues,
   11816         979 :                                           nVarId, pszVarName))
   11817           0 :         return true;
   11818             :     else
   11819         979 :         return false;
   11820             : }
   11821             : 
   11822             : /* test that a variable is a time coordinate, following CF 4.4 */
   11823         259 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
   11824             : {
   11825             :     /* check for matching attributes */
   11826         259 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
   11827             :                                     papszCFTimeAttribValues, nVarId,
   11828         259 :                                     pszVarName))
   11829         109 :         return true;
   11830             :     /* check for matching units */
   11831         150 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11832             :                                           papszCFTimeUnitsValues, nVarId,
   11833         150 :                                           pszVarName, false))
   11834           0 :         return true;
   11835             :     else
   11836         150 :         return false;
   11837             : }
   11838             : 
   11839             : // Parse a string, and return as a string list.
   11840             : // If it is an array of the form {a,b}, then tokenize it.
   11841             : // Otherwise, return a copy.
   11842         200 : static CPLStringList NCDFTokenizeArray(const char *pszValue)
   11843             : {
   11844         200 :     if (pszValue == nullptr || EQUAL(pszValue, ""))
   11845          59 :         return CPLStringList();
   11846             : 
   11847         282 :     CPLStringList aosValues;
   11848         141 :     const int nLen = static_cast<int>(strlen(pszValue));
   11849             : 
   11850         141 :     if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
   11851             :     {
   11852          41 :         char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
   11853          41 :         strncpy(pszTemp, pszValue + 1, nLen - 2);
   11854          41 :         pszTemp[nLen - 2] = '\0';
   11855             :         aosValues.Assign(
   11856          41 :             CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS));
   11857          41 :         CPLFree(pszTemp);
   11858             :     }
   11859             :     else
   11860             :     {
   11861         100 :         aosValues.AddString(pszValue);
   11862             :     }
   11863             : 
   11864         141 :     return aosValues;
   11865             : }
   11866             : 
   11867             : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
   11868             : // Leading slash is optional.
   11869         436 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
   11870             :                                  int *pnGroupId, int *pnVarId)
   11871             : {
   11872         436 :     *pnGroupId = -1;
   11873         436 :     *pnVarId = -1;
   11874             : 
   11875             :     // Open group.
   11876         872 :     std::string osGroupFullName = CPLGetPathSafe(pszSubdatasetName);
   11877             :     // Add a leading slash if needed.
   11878         436 :     if (osGroupFullName.empty() || osGroupFullName[0] != '/')
   11879             :     {
   11880         419 :         osGroupFullName = "/" + osGroupFullName;
   11881             :     }
   11882             :     // Detect root group.
   11883         436 :     if (osGroupFullName == "/")
   11884             :     {
   11885         419 :         *pnGroupId = nCdfId;
   11886             :     }
   11887             :     else
   11888             :     {
   11889             :         int status =
   11890          17 :             nc_inq_grp_full_ncid(nCdfId, osGroupFullName.c_str(), pnGroupId);
   11891          17 :         NCDF_ERR_RET(status);
   11892             :     }
   11893             : 
   11894             :     // Open var.
   11895         436 :     const char *pszVarName = CPLGetFilename(pszSubdatasetName);
   11896         436 :     NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
   11897             : 
   11898         436 :     return CE_None;
   11899             : }
   11900             : 
   11901             : // Get all dimensions visible from a given NetCDF (or group) ID and any of
   11902             : // its parents.
   11903         372 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
   11904             : {
   11905         372 :     int nDims = 0;
   11906         372 :     int *panDimIds = nullptr;
   11907         372 :     NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
   11908             : 
   11909         372 :     panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
   11910             : 
   11911         372 :     int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
   11912         372 :     if (status != NC_NOERR)
   11913           0 :         CPLFree(panDimIds);
   11914         372 :     NCDF_ERR_RET(status);
   11915             : 
   11916         372 :     *pnDims = nDims;
   11917         372 :     *ppanDimIds = panDimIds;
   11918             : 
   11919         372 :     return CE_None;
   11920             : }
   11921             : 
   11922             : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
   11923             : // Consider only direct children, does not get children of children.
   11924        3294 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
   11925             :                                int **ppanSubGroupIds)
   11926             : {
   11927        3294 :     *pnSubGroups = 0;
   11928        3294 :     *ppanSubGroupIds = nullptr;
   11929             : 
   11930             :     int nSubGroups;
   11931        3294 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
   11932             :     int *panSubGroupIds =
   11933        3294 :         static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
   11934        3294 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
   11935        3294 :     *pnSubGroups = nSubGroups;
   11936        3294 :     *ppanSubGroupIds = panSubGroupIds;
   11937             : 
   11938        3294 :     return CE_None;
   11939             : }
   11940             : 
   11941             : // Get the full name of a given NetCDF (or group) ID
   11942             : // (e.g. /group1/group2/.../groupn).
   11943             : // bNC3Compat remove the leading slash for top-level variables for
   11944             : // backward compatibility (top-level variables are the ones in the root group).
   11945       18711 : static CPLErr NCDFGetGroupFullName(int nGroupId, std::string &osFullName,
   11946             :                                    bool bNC3Compat)
   11947             : {
   11948       18711 :     osFullName = "";
   11949             : 
   11950             :     size_t nFullNameLen;
   11951       18711 :     NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
   11952       18711 :     osFullName.resize(nFullNameLen);
   11953             : 
   11954             :     const int status =
   11955       18711 :         nc_inq_grpname_full(nGroupId, &nFullNameLen, osFullName.data());
   11956       18711 :     if (status != NC_NOERR)
   11957             :     {
   11958           0 :         osFullName = "";
   11959           0 :         NCDF_ERR_RET(status);
   11960             :     }
   11961             : 
   11962       18711 :     if (bNC3Compat && osFullName == "/")
   11963        8352 :         osFullName = "";
   11964             : 
   11965       18711 :     return CE_None;
   11966             : }
   11967             : 
   11968       10148 : CPLString NCDFGetGroupFullName(int nGroupId)
   11969             : {
   11970       10148 :     CPLString osFullName;
   11971       10148 :     NCDFGetGroupFullName(nGroupId, osFullName, false);
   11972             : 
   11973       10148 :     return osFullName;
   11974             : }
   11975             : 
   11976             : // Get the full name of a given NetCDF variable ID
   11977             : // (e.g. /group1/group2/.../groupn/var).
   11978             : // Handle also NC_GLOBAL as nVarId.
   11979             : // bNC3Compat remove the leading slash for top-level variables for
   11980             : // backward compatibility (top-level variables are the ones in the root group).
   11981        8512 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId,
   11982             :                                  std::string &osFullName, bool bNC3Compat)
   11983             : {
   11984        8512 :     osFullName = "";
   11985       17024 :     std::string osGroupFullName;
   11986        8512 :     ERR_RET(NCDFGetGroupFullName(nGroupId, osGroupFullName, bNC3Compat));
   11987             :     char szVarName[NC_MAX_NAME + 1];
   11988        8512 :     if (nVarId == NC_GLOBAL)
   11989             :     {
   11990        1144 :         strcpy(szVarName, "NC_GLOBAL");
   11991             :     }
   11992             :     else
   11993             :     {
   11994        7368 :         int status = nc_inq_varname(nGroupId, nVarId, szVarName);
   11995        7368 :         if (status != NC_NOERR)
   11996             :         {
   11997           0 :             NCDF_ERR_RET(status);
   11998             :         }
   11999             :     }
   12000        8512 :     const char *pszSep = "/";
   12001        8512 :     if (osGroupFullName.empty() || osGroupFullName == "/")
   12002        8303 :         pszSep = "";
   12003             :     osFullName =
   12004        8512 :         CPLOPrintf("%s%s%s", osGroupFullName.c_str(), pszSep, szVarName);
   12005             : 
   12006        8512 :     return CE_None;
   12007             : }
   12008             : 
   12009             : // Get the NetCDF root group ID of a given group ID.
   12010           0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
   12011             : {
   12012           0 :     *pnRootGroupId = -1;
   12013             :     // Recurse on parent group.
   12014             :     int nParentGroupId;
   12015           0 :     int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
   12016           0 :     if (status == NC_NOERR)
   12017           0 :         return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
   12018           0 :     else if (status != NC_ENOGRP)
   12019           0 :         NCDF_ERR_RET(status);
   12020             :     else  // No more parent group.
   12021             :     {
   12022           0 :         *pnRootGroupId = nStartGroupId;
   12023             :     }
   12024             : 
   12025           0 :     return CE_None;
   12026             : }
   12027             : 
   12028             : // Implementation of NCDFResolveVar/Att.
   12029       13576 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
   12030             :                               const char *pszAtt, int *pnGroupId, int *pnId,
   12031             :                               bool bMandatory)
   12032             : {
   12033       13576 :     if (!pszVar && !pszAtt)
   12034             :     {
   12035           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   12036             :                  "pszVar and pszAtt NCDFResolveElem() args are both null.");
   12037           0 :         return CE_Failure;
   12038             :     }
   12039             : 
   12040             :     enum
   12041             :     {
   12042             :         NCRM_PARENT,
   12043             :         NCRM_WIDTH_WISE
   12044       13576 :     } eNCResolveMode = NCRM_PARENT;
   12045             : 
   12046       27152 :     std::queue<int> aoQueueGroupIdsToVisit;
   12047       13576 :     aoQueueGroupIdsToVisit.push(nStartGroupId);
   12048             : 
   12049       15426 :     while (!aoQueueGroupIdsToVisit.empty())
   12050             :     {
   12051             :         // Get the first group of the FIFO queue.
   12052       13770 :         *pnGroupId = aoQueueGroupIdsToVisit.front();
   12053       13770 :         aoQueueGroupIdsToVisit.pop();
   12054             : 
   12055             :         // Look if this group contains the searched element.
   12056             :         int status;
   12057       13770 :         if (pszVar)
   12058       13545 :             status = nc_inq_varid(*pnGroupId, pszVar, pnId);
   12059             :         else  // pszAtt != nullptr.
   12060         225 :             status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
   12061             : 
   12062       13770 :         if (status == NC_NOERR)
   12063             :         {
   12064       11920 :             return CE_None;
   12065             :         }
   12066        1850 :         else if ((pszVar && status != NC_ENOTVAR) ||
   12067         222 :                  (pszAtt && status != NC_ENOTATT))
   12068             :         {
   12069           0 :             NCDF_ERR(status);
   12070             :         }
   12071             :         // Element not found, in NC4 case we must search in other groups
   12072             :         // following the CF logic.
   12073             : 
   12074             :         // The first resolve mode consists to search on parent groups.
   12075        1850 :         if (eNCResolveMode == NCRM_PARENT)
   12076             :         {
   12077        1731 :             int nParentGroupId = -1;
   12078        1731 :             int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
   12079        1731 :             if (status2 == NC_NOERR)
   12080          62 :                 aoQueueGroupIdsToVisit.push(nParentGroupId);
   12081        1669 :             else if (status2 != NC_ENOGRP)
   12082           0 :                 NCDF_ERR(status2);
   12083        1669 :             else if (pszVar)
   12084             :                 // When resolving a variable, if there is no more
   12085             :                 // parent group then we switch to width-wise search mode
   12086             :                 // starting from the latest found parent group.
   12087        1450 :                 eNCResolveMode = NCRM_WIDTH_WISE;
   12088             :         }
   12089             : 
   12090             :         // The second resolve mode is a width-wise search.
   12091        1850 :         if (eNCResolveMode == NCRM_WIDTH_WISE)
   12092             :         {
   12093             :             // Enqueue all direct sub-groups.
   12094        1569 :             int nSubGroups = 0;
   12095        1569 :             int *panSubGroupIds = nullptr;
   12096        1569 :             NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
   12097        1701 :             for (int i = 0; i < nSubGroups; i++)
   12098         132 :                 aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
   12099        1569 :             CPLFree(panSubGroupIds);
   12100             :         }
   12101             :     }
   12102             : 
   12103        1656 :     if (bMandatory)
   12104             :     {
   12105           0 :         std::string osStartGroupFullName;
   12106           0 :         NCDFGetGroupFullName(nStartGroupId, osStartGroupFullName);
   12107           0 :         CPLError(CE_Failure, CPLE_AppDefined,
   12108             :                  "Cannot resolve mandatory %s %s from group %s",
   12109             :                  (pszVar ? pszVar : pszAtt),
   12110             :                  (pszVar ? "variable" : "attribute"),
   12111             :                  osStartGroupFullName.c_str());
   12112             :     }
   12113             : 
   12114        1656 :     *pnGroupId = -1;
   12115        1656 :     *pnId = -1;
   12116        1656 :     return CE_Failure;
   12117             : }
   12118             : 
   12119             : // Resolve a variable name from a given starting group following the CF logic:
   12120             : // - if var name is an absolute path then directly open it
   12121             : // - first search in the starting group and its parent groups
   12122             : // - then if there is no more parent group we switch to a width-wise search
   12123             : //   mode starting from the latest found parent group.
   12124             : // The full CF logic is described here:
   12125             : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
   12126             : // If bMandatory then print an error if resolving fails.
   12127             : // TODO: implement support of relative paths.
   12128             : // TODO: to follow strictly the CF logic, when searching for a coordinate
   12129             : //       variable, we must stop the parent search mode once the corresponding
   12130             : //       dimension is found and start the width-wise search from this group.
   12131             : // TODO: to follow strictly the CF logic, when searching in width-wise mode
   12132             : //       we should skip every groups already visited during the parent
   12133             : //       search mode (but revisiting them should have no impact so we could
   12134             : //       let as it is if it is simpler...)
   12135             : // TODO: CF specifies that the width-wise search order is "left-to-right" so
   12136             : //       maybe we must sort sibling groups alphabetically? but maybe not
   12137             : //       necessary if nc_inq_grps() already sort them?
   12138       13354 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
   12139             :                       int *pnVarId, bool bMandatory)
   12140             : {
   12141       13354 :     *pnGroupId = -1;
   12142       13354 :     *pnVarId = -1;
   12143       13354 :     int nGroupId = nStartGroupId, nVarId;
   12144       13354 :     if (pszVar[0] == '/')
   12145             :     {
   12146             :         // This is an absolute path: we can open the var directly.
   12147             :         int nRootGroupId;
   12148           0 :         ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
   12149           0 :         ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
   12150             :     }
   12151             :     else
   12152             :     {
   12153             :         // We have to search the variable following the CF logic.
   12154       13354 :         ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
   12155             :                                 &nVarId, bMandatory));
   12156             :     }
   12157       11917 :     *pnGroupId = nGroupId;
   12158       11917 :     *pnVarId = nVarId;
   12159       11917 :     return CE_None;
   12160             : }
   12161             : 
   12162             : // Like NCDFResolveVar but returns directly the var full name.
   12163        1416 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
   12164             :                                      std::string &osFullName, bool bMandatory)
   12165             : {
   12166             :     int nGroupId, nVarId;
   12167        1416 :     osFullName = "";
   12168        1416 :     ERR_RET(
   12169             :         NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
   12170        1390 :     return NCDFGetVarFullName(nGroupId, nVarId, osFullName);
   12171             : }
   12172             : 
   12173             : // Like NCDFResolveVar but resolves an attribute instead a variable and
   12174             : // returns its integer value.
   12175             : // Only GLOBAL attributes are supported for the moment.
   12176         222 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
   12177             :                                 const char *pszAtt, int *pnAtt, bool bMandatory)
   12178             : {
   12179         222 :     int nGroupId = nStartGroupId, nAttId = nStartVarId;
   12180         222 :     ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
   12181             :                             bMandatory));
   12182           3 :     NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
   12183           3 :     return CE_None;
   12184             : }
   12185             : 
   12186             : // Filter variables to keep only valid 2+D raster bands and vector fields in
   12187             : // a given a NetCDF (or group) ID and its sub-groups.
   12188             : // Coordinate or boundary variables are ignored.
   12189             : // It also creates corresponding vector layers.
   12190         550 : CPLErr netCDFDataset::FilterVars(
   12191             :     int nCdfId, bool bKeepRasters, bool bKeepVectors,
   12192             :     const CPLStringList &aosIgnoreVars, int *pnRasterVars, int *pnGroupId,
   12193             :     int *pnVarId, int *pnIgnoredVars,
   12194             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
   12195             :         &oMap2DDimsToGroupAndVar)
   12196             : {
   12197         550 :     int nVars = 0;
   12198         550 :     int nRasterVars = 0;
   12199         550 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12200             : 
   12201        1100 :     std::vector<int> anPotentialVectorVarID;
   12202             :     // oMapDimIdToCount[x] = number of times dim x is the first dimension of
   12203             :     // potential vector variables
   12204        1100 :     std::map<int, int> oMapDimIdToCount;
   12205         550 :     int nVarXId = -1;
   12206         550 :     int nVarYId = -1;
   12207         550 :     int nVarZId = -1;
   12208         550 :     int nVarTimeId = -1;
   12209         550 :     int nVarTimeDimId = -1;
   12210         550 :     bool bIsVectorOnly = true;
   12211         550 :     int nProfileDimId = -1;
   12212         550 :     int nParentIndexVarID = -1;
   12213             : 
   12214        3319 :     for (int v = 0; v < nVars; v++)
   12215             :     {
   12216             :         int nVarDims;
   12217        2769 :         NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
   12218             :         // Should we ignore this variable?
   12219             :         char szTemp[NC_MAX_NAME + 1];
   12220        2769 :         szTemp[0] = '\0';
   12221        2769 :         NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
   12222             : 
   12223        2769 :         if (strstr(szTemp, "_node_coordinates") ||
   12224        2769 :             strstr(szTemp, "_node_count"))
   12225             :         {
   12226             :             // Ignore CF-1.8 Simple Geometries helper variables
   12227          69 :             continue;
   12228             :         }
   12229             : 
   12230        4012 :         if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
   12231        1312 :                               NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
   12232             :         {
   12233         365 :             nVarXId = v;
   12234             :         }
   12235        3282 :         else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
   12236         947 :                                    NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
   12237             :         {
   12238         364 :             nVarYId = v;
   12239             :         }
   12240        1971 :         else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
   12241             :         {
   12242          78 :             nVarZId = v;
   12243             :         }
   12244             :         else
   12245             :         {
   12246        1893 :             std::string osFullName;
   12247        1893 :             CPLErr eErr = NCDFGetVarFullName(nCdfId, v, osFullName);
   12248        1893 :             if (eErr != CE_None)
   12249             :             {
   12250           0 :                 continue;
   12251             :             }
   12252             :             const bool bIgnoreVar =
   12253        1893 :                 aosIgnoreVars.FindString(osFullName.c_str()) != -1;
   12254        1893 :             if (bIgnoreVar)
   12255             :             {
   12256         120 :                 if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
   12257             :                 {
   12258          11 :                     nVarTimeId = v;
   12259          11 :                     nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
   12260             :                 }
   12261         109 :                 else if (nVarDims > 1)
   12262             :                 {
   12263         105 :                     (*pnIgnoredVars)++;
   12264         105 :                     CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
   12265             :                              szTemp);
   12266             :                 }
   12267             :             }
   12268             :             // Only accept 2+D vars.
   12269        1773 :             else if (nVarDims >= 2)
   12270             :             {
   12271         727 :                 bool bRasterCandidate = true;
   12272             :                 // Identify variables that might be vector variables
   12273         727 :                 if (nVarDims == 2)
   12274             :                 {
   12275         650 :                     int anDimIds[2] = {-1, -1};
   12276         650 :                     nc_inq_vardimid(nCdfId, v, anDimIds);
   12277             : 
   12278         650 :                     nc_type vartype = NC_NAT;
   12279         650 :                     nc_inq_vartype(nCdfId, v, &vartype);
   12280             : 
   12281             :                     char szDimNameFirst[NC_MAX_NAME + 1];
   12282             :                     char szDimNameSecond[NC_MAX_NAME + 1];
   12283         650 :                     szDimNameFirst[0] = '\0';
   12284         650 :                     szDimNameSecond[0] = '\0';
   12285        1457 :                     if (vartype == NC_CHAR &&
   12286         157 :                         nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
   12287         157 :                             NC_NOERR &&
   12288         157 :                         nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
   12289         157 :                             NC_NOERR &&
   12290         157 :                         !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
   12291         157 :                         !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
   12292         964 :                         !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
   12293         157 :                         !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
   12294             :                     {
   12295         157 :                         anPotentialVectorVarID.push_back(v);
   12296         157 :                         oMapDimIdToCount[anDimIds[0]]++;
   12297         157 :                         if (strstr(szDimNameSecond, "_max_width"))
   12298             :                         {
   12299         127 :                             bRasterCandidate = false;
   12300             :                         }
   12301             :                         else
   12302             :                         {
   12303          30 :                             std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12304          30 :                                                     vartype};
   12305          30 :                             oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12306          30 :                                 std::pair(nCdfId, v));
   12307             :                         }
   12308             :                     }
   12309             :                     else
   12310             :                     {
   12311         493 :                         std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12312         493 :                                                 vartype};
   12313         493 :                         oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12314         493 :                             std::pair(nCdfId, v));
   12315         493 :                         bIsVectorOnly = false;
   12316             :                     }
   12317             :                 }
   12318             :                 else
   12319             :                 {
   12320          77 :                     bIsVectorOnly = false;
   12321             :                 }
   12322         727 :                 if (bKeepRasters && bRasterCandidate)
   12323             :                 {
   12324         571 :                     *pnGroupId = nCdfId;
   12325         571 :                     *pnVarId = v;
   12326         571 :                     nRasterVars++;
   12327             :                 }
   12328             :             }
   12329        1046 :             else if (nVarDims == 1)
   12330             :             {
   12331         733 :                 nc_type atttype = NC_NAT;
   12332         733 :                 size_t attlen = 0;
   12333         733 :                 if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
   12334          14 :                                &attlen) == NC_NOERR &&
   12335         733 :                     atttype == NC_CHAR && attlen < NC_MAX_NAME)
   12336             :                 {
   12337          28 :                     std::string osInstanceDimension;
   12338          14 :                     if (NCDFGetAttr(nCdfId, v, "instance_dimension",
   12339          14 :                                     osInstanceDimension) == CE_None)
   12340             :                     {
   12341             :                         const int status =
   12342          14 :                             nc_inq_dimid(nCdfId, osInstanceDimension.c_str(),
   12343             :                                          &nProfileDimId);
   12344          14 :                         if (status == NC_NOERR)
   12345          14 :                             nParentIndexVarID = v;
   12346             :                         else
   12347           0 :                             nProfileDimId = -1;
   12348          14 :                         if (status == NC_EBADDIM)
   12349           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
   12350             :                                      "Attribute instance_dimension='%s' refers "
   12351             :                                      "to a non existing dimension",
   12352             :                                      osInstanceDimension.c_str());
   12353             :                         else
   12354          14 :                             NCDF_ERR(status);
   12355             :                     }
   12356             :                 }
   12357         733 :                 if (v != nParentIndexVarID)
   12358             :                 {
   12359         719 :                     anPotentialVectorVarID.push_back(v);
   12360         719 :                     int nDimId = -1;
   12361         719 :                     nc_inq_vardimid(nCdfId, v, &nDimId);
   12362         719 :                     oMapDimIdToCount[nDimId]++;
   12363             :                 }
   12364             :             }
   12365             :         }
   12366             :     }
   12367             : 
   12368             :     // If we are opened in raster-only mode and that there are only 1D or 2D
   12369             :     // variables and that the 2D variables have no X/Y dim, and all
   12370             :     // variables refer to the same main dimension (or 2 dimensions for
   12371             :     // featureType=profile), then it is a pure vector dataset
   12372             :     CPLString osFeatureType(
   12373         550 :         aosMetadata.FetchNameValueDef("NC_GLOBAL#featureType", ""));
   12374         439 :     if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
   12375         989 :         !anPotentialVectorVarID.empty() &&
   12376           0 :         (oMapDimIdToCount.size() == 1 ||
   12377           0 :          (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
   12378           0 :           nProfileDimId >= 0)))
   12379             :     {
   12380           0 :         anPotentialVectorVarID.resize(0);
   12381             :     }
   12382             :     else
   12383             :     {
   12384         550 :         *pnRasterVars += nRasterVars;
   12385             :     }
   12386             : 
   12387         550 :     if (!anPotentialVectorVarID.empty() && bKeepVectors)
   12388             :     {
   12389             :         // Take the dimension that is referenced the most times.
   12390          66 :         if (!(oMapDimIdToCount.size() == 1 ||
   12391          27 :               (EQUAL(osFeatureType, "profile") &&
   12392          26 :                oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
   12393             :         {
   12394           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12395             :                      "The dataset has several variables that could be "
   12396             :                      "identified as vector fields, but not all share the same "
   12397             :                      "primary dimension. Consequently they will be ignored.");
   12398             :         }
   12399             :         else
   12400             :         {
   12401          52 :             if (nVarTimeId >= 0 &&
   12402          52 :                 oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
   12403             :             {
   12404           1 :                 anPotentialVectorVarID.push_back(nVarTimeId);
   12405             :             }
   12406          51 :             CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
   12407             :                                   oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
   12408             :                                   nProfileDimId, nParentIndexVarID,
   12409             :                                   bKeepRasters);
   12410             :         }
   12411             :     }
   12412             : 
   12413             :     // Recurse on sub-groups.
   12414         550 :     int nSubGroups = 0;
   12415         550 :     int *panSubGroupIds = nullptr;
   12416         550 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12417         586 :     for (int i = 0; i < nSubGroups; i++)
   12418             :     {
   12419          36 :         FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors, aosIgnoreVars,
   12420             :                    pnRasterVars, pnGroupId, pnVarId, pnIgnoredVars,
   12421             :                    oMap2DDimsToGroupAndVar);
   12422             :     }
   12423         550 :     CPLFree(panSubGroupIds);
   12424             : 
   12425         550 :     return CE_None;
   12426             : }
   12427             : 
   12428             : // Create vector layers from given potentially identified vector variables
   12429             : // resulting from the scanning of a NetCDF (or group) ID.
   12430          51 : CPLErr netCDFDataset::CreateGrpVectorLayers(
   12431             :     int nCdfId, const CPLString &osFeatureType,
   12432             :     const std::vector<int> &anPotentialVectorVarID,
   12433             :     const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
   12434             :     int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
   12435             : {
   12436         102 :     std::string osGroupName;
   12437          51 :     NCDFGetGroupFullName(nCdfId, osGroupName);
   12438          51 :     if (osGroupName.empty())
   12439             :     {
   12440          49 :         osGroupName = CPLGetBasenameSafe(osFilename);
   12441             :     }
   12442          51 :     OGRwkbGeometryType eGType = wkbUnknown;
   12443             :     CPLString osLayerName = aosMetadata.FetchNameValueDef(
   12444         102 :         "NC_GLOBAL#ogr_layer_name", osGroupName.c_str());
   12445          51 :     aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_name", nullptr);
   12446             : 
   12447          51 :     if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
   12448             :     {
   12449          33 :         aosMetadata.SetNameValue("NC_GLOBAL#featureType", nullptr);
   12450          33 :         eGType = wkbPoint;
   12451             :     }
   12452             : 
   12453             :     const char *pszLayerType =
   12454          51 :         aosMetadata.FetchNameValue("NC_GLOBAL#ogr_layer_type");
   12455          51 :     if (pszLayerType != nullptr)
   12456             :     {
   12457           9 :         eGType = OGRFromOGCGeomType(pszLayerType);
   12458           9 :         aosMetadata.SetNameValue("NC_GLOBAL#ogr_layer_type", nullptr);
   12459             :     }
   12460             : 
   12461             :     CPLString osGeometryField =
   12462         102 :         aosMetadata.FetchNameValueDef("NC_GLOBAL#ogr_geometry_field", "");
   12463          51 :     aosMetadata.SetNameValue("NC_GLOBAL#ogr_geometry_field", nullptr);
   12464             : 
   12465          51 :     int nFirstVarId = -1;
   12466          51 :     int nVectorDim = oMapDimIdToCount.rbegin()->first;
   12467          51 :     if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
   12468             :     {
   12469          13 :         if (nVectorDim == nProfileDimId)
   12470           0 :             nVectorDim = oMapDimIdToCount.begin()->first;
   12471             :     }
   12472             :     else
   12473             :     {
   12474          38 :         nProfileDimId = -1;
   12475             :     }
   12476          64 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12477             :     {
   12478          64 :         int anDimIds[2] = {-1, -1};
   12479          64 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12480          64 :         if (nVectorDim == anDimIds[0])
   12481             :         {
   12482          51 :             nFirstVarId = anPotentialVectorVarID[j];
   12483          51 :             break;
   12484             :         }
   12485             :     }
   12486             : 
   12487             :     // In case where coordinates are explicitly specified for one of the
   12488             :     // field/variable, use them in priority over the ones that might have been
   12489             :     // identified above.
   12490          51 :     char *pszCoordinates = nullptr;
   12491          51 :     if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
   12492             :         CE_None)
   12493             :     {
   12494             :         const CPLStringList aosTokens(
   12495          68 :             NCDFTokenizeCoordinatesAttribute(pszCFCoordinates));
   12496          34 :         for (int i = 0; i < aosTokens.size(); i++)
   12497             :         {
   12498           0 :             if (NCDFIsVarLongitude(nCdfId, -1, aosTokens[i]) ||
   12499           0 :                 NCDFIsVarProjectionX(nCdfId, -1, aosTokens[i]))
   12500             :             {
   12501           0 :                 nVarXId = -1;
   12502           0 :                 CPL_IGNORE_RET_VAL(
   12503           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarXId));
   12504             :             }
   12505           0 :             else if (NCDFIsVarLatitude(nCdfId, -1, aosTokens[i]) ||
   12506           0 :                      NCDFIsVarProjectionY(nCdfId, -1, aosTokens[i]))
   12507             :             {
   12508           0 :                 nVarYId = -1;
   12509           0 :                 CPL_IGNORE_RET_VAL(
   12510           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarYId));
   12511             :             }
   12512           0 :             else if (NCDFIsVarVerticalCoord(nCdfId, -1, aosTokens[i]))
   12513             :             {
   12514           0 :                 nVarZId = -1;
   12515           0 :                 CPL_IGNORE_RET_VAL(
   12516           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarZId));
   12517             :             }
   12518             :         }
   12519             :     }
   12520          51 :     CPLFree(pszCoordinates);
   12521             : 
   12522             :     // Check that the X,Y,Z vars share 1D and share the same dimension as
   12523             :     // attribute variables.
   12524          51 :     if (nVarXId >= 0 && nVarYId >= 0)
   12525             :     {
   12526          39 :         int nVarDimCount = -1;
   12527          39 :         int nVarDimId = -1;
   12528          39 :         if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
   12529          39 :             nVarDimCount != 1 ||
   12530          39 :             nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
   12531          39 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
   12532          35 :             nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
   12533          35 :             nVarDimCount != 1 ||
   12534         113 :             nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
   12535          35 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
   12536             :         {
   12537           4 :             nVarXId = nVarYId = -1;
   12538             :         }
   12539          69 :         else if (nVarZId >= 0 &&
   12540          34 :                  (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
   12541          34 :                   nVarDimCount != 1 ||
   12542          34 :                   nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
   12543          34 :                   nVarDimId != nVectorDim))
   12544             :         {
   12545           0 :             nVarZId = -1;
   12546             :         }
   12547             :     }
   12548             : 
   12549          51 :     if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
   12550             :     {
   12551           2 :         eGType = wkbPoint;
   12552             :     }
   12553          51 :     if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
   12554             :     {
   12555          34 :         eGType = wkbPoint25D;
   12556             :     }
   12557          51 :     if (eGType == wkbUnknown && osGeometryField.empty())
   12558             :     {
   12559           7 :         eGType = wkbNone;
   12560             :     }
   12561             : 
   12562             :     // Read projection info
   12563         102 :     CPLStringList aosMetadataBackup = aosMetadata;
   12564          51 :     ReadAttributes(nCdfId, nFirstVarId);
   12565          51 :     if (!this->bSGSupport)
   12566          51 :         SetProjectionFromVar(nCdfId, nFirstVarId, true);
   12567          51 :     const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
   12568         102 :     std::string osGridMapping = pszValue ? pszValue : "";
   12569          51 :     aosMetadata = aosMetadataBackup;
   12570             : 
   12571          51 :     OGRSpatialReference *poSRS = nullptr;
   12572          51 :     if (!m_oSRS.IsEmpty())
   12573             :     {
   12574          21 :         poSRS = m_oSRS.Clone();
   12575             :     }
   12576             :     // Reset if there's a 2D raster
   12577          51 :     m_bHasProjection = false;
   12578          51 :     m_bHasGeoTransform = false;
   12579             : 
   12580          51 :     if (!bKeepRasters)
   12581             :     {
   12582             :         // Strip out uninteresting metadata.
   12583          45 :         aosMetadata.SetNameValue("NC_GLOBAL#Conventions", nullptr);
   12584          45 :         aosMetadata.SetNameValue("NC_GLOBAL#GDAL", nullptr);
   12585          45 :         aosMetadata.SetNameValue("NC_GLOBAL#history", nullptr);
   12586             :     }
   12587             : 
   12588             :     std::shared_ptr<netCDFLayer> poLayer(
   12589          51 :         new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
   12590          51 :     if (poSRS != nullptr)
   12591          21 :         poSRS->Release();
   12592          51 :     poLayer->SetRecordDimID(nVectorDim);
   12593          51 :     if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
   12594             :     {
   12595          35 :         poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
   12596             :     }
   12597          16 :     else if (!osGeometryField.empty())
   12598             :     {
   12599           9 :         poLayer->SetWKTGeometryField(osGeometryField);
   12600             :     }
   12601          51 :     if (!osGridMapping.empty())
   12602             :     {
   12603          21 :         poLayer->SetGridMapping(osGridMapping.c_str());
   12604             :     }
   12605          51 :     poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
   12606             : 
   12607         578 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12608             :     {
   12609         527 :         int anDimIds[2] = {-1, -1};
   12610         527 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12611         527 :         if (anDimIds[0] == nVectorDim ||
   12612          24 :             (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
   12613             :         {
   12614             : #ifdef NCDF_DEBUG
   12615             :             char szTemp2[NC_MAX_NAME + 1] = {};
   12616             :             CPL_IGNORE_RET_VAL(
   12617             :                 nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
   12618             :             CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
   12619             : #endif
   12620         527 :             poLayer->AddField(anPotentialVectorVarID[j]);
   12621             :         }
   12622             :     }
   12623             : 
   12624          51 :     if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
   12625           0 :         poLayer->GetGeomType() != wkbNone)
   12626             :     {
   12627          51 :         papoLayers.push_back(poLayer);
   12628             :     }
   12629             : 
   12630         102 :     return CE_None;
   12631             : }
   12632             : 
   12633             : // Get all coordinate and boundary variables full names referenced in
   12634             : // a given a NetCDF (or group) ID and its sub-groups.
   12635             : // These variables are identified in other variable's
   12636             : // "coordinates" and "bounds" attribute.
   12637             : // Searching coordinate and boundary variables may need to explore
   12638             : // parents groups (or other groups in case of reference given in form of an
   12639             : // absolute path).
   12640             : // See CF sections 5.2, 5.6 and 7.1
   12641         551 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId,
   12642             :                                                CPLStringList &aosVars)
   12643             : {
   12644         551 :     int nVars = 0;
   12645         551 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12646             : 
   12647        3334 :     for (int v = 0; v < nVars; v++)
   12648             :     {
   12649        2783 :         char *pszTemp = nullptr;
   12650        5566 :         CPLStringList aosTokens;
   12651        2783 :         if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
   12652         454 :             aosTokens.Assign(NCDFTokenizeCoordinatesAttribute(pszTemp));
   12653        2783 :         CPLFree(pszTemp);
   12654        2783 :         pszTemp = nullptr;
   12655        2783 :         if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
   12656        2783 :             pszTemp != nullptr && !EQUAL(pszTemp, ""))
   12657          17 :             aosTokens.AddString(pszTemp);
   12658        2783 :         CPLFree(pszTemp);
   12659        4087 :         for (int i = 0; i < aosTokens.size(); i++)
   12660             :         {
   12661        2608 :             std::string osVarFullName;
   12662        1304 :             if (NCDFResolveVarFullName(nCdfId, aosTokens[i], osVarFullName) ==
   12663             :                 CE_None)
   12664        1278 :                 aosVars.AddString(osVarFullName);
   12665             :         }
   12666             :     }
   12667             : 
   12668             :     // Recurse on sub-groups.
   12669             :     int nSubGroups;
   12670         551 :     int *panSubGroupIds = nullptr;
   12671         551 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12672         587 :     for (int i = 0; i < nSubGroups; i++)
   12673             :     {
   12674          36 :         NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], aosVars);
   12675             :     }
   12676         551 :     CPLFree(panSubGroupIds);
   12677             : 
   12678         551 :     return CE_None;
   12679             : }
   12680             : 
   12681             : // Check if give type is user defined
   12682        1935 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
   12683             : {
   12684        1935 :     return type >= NC_FIRSTUSERTYPEID;
   12685             : }
   12686             : 
   12687         593 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
   12688             : {
   12689             :     // CF conventions use space as the separator for variable names in the
   12690             :     // coordinates attribute, but some products such as
   12691             :     // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
   12692             :     // use comma.
   12693         593 :     return CSLTokenizeString2(pszCoordinates, ", ", 0);
   12694             : }

Generated by: LCOV version 1.14