LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4993 5879 84.9 %
Date: 2026-03-05 10:33:42 Functions: 156 163 95.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  netCDF read/write Driver
       4             :  * Purpose:  GDAL bindings over netCDF library.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *           Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2004, Frank Warmerdam
      10             :  * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
      11             :  * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
      12             :  * Copyright (c) 2021, CLS
      13             :  *
      14             :  * SPDX-License-Identifier: MIT
      15             :  ****************************************************************************/
      16             : 
      17             : #include "cpl_port.h"
      18             : 
      19             : #include <array>
      20             : #include <cassert>
      21             : #include <cctype>
      22             : #include <cerrno>
      23             : #include <climits>
      24             : #include <cmath>
      25             : #include <cstdio>
      26             : #include <cstdlib>
      27             : #include <cstring>
      28             : #include <ctime>
      29             : #include <algorithm>
      30             : #include <limits>
      31             : #include <map>
      32             : #include <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, char **ppszFullName,
     111             :                                    bool bNC3Compat = true);
     112             : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
     113             :                                  bool bNC3Compat = true);
     114             : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
     115             : 
     116             : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
     117             :                                      char **ppszFullName,
     118             :                                      bool bMandatory = false);
     119             : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
     120             :                                 const char *pszAtt, int *pnAtt,
     121             :                                 bool bMandatory = false);
     122             : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
     123             : 
     124             : // Uncomment this for more debug output.
     125             : // #define NCDF_DEBUG 1
     126             : 
     127             : CPLMutex *hNCMutex = nullptr;
     128             : 
     129             : // Workaround https://github.com/OSGeo/gdal/issues/6253
     130             : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
     131             : // way. Apparently having the same handle works better (this is OK since
     132             : // we have a global mutex on the netCDF library)
     133             : static std::map<std::string, int> goMapNameToNetCDFId;
     134             : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
     135             : 
     136         784 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
     137             : {
     138        1568 :     std::string osKey(pszFilename);
     139         784 :     osKey += "#####";
     140         784 :     osKey += std::to_string(nMode);
     141         784 :     auto oIter = goMapNameToNetCDFId.find(osKey);
     142         784 :     if (oIter == goMapNameToNetCDFId.end())
     143             :     {
     144         724 :         int ret = nc_open(pszFilename, nMode, pID);
     145         724 :         if (ret != NC_NOERR)
     146           3 :             return ret;
     147         721 :         goMapNameToNetCDFId[osKey] = *pID;
     148         721 :         goMapNetCDFIdToKeyAndCount[*pID] =
     149        1442 :             std::pair<std::string, int>(osKey, 1);
     150         721 :         return ret;
     151             :     }
     152             :     else
     153             :     {
     154          60 :         *pID = oIter->second;
     155          60 :         goMapNetCDFIdToKeyAndCount[oIter->second].second++;
     156          60 :         return NC_NOERR;
     157             :     }
     158             : }
     159             : 
     160        1099 : int GDAL_nc_close(int cdfid)
     161             : {
     162        1099 :     int ret = NC_NOERR;
     163        1099 :     auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
     164        1099 :     if (oIter != goMapNetCDFIdToKeyAndCount.end())
     165             :     {
     166         781 :         if (--oIter->second.second == 0)
     167             :         {
     168         721 :             ret = nc_close(cdfid);
     169         721 :             goMapNameToNetCDFId.erase(oIter->second.first);
     170         721 :             goMapNetCDFIdToKeyAndCount.erase(oIter);
     171             :         }
     172             :     }
     173             :     else
     174             :     {
     175             :         // we can go here if file opened with nc_open_mem() or nc_create()
     176         318 :         ret = nc_close(cdfid);
     177             :     }
     178        1099 :     return ret;
     179             : }
     180             : 
     181             : /************************************************************************/
     182             : /* ==================================================================== */
     183             : /*                         netCDFRasterBand                             */
     184             : /* ==================================================================== */
     185             : /************************************************************************/
     186             : 
     187             : class netCDFRasterBand final : public GDALPamRasterBand
     188             : {
     189             :     friend class netCDFDataset;
     190             : 
     191             :     nc_type nc_datatype;
     192             :     int cdfid;
     193             :     int nZId;
     194             :     int nZDim;
     195             :     int nLevel;
     196             :     int nBandXPos;
     197             :     int nBandYPos;
     198             :     int *panBandZPos;
     199             :     int *panBandZLev;
     200             :     bool m_bNoDataSet = false;
     201             :     double m_dfNoDataValue = 0;
     202             :     bool m_bNoDataSetAsInt64 = false;
     203             :     int64_t m_nNodataValueInt64 = 0;
     204             :     bool m_bNoDataSetAsUInt64 = false;
     205             :     uint64_t m_nNodataValueUInt64 = 0;
     206             :     bool bValidRangeValid = false;
     207             :     double adfValidRange[2]{0, 0};
     208             :     bool m_bHaveScale = false;
     209             :     bool m_bHaveOffset = false;
     210             :     double m_dfScale = 1;
     211             :     double m_dfOffset = 0;
     212             :     CPLString m_osUnitType{};
     213             :     bool bSignedData;
     214             :     bool bCheckLongitude;
     215             :     bool m_bCreateMetadataFromOtherVarsDone = false;
     216             : 
     217             :     void CreateMetadataFromAttributes();
     218             :     void CreateMetadataFromOtherVars();
     219             : 
     220             :     template <class T>
     221             :     void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     222             :                    size_t nTmpBlockYSize, bool bCheckIsNan = false);
     223             :     template <class T>
     224             :     void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     225             :                       size_t nTmpBlockYSize, bool bCheckIsNan = false);
     226             :     void SetBlockSize();
     227             : 
     228             :     bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
     229             : 
     230             :     void SetNoDataValueNoUpdate(double dfNoData);
     231             :     void SetNoDataValueNoUpdate(int64_t nNoData);
     232             :     void SetNoDataValueNoUpdate(uint64_t nNoData);
     233             : 
     234             :     void SetOffsetNoUpdate(double dfVal);
     235             :     void SetScaleNoUpdate(double dfVal);
     236             :     void SetUnitTypeNoUpdate(const char *pszNewValue);
     237             : 
     238             :   protected:
     239             :     CPLXMLNode *SerializeToXML(const char *pszUnused) override;
     240             : 
     241             :   public:
     242             :     struct CONSTRUCTOR_OPEN
     243             :     {
     244             :     };
     245             : 
     246             :     struct CONSTRUCTOR_CREATE
     247             :     {
     248             :     };
     249             : 
     250             :     netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
     251             :                      int nGroupId, int nZId, int nZDim, int nLevel,
     252             :                      const int *panBandZLen, const int *panBandPos, int nBand);
     253             :     netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
     254             :                      GDALDataType eType, int nBand, bool bSigned = true,
     255             :                      const char *pszBandName = nullptr,
     256             :                      const char *pszLongName = nullptr, int nZId = -1,
     257             :                      int nZDim = 2, int nLevel = 0,
     258             :                      const int *panBandZLev = nullptr,
     259             :                      const int *panBandZPos = nullptr,
     260             :                      const int *paDimIds = nullptr);
     261             :     ~netCDFRasterBand() override;
     262             : 
     263             :     double GetNoDataValue(int *) override;
     264             :     int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
     265             :     uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
     266             :     CPLErr SetNoDataValue(double) override;
     267             :     CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
     268             :     CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
     269             :     // virtual CPLErr DeleteNoDataValue();
     270             :     double GetOffset(int *) override;
     271             :     CPLErr SetOffset(double) override;
     272             :     double GetScale(int *) override;
     273             :     CPLErr SetScale(double) override;
     274             :     const char *GetUnitType() override;
     275             :     CPLErr SetUnitType(const char *) override;
     276             :     CPLErr IReadBlock(int, int, void *) override;
     277             :     CPLErr IWriteBlock(int, int, void *) override;
     278             : 
     279             :     CSLConstList GetMetadata(const char *pszDomain = "") override;
     280             :     const char *GetMetadataItem(const char *pszName,
     281             :                                 const char *pszDomain = "") override;
     282             : 
     283             :     CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     284             :                            const char *pszDomain = "") override;
     285             :     CPLErr SetMetadata(CSLConstList papszMD,
     286             :                        const char *pszDomain = "") override;
     287             : };
     288             : 
     289             : /************************************************************************/
     290             : /*                          netCDFRasterBand()                          */
     291             : /************************************************************************/
     292             : 
     293         498 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
     294             :                                    netCDFDataset *poNCDFDS, int nGroupId,
     295             :                                    int nZIdIn, int nZDimIn, int nLevelIn,
     296             :                                    const int *panBandZLevIn,
     297         498 :                                    const int *panBandZPosIn, int nBandIn)
     298             :     : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
     299         498 :       nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
     300         498 :       nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
     301             :       panBandZLev(nullptr),
     302             :       bSignedData(true),  // Default signed, except for Byte.
     303         996 :       bCheckLongitude(false)
     304             : {
     305         498 :     poDS = poNCDFDS;
     306         498 :     nBand = nBandIn;
     307             : 
     308             :     // Take care of all other dimensions.
     309         498 :     if (nZDim > 2)
     310             :     {
     311         176 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     312         176 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     313             : 
     314         478 :         for (int i = 0; i < nZDim - 2; i++)
     315             :         {
     316         302 :             panBandZPos[i] = panBandZPosIn[i + 2];
     317         302 :             panBandZLev[i] = panBandZLevIn[i];
     318             :         }
     319             :     }
     320             : 
     321         498 :     nRasterXSize = poDS->GetRasterXSize();
     322         498 :     nRasterYSize = poDS->GetRasterYSize();
     323         498 :     nBlockXSize = poDS->GetRasterXSize();
     324         498 :     nBlockYSize = 1;
     325             : 
     326             :     // Get the type of the "z" variable, our target raster array.
     327         498 :     if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
     328         498 :                    nullptr) != NC_NOERR)
     329             :     {
     330           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
     331           0 :         return;
     332             :     }
     333             : 
     334         498 :     if (NCDFIsUserDefinedType(cdfid, nc_datatype))
     335             :     {
     336             :         // First enquire and check that the number of fields is 2
     337             :         size_t nfields, compoundsize;
     338           5 :         if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
     339           5 :                             &nfields) != NC_NOERR)
     340             :         {
     341           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     342             :                      "Error in nc_inq_compound() on 'z'.");
     343           0 :             return;
     344             :         }
     345             : 
     346           5 :         if (nfields != 2)
     347             :         {
     348           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     349             :                      "Unsupported data type encountered in nc_inq_compound() "
     350             :                      "on 'z'.");
     351           0 :             return;
     352             :         }
     353             : 
     354             :         // Now check that that two types are the same in the struct.
     355             :         nc_type field_type1, field_type2;
     356             :         int field_dims1, field_dims2;
     357           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     358             :                                   &field_type1, &field_dims1,
     359           5 :                                   nullptr) != NC_NOERR)
     360             :         {
     361           0 :             CPLError(
     362             :                 CE_Failure, CPLE_AppDefined,
     363             :                 "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
     364           0 :             return;
     365             :         }
     366             : 
     367           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     368             :                                   &field_type2, &field_dims2,
     369           5 :                                   nullptr) != NC_NOERR)
     370             :         {
     371           0 :             CPLError(
     372             :                 CE_Failure, CPLE_AppDefined,
     373             :                 "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
     374           0 :             return;
     375             :         }
     376             : 
     377           5 :         if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
     378           5 :             (field_dims1 != 0))
     379             :         {
     380           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     381             :                      "Error in interpreting compound data type on 'z'.");
     382           0 :             return;
     383             :         }
     384             : 
     385           5 :         if (field_type1 == NC_SHORT)
     386           0 :             eDataType = GDT_CInt16;
     387           5 :         else if (field_type1 == NC_INT)
     388           0 :             eDataType = GDT_CInt32;
     389           5 :         else if (field_type1 == NC_FLOAT)
     390           4 :             eDataType = GDT_CFloat32;
     391           1 :         else if (field_type1 == NC_DOUBLE)
     392           1 :             eDataType = GDT_CFloat64;
     393             :         else
     394             :         {
     395           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     396             :                      "Unsupported netCDF compound data type encountered.");
     397           0 :             return;
     398             :         }
     399             :     }
     400             :     else
     401             :     {
     402         493 :         if (nc_datatype == NC_BYTE)
     403         157 :             eDataType = GDT_UInt8;
     404         336 :         else if (nc_datatype == NC_CHAR)
     405           0 :             eDataType = GDT_UInt8;
     406         336 :         else if (nc_datatype == NC_SHORT)
     407          41 :             eDataType = GDT_Int16;
     408         295 :         else if (nc_datatype == NC_INT)
     409          89 :             eDataType = GDT_Int32;
     410         206 :         else if (nc_datatype == NC_FLOAT)
     411         127 :             eDataType = GDT_Float32;
     412          79 :         else if (nc_datatype == NC_DOUBLE)
     413          40 :             eDataType = GDT_Float64;
     414          39 :         else if (nc_datatype == NC_UBYTE)
     415          16 :             eDataType = GDT_UInt8;
     416          23 :         else if (nc_datatype == NC_USHORT)
     417           4 :             eDataType = GDT_UInt16;
     418          19 :         else if (nc_datatype == NC_UINT)
     419           4 :             eDataType = GDT_UInt32;
     420          15 :         else if (nc_datatype == NC_INT64)
     421           8 :             eDataType = GDT_Int64;
     422           7 :         else if (nc_datatype == NC_UINT64)
     423           7 :             eDataType = GDT_UInt64;
     424             :         else
     425             :         {
     426           0 :             if (nBand == 1)
     427           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     428             :                          "Unsupported netCDF datatype (%d), treat as Float32.",
     429           0 :                          static_cast<int>(nc_datatype));
     430           0 :             eDataType = GDT_Float32;
     431           0 :             nc_datatype = NC_FLOAT;
     432             :         }
     433             :     }
     434             : 
     435             :     // Find and set No Data for this variable.
     436         498 :     nc_type atttype = NC_NAT;
     437         498 :     size_t attlen = 0;
     438         498 :     const char *pszNoValueName = nullptr;
     439             : 
     440             :     // Find attribute name, either _FillValue or missing_value.
     441         498 :     int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
     442         498 :     if (status == NC_NOERR)
     443             :     {
     444         249 :         pszNoValueName = NCDF_FillValue;
     445             :     }
     446             :     else
     447             :     {
     448         249 :         status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
     449         249 :         if (status == NC_NOERR)
     450             :         {
     451          12 :             pszNoValueName = "missing_value";
     452             :         }
     453             :     }
     454             : 
     455             :     // Fetch missing value.
     456         498 :     double dfNoData = 0.0;
     457         498 :     bool bGotNoData = false;
     458         498 :     int64_t nNoDataAsInt64 = 0;
     459         498 :     bool bGotNoDataAsInt64 = false;
     460         498 :     uint64_t nNoDataAsUInt64 = 0;
     461         498 :     bool bGotNoDataAsUInt64 = false;
     462         498 :     if (status == NC_NOERR)
     463             :     {
     464         261 :         nc_type nAttrType = NC_NAT;
     465         261 :         size_t nAttrLen = 0;
     466         261 :         status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
     467         261 :         if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
     468             :         {
     469             :             long long v;
     470           7 :             nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
     471           7 :             bGotNoData = true;
     472           7 :             bGotNoDataAsInt64 = true;
     473           7 :             nNoDataAsInt64 = static_cast<int64_t>(v);
     474             :         }
     475         254 :         else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
     476             :         {
     477             :             unsigned long long v;
     478           7 :             nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
     479           7 :             bGotNoData = true;
     480           7 :             bGotNoDataAsUInt64 = true;
     481           7 :             nNoDataAsUInt64 = static_cast<uint64_t>(v);
     482             :         }
     483         247 :         else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
     484             :         {
     485         246 :             bGotNoData = true;
     486             :         }
     487             :     }
     488             : 
     489             :     // If NoData was not found, use the default value, but for non-Byte types
     490             :     // as it is not recommended:
     491             :     // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
     492         498 :     nc_type vartype = NC_NAT;
     493         498 :     if (!bGotNoData)
     494             :     {
     495         238 :         nc_inq_vartype(cdfid, nZId, &vartype);
     496         238 :         if (vartype == NC_INT64)
     497             :         {
     498             :             nNoDataAsInt64 =
     499           1 :                 NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
     500           1 :             bGotNoDataAsInt64 = bGotNoData;
     501             :         }
     502         237 :         else if (vartype == NC_UINT64)
     503             :         {
     504             :             nNoDataAsUInt64 =
     505           0 :                 NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
     506           0 :             bGotNoDataAsUInt64 = bGotNoData;
     507             :         }
     508         237 :         else if (vartype != NC_CHAR && vartype != NC_BYTE &&
     509         103 :                  vartype != NC_UBYTE)
     510             :         {
     511          93 :             dfNoData =
     512          93 :                 NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
     513          93 :             if (bGotNoData)
     514             :             {
     515          82 :                 CPLDebug("GDAL_netCDF",
     516             :                          "did not get nodata value for variable #%d, using "
     517             :                          "default %f",
     518             :                          nZId, dfNoData);
     519             :             }
     520             :         }
     521             :     }
     522             : 
     523         498 :     bool bHasUnderscoreUnsignedAttr = false;
     524         498 :     bool bUnderscoreUnsignedAttrVal = false;
     525             :     {
     526         498 :         char *pszTemp = nullptr;
     527         498 :         if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
     528             :         {
     529         149 :             if (EQUAL(pszTemp, "true"))
     530             :             {
     531         141 :                 bHasUnderscoreUnsignedAttr = true;
     532         141 :                 bUnderscoreUnsignedAttrVal = true;
     533             :             }
     534           8 :             else if (EQUAL(pszTemp, "false"))
     535             :             {
     536           8 :                 bHasUnderscoreUnsignedAttr = true;
     537           8 :                 bUnderscoreUnsignedAttrVal = false;
     538             :             }
     539         149 :             CPLFree(pszTemp);
     540             :         }
     541             :     }
     542             : 
     543             :     // Look for valid_range or valid_min/valid_max.
     544             : 
     545             :     // First look for valid_range.
     546         498 :     if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
     547             :     {
     548         496 :         char *pszValidRange = nullptr;
     549         496 :         if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
     550         142 :                 CE_None &&
     551         638 :             pszValidRange[0] == '{' &&
     552         142 :             pszValidRange[strlen(pszValidRange) - 1] == '}')
     553             :         {
     554             :             const std::string osValidRange =
     555         426 :                 std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
     556             :             const CPLStringList aosValidRange(
     557         284 :                 CSLTokenizeString2(osValidRange.c_str(), ",", 0));
     558         142 :             if (aosValidRange.size() == 2 &&
     559         284 :                 CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
     560         142 :                 CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
     561             :             {
     562         142 :                 bValidRangeValid = true;
     563         142 :                 adfValidRange[0] = CPLAtof(aosValidRange[0]);
     564         142 :                 adfValidRange[1] = CPLAtof(aosValidRange[1]);
     565             :             }
     566             :         }
     567         496 :         CPLFree(pszValidRange);
     568             : 
     569             :         // If not found look for valid_min and valid_max.
     570         496 :         if (!bValidRangeValid)
     571             :         {
     572         354 :             double dfMin = 0;
     573         354 :             double dfMax = 0;
     574         369 :             if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
     575          15 :                 NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
     576             :             {
     577           8 :                 adfValidRange[0] = dfMin;
     578           8 :                 adfValidRange[1] = dfMax;
     579           8 :                 bValidRangeValid = true;
     580             :             }
     581             :         }
     582             : 
     583         496 :         if (bValidRangeValid &&
     584         150 :             (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
     585          17 :             nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
     586             :             bUnderscoreUnsignedAttrVal)
     587             :         {
     588           2 :             if (adfValidRange[0] < 0)
     589           0 :                 adfValidRange[0] += 65536;
     590           2 :             if (adfValidRange[1] < 0)
     591           2 :                 adfValidRange[1] += 65536;
     592           2 :             if (adfValidRange[0] <= adfValidRange[1])
     593             :             {
     594             :                 // Updating metadata item
     595           2 :                 GDALPamRasterBand::SetMetadataItem(
     596             :                     "valid_range",
     597           2 :                     CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
     598           2 :                                static_cast<int>(adfValidRange[1])));
     599             :             }
     600             :         }
     601             : 
     602         496 :         if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
     603             :         {
     604           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     605             :                      "netCDFDataset::valid_range: min > max:\n"
     606             :                      "  min: %lf\n  max: %lf\n",
     607             :                      adfValidRange[0], adfValidRange[1]);
     608           0 :             bValidRangeValid = false;
     609           0 :             adfValidRange[0] = 0.0;
     610           0 :             adfValidRange[1] = 0.0;
     611             :         }
     612             :     }
     613             : 
     614             :     // Special For Byte Bands: check for signed/unsigned byte.
     615         498 :     if (nc_datatype == NC_BYTE)
     616             :     {
     617             :         // netcdf uses signed byte by default, but GDAL uses unsigned by default
     618             :         // This may cause unexpected results, but is needed for back-compat.
     619         157 :         if (poNCDFDS->bIsGdalFile)
     620         135 :             bSignedData = false;
     621             :         else
     622          22 :             bSignedData = true;
     623             : 
     624             :         // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
     625             :         // But in case a NC3 file was converted automatically and has hints
     626             :         // that it is unsigned, take them into account
     627         157 :         if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     628             :         {
     629           3 :             bSignedData = true;
     630             :         }
     631             : 
     632             :         // If we got valid_range, test for signed/unsigned range.
     633             :         // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
     634         157 :         if (bValidRangeValid)
     635             :         {
     636             :             // If we got valid_range={0,255}, treat as unsigned.
     637         138 :             if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
     638             :             {
     639         130 :                 bSignedData = false;
     640             :                 // Reset valid_range.
     641         130 :                 bValidRangeValid = false;
     642             :             }
     643             :             // If we got valid_range={-128,127}, treat as signed.
     644           8 :             else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
     645             :             {
     646           8 :                 bSignedData = true;
     647             :                 // Reset valid_range.
     648           8 :                 bValidRangeValid = false;
     649             :             }
     650             :         }
     651             :         // Else test for _Unsigned.
     652             :         // https://docs.unidata.ucar.edu/nug/current/best_practices.html
     653             :         else
     654             :         {
     655          19 :             if (bHasUnderscoreUnsignedAttr)
     656           7 :                 bSignedData = !bUnderscoreUnsignedAttrVal;
     657             :         }
     658             : 
     659         157 :         if (bSignedData)
     660             :         {
     661          20 :             eDataType = GDT_Int8;
     662             :         }
     663         137 :         else if (dfNoData < 0)
     664             :         {
     665             :             // Fix nodata value as it was stored signed.
     666           6 :             dfNoData += 256;
     667           6 :             if (pszNoValueName)
     668             :             {
     669             :                 // Updating metadata item
     670           6 :                 GDALPamRasterBand::SetMetadataItem(
     671             :                     pszNoValueName,
     672             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     673             :             }
     674             :         }
     675             :     }
     676         341 :     else if (nc_datatype == NC_SHORT)
     677             :     {
     678          41 :         if (bHasUnderscoreUnsignedAttr)
     679             :         {
     680           4 :             bSignedData = !bUnderscoreUnsignedAttrVal;
     681           4 :             if (!bSignedData)
     682           4 :                 eDataType = GDT_UInt16;
     683             :         }
     684             : 
     685             :         // Fix nodata value as it was stored signed.
     686          41 :         if (!bSignedData && dfNoData < 0)
     687             :         {
     688           4 :             dfNoData += 65536;
     689           4 :             if (pszNoValueName)
     690             :             {
     691             :                 // Updating metadata item
     692           4 :                 GDALPamRasterBand::SetMetadataItem(
     693             :                     pszNoValueName,
     694             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     695             :             }
     696             :         }
     697             :     }
     698             : 
     699         300 :     else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
     700         280 :              nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
     701             :     {
     702          31 :         bSignedData = false;
     703             :     }
     704             : 
     705         498 :     CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
     706         498 :              nc_datatype, eDataType, static_cast<int>(bSignedData));
     707             : 
     708         498 :     if (bGotNoData)
     709             :     {
     710             :         // Set nodata value.
     711         343 :         if (bGotNoDataAsInt64)
     712             :         {
     713           8 :             if (eDataType == GDT_Int64)
     714             :             {
     715           8 :                 SetNoDataValueNoUpdate(nNoDataAsInt64);
     716             :             }
     717           0 :             else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
     718             :             {
     719           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
     720             :             }
     721             :             else
     722             :             {
     723           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
     724             :             }
     725             :         }
     726         335 :         else if (bGotNoDataAsUInt64)
     727             :         {
     728           7 :             if (eDataType == GDT_UInt64)
     729             :             {
     730           7 :                 SetNoDataValueNoUpdate(nNoDataAsUInt64);
     731             :             }
     732           0 :             else if (eDataType == GDT_Int64 &&
     733             :                      nNoDataAsUInt64 <=
     734           0 :                          static_cast<uint64_t>(
     735           0 :                              std::numeric_limits<int64_t>::max()))
     736             :             {
     737           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
     738             :             }
     739             :             else
     740             :             {
     741           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
     742             :             }
     743             :         }
     744             :         else
     745             :         {
     746             : #ifdef NCDF_DEBUG
     747             :             CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
     748             : #endif
     749         328 :             if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
     750             :             {
     751           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
     752             :             }
     753         328 :             else if (eDataType == GDT_UInt64 &&
     754           0 :                      GDALIsValueExactAs<uint64_t>(dfNoData))
     755             :             {
     756           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
     757             :             }
     758             :             else
     759             :             {
     760         328 :                 SetNoDataValueNoUpdate(dfNoData);
     761             :             }
     762             :         }
     763             :     }
     764             : 
     765         498 :     CreateMetadataFromAttributes();
     766             : 
     767             :     // Attempt to fetch the scale_factor and add_offset attributes for the
     768             :     // variable and set them.  If these values are not available, set
     769             :     // offset to 0 and scale to 1.
     770         498 :     if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
     771             :     {
     772          16 :         double dfOffset = 0;
     773          16 :         status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
     774          16 :         CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
     775             :                  status);
     776          16 :         SetOffsetNoUpdate(dfOffset);
     777             :     }
     778             : 
     779         498 :     bool bHasScale = false;
     780         498 :     if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
     781             :     {
     782          20 :         bHasScale = true;
     783          20 :         double dfScale = 1;
     784          20 :         status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
     785          20 :         CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
     786             :                  status);
     787          20 :         SetScaleNoUpdate(dfScale);
     788             :     }
     789             : 
     790          12 :     if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
     791           4 :         eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
     792           4 :         (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
     793         510 :          std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
     794           1 :         CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
     795             :             nullptr)
     796             :     {
     797           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     798             :                  "validity range = %f, %f contains floating-point values, "
     799             :                  "whereas data type is integer. valid_range is thus likely "
     800             :                  "wrong%s. Ignoring it.",
     801             :                  adfValidRange[0], adfValidRange[1],
     802             :                  bHasScale ? " (likely scaled using scale_factor/add_factor "
     803             :                              "whereas it should be using the packed data type)"
     804             :                            : "");
     805           1 :         bValidRangeValid = false;
     806           1 :         adfValidRange[0] = 0.0;
     807           1 :         adfValidRange[1] = 0.0;
     808             :     }
     809             : 
     810             :     // Should we check for longitude values > 360?
     811         498 :     bCheckLongitude =
     812         996 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
     813         498 :         NCDFIsVarLongitude(cdfid, nZId, nullptr);
     814             : 
     815             :     // Attempt to fetch the units attribute for the variable and set it.
     816         498 :     SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
     817             : 
     818         498 :     SetBlockSize();
     819             : }
     820             : 
     821         686 : void netCDFRasterBand::SetBlockSize()
     822             : {
     823             :     // Check for variable chunking (netcdf-4 only).
     824             :     // GDAL block size should be set to hdf5 chunk size.
     825         686 :     int nTmpFormat = 0;
     826         686 :     int status = nc_inq_format(cdfid, &nTmpFormat);
     827         686 :     NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
     828         686 :     if ((status == NC_NOERR) &&
     829         587 :         (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
     830             :     {
     831         115 :         size_t chunksize[MAX_NC_DIMS] = {};
     832             :         // Check for chunksize and set it as the blocksize (optimizes read).
     833         115 :         status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
     834         115 :         if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
     835             :         {
     836          14 :             nBlockXSize = (int)chunksize[nZDim - 1];
     837          14 :             if (nZDim >= 2)
     838          14 :                 nBlockYSize = (int)chunksize[nZDim - 2];
     839             :             else
     840           0 :                 nBlockYSize = 1;
     841             :         }
     842             :     }
     843             : 
     844             :     // Deal with bottom-up datasets and nBlockYSize != 1.
     845         686 :     auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
     846         686 :     if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
     847             :     {
     848           6 :         if (poGDS->eAccess == GA_ReadOnly)
     849             :         {
     850             :             // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
     851             :             // width of the raster
     852           6 :             size_t nChunks =
     853           6 :                 static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
     854           6 :             if ((nRasterYSize % nBlockYSize) != 0)
     855           2 :                 nChunks *= 2;
     856             :             const size_t nChunkSize =
     857           6 :                 static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
     858           6 :                 nBlockXSize * nBlockYSize;
     859           6 :             constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
     860           6 :             nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
     861           6 :             if (nChunks)
     862             :             {
     863           6 :                 poGDS->poChunkCache.reset(
     864           6 :                     new netCDFDataset::ChunkCacheType(nChunks));
     865             :             }
     866             :         }
     867             :         else
     868             :         {
     869           0 :             nBlockYSize = 1;
     870             :         }
     871             :     }
     872         686 : }
     873             : 
     874             : // Constructor in create mode.
     875             : // If nZId and following variables are not passed, the band will have 2
     876             : // dimensions.
     877             : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
     878         188 : netCDFRasterBand::netCDFRasterBand(
     879             :     const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
     880             :     const GDALDataType eTypeIn, int nBandIn, bool bSigned,
     881             :     const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
     882             :     int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
     883         188 :     const int *paDimIds)
     884         188 :     : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
     885             :       nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
     886             :       panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
     887         188 :       bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
     888             : {
     889         188 :     poDS = poNCDFDS;
     890         188 :     nBand = nBandIn;
     891             : 
     892         188 :     nRasterXSize = poDS->GetRasterXSize();
     893         188 :     nRasterYSize = poDS->GetRasterYSize();
     894         188 :     nBlockXSize = poDS->GetRasterXSize();
     895         188 :     nBlockYSize = 1;
     896             : 
     897         188 :     if (poDS->GetAccess() != GA_Update)
     898             :     {
     899           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     900             :                  "Dataset is not in update mode, "
     901             :                  "wrong netCDFRasterBand constructor");
     902           0 :         return;
     903             :     }
     904             : 
     905             :     // Take care of all other dimensions.
     906         188 :     if (nZDim > 2 && paDimIds != nullptr)
     907             :     {
     908          27 :         nBandXPos = panBandZPosIn[0];
     909          27 :         nBandYPos = panBandZPosIn[1];
     910             : 
     911          27 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     912          27 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     913             : 
     914          76 :         for (int i = 0; i < nZDim - 2; i++)
     915             :         {
     916          49 :             panBandZPos[i] = panBandZPosIn[i + 2];
     917          49 :             panBandZLev[i] = panBandZLevIn[i];
     918             :         }
     919             :     }
     920             : 
     921             :     // Get the type of the "z" variable, our target raster array.
     922         188 :     eDataType = eTypeIn;
     923             : 
     924         188 :     switch (eDataType)
     925             :     {
     926          83 :         case GDT_UInt8:
     927          83 :             nc_datatype = NC_BYTE;
     928             :             // NC_UBYTE (unsigned byte) is only available for NC4.
     929          83 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     930           3 :                 nc_datatype = NC_UBYTE;
     931          83 :             break;
     932           7 :         case GDT_Int8:
     933           7 :             nc_datatype = NC_BYTE;
     934           7 :             break;
     935          11 :         case GDT_Int16:
     936          11 :             nc_datatype = NC_SHORT;
     937          11 :             break;
     938          24 :         case GDT_Int32:
     939          24 :             nc_datatype = NC_INT;
     940          24 :             break;
     941          13 :         case GDT_Float32:
     942          13 :             nc_datatype = NC_FLOAT;
     943          13 :             break;
     944           8 :         case GDT_Float64:
     945           8 :             nc_datatype = NC_DOUBLE;
     946           8 :             break;
     947           7 :         case GDT_Int64:
     948           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     949             :             {
     950           7 :                 nc_datatype = NC_INT64;
     951             :             }
     952             :             else
     953             :             {
     954           0 :                 if (nBand == 1)
     955           0 :                     CPLError(
     956             :                         CE_Warning, CPLE_AppDefined,
     957             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     958             :                         "Int64");
     959           0 :                 nc_datatype = NC_DOUBLE;
     960           0 :                 eDataType = GDT_Float64;
     961             :             }
     962           7 :             break;
     963           7 :         case GDT_UInt64:
     964           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     965             :             {
     966           7 :                 nc_datatype = NC_UINT64;
     967             :             }
     968             :             else
     969             :             {
     970           0 :                 if (nBand == 1)
     971           0 :                     CPLError(
     972             :                         CE_Warning, CPLE_AppDefined,
     973             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     974             :                         "UInt64");
     975           0 :                 nc_datatype = NC_DOUBLE;
     976           0 :                 eDataType = GDT_Float64;
     977             :             }
     978           7 :             break;
     979           6 :         case GDT_UInt16:
     980           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     981             :             {
     982           6 :                 nc_datatype = NC_USHORT;
     983           6 :                 break;
     984             :             }
     985             :             [[fallthrough]];
     986             :         case GDT_UInt32:
     987           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     988             :             {
     989           6 :                 nc_datatype = NC_UINT;
     990           6 :                 break;
     991             :             }
     992             :             [[fallthrough]];
     993             :         default:
     994          16 :             if (nBand == 1)
     995           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
     996             :                          "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
     997           8 :                          static_cast<int>(eDataType));
     998          16 :             nc_datatype = NC_FLOAT;
     999          16 :             eDataType = GDT_Float32;
    1000          16 :             break;
    1001             :     }
    1002             : 
    1003             :     // Define the variable if necessary (if nZId == -1).
    1004         188 :     bool bDefineVar = false;
    1005             : 
    1006         188 :     if (nZId == -1)
    1007             :     {
    1008         166 :         bDefineVar = true;
    1009             : 
    1010             :         // Make sure we are in define mode.
    1011         166 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1012             : 
    1013             :         char szTempPrivate[256 + 1];
    1014         166 :         const char *pszTemp = nullptr;
    1015         166 :         if (!pszBandName || EQUAL(pszBandName, ""))
    1016             :         {
    1017         144 :             snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
    1018         144 :             pszTemp = szTempPrivate;
    1019             :         }
    1020             :         else
    1021             :         {
    1022          22 :             pszTemp = pszBandName;
    1023             :         }
    1024             : 
    1025             :         int status;
    1026         166 :         if (nZDim > 2 && paDimIds != nullptr)
    1027             :         {
    1028           5 :             status =
    1029           5 :                 nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
    1030             :         }
    1031             :         else
    1032             :         {
    1033         161 :             int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
    1034             :             status =
    1035         161 :                 nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
    1036             :         }
    1037         166 :         NCDF_ERR(status);
    1038         166 :         CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
    1039             :                  nc_datatype, nZId);
    1040             : 
    1041         166 :         if (!pszLongName || EQUAL(pszLongName, ""))
    1042             :         {
    1043         159 :             snprintf(szTempPrivate, sizeof(szTempPrivate),
    1044             :                      "GDAL Band Number %d", nBand);
    1045         159 :             pszTemp = szTempPrivate;
    1046             :         }
    1047             :         else
    1048             :         {
    1049           7 :             pszTemp = pszLongName;
    1050             :         }
    1051             :         status =
    1052         166 :             nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
    1053         166 :         NCDF_ERR(status);
    1054             : 
    1055         166 :         poNCDFDS->DefVarDeflate(nZId, true);
    1056             :     }
    1057             : 
    1058             :     // For Byte data add signed/unsigned info.
    1059         188 :     if (eDataType == GDT_UInt8 || eDataType == GDT_Int8)
    1060             :     {
    1061          90 :         if (bDefineVar)
    1062             :         {
    1063             :             // Only add attributes if creating variable.
    1064             :             // For unsigned NC_BYTE (except NC4 format),
    1065             :             // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
    1066          82 :             if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
    1067             :             {
    1068          79 :                 CPLDebug("GDAL_netCDF",
    1069             :                          "adding valid_range attributes for Byte Band");
    1070          79 :                 short l_adfValidRange[2] = {0, 0};
    1071             :                 int status;
    1072          79 :                 if (bSignedData || eDataType == GDT_Int8)
    1073             :                 {
    1074           7 :                     l_adfValidRange[0] = -128;
    1075           7 :                     l_adfValidRange[1] = 127;
    1076           7 :                     status =
    1077           7 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
    1078             :                 }
    1079             :                 else
    1080             :                 {
    1081          72 :                     l_adfValidRange[0] = 0;
    1082          72 :                     l_adfValidRange[1] = 255;
    1083             :                     status =
    1084          72 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
    1085             :                 }
    1086          79 :                 NCDF_ERR(status);
    1087          79 :                 status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
    1088             :                                           2, l_adfValidRange);
    1089          79 :                 NCDF_ERR(status);
    1090             :             }
    1091             :         }
    1092             :     }
    1093             : 
    1094         188 :     if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
    1095         101 :         nc_datatype != NC_UBYTE)
    1096             :     {
    1097             :         // Set default nodata.
    1098          98 :         bool bIgnored = false;
    1099             :         double dfNoData =
    1100          98 :             NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
    1101             : #ifdef NCDF_DEBUG
    1102             :         CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
    1103             : #endif
    1104          98 :         netCDFRasterBand::SetNoDataValue(dfNoData);
    1105             :     }
    1106             : 
    1107         188 :     SetBlockSize();
    1108             : }
    1109             : 
    1110             : /************************************************************************/
    1111             : /*                         ~netCDFRasterBand()                          */
    1112             : /************************************************************************/
    1113             : 
    1114        1372 : netCDFRasterBand::~netCDFRasterBand()
    1115             : {
    1116         686 :     netCDFRasterBand::FlushCache(true);
    1117         686 :     CPLFree(panBandZPos);
    1118         686 :     CPLFree(panBandZLev);
    1119        1372 : }
    1120             : 
    1121             : /************************************************************************/
    1122             : /*                            GetMetadata()                             */
    1123             : /************************************************************************/
    1124             : 
    1125          54 : CSLConstList netCDFRasterBand::GetMetadata(const char *pszDomain)
    1126             : {
    1127          54 :     if (!m_bCreateMetadataFromOtherVarsDone)
    1128          52 :         CreateMetadataFromOtherVars();
    1129          54 :     return GDALPamRasterBand::GetMetadata(pszDomain);
    1130             : }
    1131             : 
    1132             : /************************************************************************/
    1133             : /*                          GetMetadataItem()                           */
    1134             : /************************************************************************/
    1135             : 
    1136         572 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
    1137             :                                               const char *pszDomain)
    1138             : {
    1139         572 :     if (!m_bCreateMetadataFromOtherVarsDone &&
    1140         556 :         STARTS_WITH(pszName, "NETCDF_DIM_") &&
    1141           1 :         (!pszDomain || pszDomain[0] == 0))
    1142           1 :         CreateMetadataFromOtherVars();
    1143         572 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
    1144             : }
    1145             : 
    1146             : /************************************************************************/
    1147             : /*                          SetMetadataItem()                           */
    1148             : /************************************************************************/
    1149             : 
    1150           7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
    1151             :                                          const char *pszValue,
    1152             :                                          const char *pszDomain)
    1153             : {
    1154           9 :     if (GetAccess() == GA_Update &&
    1155           9 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    1156             :     {
    1157             :         // Same logic as in CopyMetadata()
    1158             : 
    1159           2 :         const char *const papszIgnoreBand[] = {
    1160             :             CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    1161             :             NCDF_FillValue, "coordinates",   nullptr};
    1162             :         // Do not copy varname, stats, NETCDF_DIM_*, nodata
    1163             :         // and items in papszIgnoreBand.
    1164           6 :         if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
    1165           2 :             STARTS_WITH(pszName, "STATISTICS_") ||
    1166           2 :             STARTS_WITH(pszName, "NETCDF_DIM_") ||
    1167           2 :             STARTS_WITH(pszName, "missing_value") ||
    1168           6 :             STARTS_WITH(pszName, "_FillValue") ||
    1169           2 :             CSLFindString(papszIgnoreBand, pszName) != -1)
    1170             :         {
    1171             :             // do nothing
    1172             :         }
    1173             :         else
    1174             :         {
    1175           2 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1176             : 
    1177           2 :             if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
    1178           2 :                 return CE_Failure;
    1179             :         }
    1180             :     }
    1181             : 
    1182           5 :     return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    1183             : }
    1184             : 
    1185             : /************************************************************************/
    1186             : /*                            SetMetadata()                             */
    1187             : /************************************************************************/
    1188             : 
    1189           2 : CPLErr netCDFRasterBand::SetMetadata(CSLConstList papszMD,
    1190             :                                      const char *pszDomain)
    1191             : {
    1192           4 :     if (GetAccess() == GA_Update &&
    1193           2 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    1194             :     {
    1195             :         // We don't handle metadata item removal for now
    1196           4 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    1197             :              ++papszIter)
    1198             :         {
    1199           2 :             char *pszName = nullptr;
    1200           2 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    1201           2 :             if (pszName && pszValue)
    1202           2 :                 SetMetadataItem(pszName, pszValue);
    1203           2 :             CPLFree(pszName);
    1204             :         }
    1205             :     }
    1206           2 :     return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
    1207             : }
    1208             : 
    1209             : /************************************************************************/
    1210             : /*                             GetOffset()                              */
    1211             : /************************************************************************/
    1212          54 : double netCDFRasterBand::GetOffset(int *pbSuccess)
    1213             : {
    1214          54 :     if (pbSuccess != nullptr)
    1215          49 :         *pbSuccess = static_cast<int>(m_bHaveOffset);
    1216             : 
    1217          54 :     return m_dfOffset;
    1218             : }
    1219             : 
    1220             : /************************************************************************/
    1221             : /*                             SetOffset()                              */
    1222             : /************************************************************************/
    1223           1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
    1224             : {
    1225           2 :     CPLMutexHolderD(&hNCMutex);
    1226             : 
    1227             :     // Write value if in update mode.
    1228           1 :     if (poDS->GetAccess() == GA_Update)
    1229             :     {
    1230             :         // Make sure we are in define mode.
    1231           1 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1232             : 
    1233           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
    1234             :                                              NC_DOUBLE, 1, &dfNewOffset);
    1235             : 
    1236           1 :         NCDF_ERR(status);
    1237           1 :         if (status == NC_NOERR)
    1238             :         {
    1239           1 :             SetOffsetNoUpdate(dfNewOffset);
    1240           1 :             return CE_None;
    1241             :         }
    1242             : 
    1243           0 :         return CE_Failure;
    1244             :     }
    1245             : 
    1246           0 :     SetOffsetNoUpdate(dfNewOffset);
    1247           0 :     return CE_None;
    1248             : }
    1249             : 
    1250             : /************************************************************************/
    1251             : /*                         SetOffsetNoUpdate()                          */
    1252             : /************************************************************************/
    1253          17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
    1254             : {
    1255          17 :     m_dfOffset = dfVal;
    1256          17 :     m_bHaveOffset = true;
    1257          17 : }
    1258             : 
    1259             : /************************************************************************/
    1260             : /*                              GetScale()                              */
    1261             : /************************************************************************/
    1262          54 : double netCDFRasterBand::GetScale(int *pbSuccess)
    1263             : {
    1264          54 :     if (pbSuccess != nullptr)
    1265          49 :         *pbSuccess = static_cast<int>(m_bHaveScale);
    1266             : 
    1267          54 :     return m_dfScale;
    1268             : }
    1269             : 
    1270             : /************************************************************************/
    1271             : /*                              SetScale()                              */
    1272             : /************************************************************************/
    1273           1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
    1274             : {
    1275           2 :     CPLMutexHolderD(&hNCMutex);
    1276             : 
    1277             :     // Write value if in update mode.
    1278           1 :     if (poDS->GetAccess() == GA_Update)
    1279             :     {
    1280             :         // Make sure we are in define mode.
    1281           1 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1282             : 
    1283           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
    1284             :                                              NC_DOUBLE, 1, &dfNewScale);
    1285             : 
    1286           1 :         NCDF_ERR(status);
    1287           1 :         if (status == NC_NOERR)
    1288             :         {
    1289           1 :             SetScaleNoUpdate(dfNewScale);
    1290           1 :             return CE_None;
    1291             :         }
    1292             : 
    1293           0 :         return CE_Failure;
    1294             :     }
    1295             : 
    1296           0 :     SetScaleNoUpdate(dfNewScale);
    1297           0 :     return CE_None;
    1298             : }
    1299             : 
    1300             : /************************************************************************/
    1301             : /*                          SetScaleNoUpdate()                          */
    1302             : /************************************************************************/
    1303          21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
    1304             : {
    1305          21 :     m_dfScale = dfVal;
    1306          21 :     m_bHaveScale = true;
    1307          21 : }
    1308             : 
    1309             : /************************************************************************/
    1310             : /*                            GetUnitType()                             */
    1311             : /************************************************************************/
    1312             : 
    1313          26 : const char *netCDFRasterBand::GetUnitType()
    1314             : 
    1315             : {
    1316          26 :     if (!m_osUnitType.empty())
    1317           6 :         return m_osUnitType;
    1318             : 
    1319          20 :     return GDALRasterBand::GetUnitType();
    1320             : }
    1321             : 
    1322             : /************************************************************************/
    1323             : /*                            SetUnitType()                             */
    1324             : /************************************************************************/
    1325             : 
    1326           1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
    1327             : 
    1328             : {
    1329           2 :     CPLMutexHolderD(&hNCMutex);
    1330             : 
    1331           2 :     const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1332             : 
    1333           1 :     if (!osUnitType.empty())
    1334             :     {
    1335             :         // Write value if in update mode.
    1336           1 :         if (poDS->GetAccess() == GA_Update)
    1337             :         {
    1338             :             // Make sure we are in define mode.
    1339           1 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
    1340             : 
    1341           1 :             const int status = nc_put_att_text(
    1342             :                 cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
    1343             : 
    1344           1 :             NCDF_ERR(status);
    1345           1 :             if (status == NC_NOERR)
    1346             :             {
    1347           1 :                 SetUnitTypeNoUpdate(pszNewValue);
    1348           1 :                 return CE_None;
    1349             :             }
    1350             : 
    1351           0 :             return CE_Failure;
    1352             :         }
    1353             :     }
    1354             : 
    1355           0 :     SetUnitTypeNoUpdate(pszNewValue);
    1356             : 
    1357           0 :     return CE_None;
    1358             : }
    1359             : 
    1360             : /************************************************************************/
    1361             : /*                        SetUnitTypeNoUpdate()                         */
    1362             : /************************************************************************/
    1363             : 
    1364         499 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
    1365             : {
    1366         499 :     m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1367         499 : }
    1368             : 
    1369             : /************************************************************************/
    1370             : /*                           GetNoDataValue()                           */
    1371             : /************************************************************************/
    1372             : 
    1373         193 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
    1374             : 
    1375             : {
    1376         193 :     if (m_bNoDataSetAsInt64)
    1377             :     {
    1378           0 :         if (pbSuccess)
    1379           0 :             *pbSuccess = TRUE;
    1380           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
    1381             :     }
    1382             : 
    1383         193 :     if (m_bNoDataSetAsUInt64)
    1384             :     {
    1385           0 :         if (pbSuccess)
    1386           0 :             *pbSuccess = TRUE;
    1387           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
    1388             :     }
    1389             : 
    1390         193 :     if (m_bNoDataSet)
    1391             :     {
    1392         144 :         if (pbSuccess)
    1393         127 :             *pbSuccess = TRUE;
    1394         144 :         return m_dfNoDataValue;
    1395             :     }
    1396             : 
    1397          49 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
    1398             : }
    1399             : 
    1400             : /************************************************************************/
    1401             : /*                       GetNoDataValueAsInt64()                        */
    1402             : /************************************************************************/
    1403             : 
    1404           4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
    1405             : 
    1406             : {
    1407           4 :     if (m_bNoDataSetAsInt64)
    1408             :     {
    1409           4 :         if (pbSuccess)
    1410           4 :             *pbSuccess = TRUE;
    1411             : 
    1412           4 :         return m_nNodataValueInt64;
    1413             :     }
    1414             : 
    1415           0 :     return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
    1416             : }
    1417             : 
    1418             : /************************************************************************/
    1419             : /*                       GetNoDataValueAsUInt64()                       */
    1420             : /************************************************************************/
    1421             : 
    1422           4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
    1423             : 
    1424             : {
    1425           4 :     if (m_bNoDataSetAsUInt64)
    1426             :     {
    1427           4 :         if (pbSuccess)
    1428           4 :             *pbSuccess = TRUE;
    1429             : 
    1430           4 :         return m_nNodataValueUInt64;
    1431             :     }
    1432             : 
    1433           0 :     return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
    1434             : }
    1435             : 
    1436             : /************************************************************************/
    1437             : /*                           SetNoDataValue()                           */
    1438             : /************************************************************************/
    1439             : 
    1440         134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
    1441             : 
    1442             : {
    1443         268 :     CPLMutexHolderD(&hNCMutex);
    1444             : 
    1445             :     // If already set to new value, don't do anything.
    1446         134 :     if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
    1447          19 :         return CE_None;
    1448             : 
    1449             :     // Write value if in update mode.
    1450         115 :     if (poDS->GetAccess() == GA_Update)
    1451             :     {
    1452             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1453             :         // but it is ok if variable has not been written to, so only print
    1454             :         // debug. See bug #4484.
    1455         125 :         if (m_bNoDataSet &&
    1456          10 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1457             :         {
    1458           0 :             CPLDebug("GDAL_netCDF",
    1459             :                      "Setting NoDataValue to %.17g (previously set to %.17g) "
    1460             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1461             :                      dfNoData, m_dfNoDataValue, cdfid, nBand);
    1462             :         }
    1463             : #ifdef NCDF_DEBUG
    1464             :         else
    1465             :         {
    1466             :             CPLDebug("GDAL_netCDF",
    1467             :                      "Setting NoDataValue to %.17g (id #%d, band #%d)",
    1468             :                      dfNoData, cdfid, nBand);
    1469             :         }
    1470             : #endif
    1471             :         // Make sure we are in define mode.
    1472         115 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1473             : 
    1474             :         int status;
    1475         115 :         if (eDataType == GDT_UInt8)
    1476             :         {
    1477           6 :             if (bSignedData)
    1478             :             {
    1479           0 :                 signed char cNoDataValue = static_cast<signed char>(dfNoData);
    1480           0 :                 status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
    1481             :                                           nc_datatype, 1, &cNoDataValue);
    1482             :             }
    1483             :             else
    1484             :             {
    1485           6 :                 const unsigned char ucNoDataValue =
    1486           6 :                     static_cast<unsigned char>(dfNoData);
    1487           6 :                 status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
    1488             :                                           nc_datatype, 1, &ucNoDataValue);
    1489             :             }
    1490             :         }
    1491         109 :         else if (eDataType == GDT_Int16)
    1492             :         {
    1493          14 :             short nsNoDataValue = static_cast<short>(dfNoData);
    1494          14 :             status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1495             :                                       1, &nsNoDataValue);
    1496             :         }
    1497          95 :         else if (eDataType == GDT_Int32)
    1498             :         {
    1499          27 :             int nNoDataValue = static_cast<int>(dfNoData);
    1500          27 :             status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
    1501             :                                     &nNoDataValue);
    1502             :         }
    1503          68 :         else if (eDataType == GDT_Float32)
    1504             :         {
    1505          31 :             float fNoDataValue = static_cast<float>(dfNoData);
    1506          31 :             status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1507             :                                       1, &fNoDataValue);
    1508             :         }
    1509          43 :         else if (eDataType == GDT_UInt16 &&
    1510           6 :                  cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
    1511             :                      NCDF_FORMAT_NC4)
    1512             :         {
    1513           6 :             unsigned short usNoDataValue =
    1514           6 :                 static_cast<unsigned short>(dfNoData);
    1515           6 :             status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1516             :                                        1, &usNoDataValue);
    1517             :         }
    1518          38 :         else if (eDataType == GDT_UInt32 &&
    1519           7 :                  cpl::down_cast<netCDFDataset *>(poDS)->eFormat ==
    1520             :                      NCDF_FORMAT_NC4)
    1521             :         {
    1522           7 :             unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
    1523           7 :             status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1524             :                                      1, &unNoDataValue);
    1525             :         }
    1526             :         else
    1527             :         {
    1528          24 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1529             :                                        1, &dfNoData);
    1530             :         }
    1531             : 
    1532         115 :         NCDF_ERR(status);
    1533             : 
    1534             :         // Update status if write worked.
    1535         115 :         if (status == NC_NOERR)
    1536             :         {
    1537         115 :             SetNoDataValueNoUpdate(dfNoData);
    1538         115 :             return CE_None;
    1539             :         }
    1540             : 
    1541           0 :         return CE_Failure;
    1542             :     }
    1543             : 
    1544           0 :     SetNoDataValueNoUpdate(dfNoData);
    1545           0 :     return CE_None;
    1546             : }
    1547             : 
    1548             : /************************************************************************/
    1549             : /*                       SetNoDataValueNoUpdate()                       */
    1550             : /************************************************************************/
    1551             : 
    1552         443 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
    1553             : {
    1554         443 :     m_dfNoDataValue = dfNoData;
    1555         443 :     m_bNoDataSet = true;
    1556         443 :     m_bNoDataSetAsInt64 = false;
    1557         443 :     m_bNoDataSetAsUInt64 = false;
    1558         443 : }
    1559             : 
    1560             : /************************************************************************/
    1561             : /*                       SetNoDataValueAsInt64()                        */
    1562             : /************************************************************************/
    1563             : 
    1564           3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    1565             : 
    1566             : {
    1567           6 :     CPLMutexHolderD(&hNCMutex);
    1568             : 
    1569             :     // If already set to new value, don't do anything.
    1570           3 :     if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
    1571           0 :         return CE_None;
    1572             : 
    1573             :     // Write value if in update mode.
    1574           3 :     if (poDS->GetAccess() == GA_Update)
    1575             :     {
    1576             :         // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
    1577             :         // but it is ok if variable has not been written to, so only print
    1578             :         // debug. See bug #4484.
    1579           3 :         if (m_bNoDataSetAsInt64 &&
    1580           0 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1581             :         {
    1582           0 :             CPLDebug("GDAL_netCDF",
    1583             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1584             :                      " (previously set to " CPL_FRMT_GIB ") "
    1585             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1586             :                      static_cast<GIntBig>(nNoData),
    1587           0 :                      static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
    1588             :         }
    1589             : #ifdef NCDF_DEBUG
    1590             :         else
    1591             :         {
    1592             :             CPLDebug("GDAL_netCDF",
    1593             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1594             :                      " (id #%d, band #%d)",
    1595             :                      static_cast<GIntBig>(nNoData), cdfid, nBand);
    1596             :         }
    1597             : #endif
    1598             :         // Make sure we are in define mode.
    1599           3 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1600             : 
    1601             :         int status;
    1602           6 :         if (eDataType == GDT_Int64 &&
    1603           3 :             cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1604             :         {
    1605           3 :             long long tmp = static_cast<long long>(nNoData);
    1606           3 :             status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
    1607             :                                          nc_datatype, 1, &tmp);
    1608             :         }
    1609             :         else
    1610             :         {
    1611           0 :             double dfNoData = static_cast<double>(nNoData);
    1612           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1613             :                                        1, &dfNoData);
    1614             :         }
    1615             : 
    1616           3 :         NCDF_ERR(status);
    1617             : 
    1618             :         // Update status if write worked.
    1619           3 :         if (status == NC_NOERR)
    1620             :         {
    1621           3 :             SetNoDataValueNoUpdate(nNoData);
    1622           3 :             return CE_None;
    1623             :         }
    1624             : 
    1625           0 :         return CE_Failure;
    1626             :     }
    1627             : 
    1628           0 :     SetNoDataValueNoUpdate(nNoData);
    1629           0 :     return CE_None;
    1630             : }
    1631             : 
    1632             : /************************************************************************/
    1633             : /*                       SetNoDataValueNoUpdate()                       */
    1634             : /************************************************************************/
    1635             : 
    1636          11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
    1637             : {
    1638          11 :     m_nNodataValueInt64 = nNoData;
    1639          11 :     m_bNoDataSet = false;
    1640          11 :     m_bNoDataSetAsInt64 = true;
    1641          11 :     m_bNoDataSetAsUInt64 = false;
    1642          11 : }
    1643             : 
    1644             : /************************************************************************/
    1645             : /*                       SetNoDataValueAsUInt64()                       */
    1646             : /************************************************************************/
    1647             : 
    1648           3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    1649             : 
    1650             : {
    1651           6 :     CPLMutexHolderD(&hNCMutex);
    1652             : 
    1653             :     // If already set to new value, don't do anything.
    1654           3 :     if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
    1655           0 :         return CE_None;
    1656             : 
    1657             :     // Write value if in update mode.
    1658           3 :     if (poDS->GetAccess() == GA_Update)
    1659             :     {
    1660             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1661             :         // but it is ok if variable has not been written to, so only print
    1662             :         // debug. See bug #4484.
    1663           3 :         if (m_bNoDataSetAsUInt64 &&
    1664           0 :             !cpl::down_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1665             :         {
    1666           0 :             CPLDebug("GDAL_netCDF",
    1667             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1668             :                      " (previously set to " CPL_FRMT_GUIB ") "
    1669             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1670             :                      static_cast<GUIntBig>(nNoData),
    1671           0 :                      static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
    1672             :         }
    1673             : #ifdef NCDF_DEBUG
    1674             :         else
    1675             :         {
    1676             :             CPLDebug("GDAL_netCDF",
    1677             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1678             :                      " (id #%d, band #%d)",
    1679             :                      static_cast<GUIntBig>(nNoData), cdfid, nBand);
    1680             :         }
    1681             : #endif
    1682             :         // Make sure we are in define mode.
    1683           3 :         cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1684             : 
    1685             :         int status;
    1686           6 :         if (eDataType == GDT_UInt64 &&
    1687           3 :             cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1688             :         {
    1689           3 :             unsigned long long tmp = static_cast<long long>(nNoData);
    1690           3 :             status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
    1691             :                                           nc_datatype, 1, &tmp);
    1692             :         }
    1693             :         else
    1694             :         {
    1695           0 :             double dfNoData = static_cast<double>(nNoData);
    1696           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1697             :                                        1, &dfNoData);
    1698             :         }
    1699             : 
    1700           3 :         NCDF_ERR(status);
    1701             : 
    1702             :         // Update status if write worked.
    1703           3 :         if (status == NC_NOERR)
    1704             :         {
    1705           3 :             SetNoDataValueNoUpdate(nNoData);
    1706           3 :             return CE_None;
    1707             :         }
    1708             : 
    1709           0 :         return CE_Failure;
    1710             :     }
    1711             : 
    1712           0 :     SetNoDataValueNoUpdate(nNoData);
    1713           0 :     return CE_None;
    1714             : }
    1715             : 
    1716             : /************************************************************************/
    1717             : /*                       SetNoDataValueNoUpdate()                       */
    1718             : /************************************************************************/
    1719             : 
    1720          10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
    1721             : {
    1722          10 :     m_nNodataValueUInt64 = nNoData;
    1723          10 :     m_bNoDataSet = false;
    1724          10 :     m_bNoDataSetAsInt64 = false;
    1725          10 :     m_bNoDataSetAsUInt64 = true;
    1726          10 : }
    1727             : 
    1728             : /************************************************************************/
    1729             : /*                         DeleteNoDataValue()                          */
    1730             : /************************************************************************/
    1731             : 
    1732             : #ifdef notdef
    1733             : CPLErr netCDFRasterBand::DeleteNoDataValue()
    1734             : 
    1735             : {
    1736             :     CPLMutexHolderD(&hNCMutex);
    1737             : 
    1738             :     if (!bNoDataSet)
    1739             :         return CE_None;
    1740             : 
    1741             :     // Write value if in update mode.
    1742             :     if (poDS->GetAccess() == GA_Update)
    1743             :     {
    1744             :         // Make sure we are in define mode.
    1745             :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1746             : 
    1747             :         status = nc_del_att(cdfid, nZId, NCDF_FillValue);
    1748             : 
    1749             :         NCDF_ERR(status);
    1750             : 
    1751             :         // Update status if write worked.
    1752             :         if (status == NC_NOERR)
    1753             :         {
    1754             :             dfNoDataValue = 0.0;
    1755             :             bNoDataSet = false;
    1756             :             return CE_None;
    1757             :         }
    1758             : 
    1759             :         return CE_Failure;
    1760             :     }
    1761             : 
    1762             :     dfNoDataValue = 0.0;
    1763             :     bNoDataSet = false;
    1764             :     return CE_None;
    1765             : }
    1766             : #endif
    1767             : 
    1768             : /************************************************************************/
    1769             : /*                           SerializeToXML()                           */
    1770             : /************************************************************************/
    1771             : 
    1772           5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
    1773             : {
    1774             :     // Overridden from GDALPamDataset to add only band histogram
    1775             :     // and statistics. See bug #4244.
    1776           5 :     if (psPam == nullptr)
    1777           0 :         return nullptr;
    1778             : 
    1779             :     // Setup root node and attributes.
    1780             :     CPLXMLNode *psTree =
    1781           5 :         CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
    1782             : 
    1783           5 :     if (GetBand() > 0)
    1784             :     {
    1785          10 :         CPLString oFmt;
    1786           5 :         CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
    1787             :     }
    1788             : 
    1789             :     // Histograms.
    1790           5 :     if (psPam->psSavedHistograms != nullptr)
    1791           1 :         CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
    1792             : 
    1793             :     // Metadata (statistics only).
    1794           5 :     GDALMultiDomainMetadata oMDMDStats;
    1795           5 :     const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
    1796             :                                   "STATISTICS_MEAN", "STATISTICS_STDDEV",
    1797             :                                   nullptr};
    1798          25 :     for (int i = 0; i < CSLCount(papszMDStats); i++)
    1799             :     {
    1800          20 :         const char *pszMDI = GetMetadataItem(papszMDStats[i]);
    1801          20 :         if (pszMDI)
    1802           4 :             oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
    1803             :     }
    1804           5 :     CPLXMLNode *psMD = oMDMDStats.Serialize();
    1805             : 
    1806           5 :     if (psMD != nullptr)
    1807             :     {
    1808           1 :         if (psMD->psChild == nullptr)
    1809           0 :             CPLDestroyXMLNode(psMD);
    1810             :         else
    1811           1 :             CPLAddXMLChild(psTree, psMD);
    1812             :     }
    1813             : 
    1814             :     // We don't want to return anything if we had no metadata to attach.
    1815           5 :     if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
    1816             :     {
    1817           3 :         CPLDestroyXMLNode(psTree);
    1818           3 :         psTree = nullptr;
    1819             :     }
    1820             : 
    1821           5 :     return psTree;
    1822             : }
    1823             : 
    1824             : /************************************************************************/
    1825             : /*                  Get1DVariableIndexedByDimension()                   */
    1826             : /************************************************************************/
    1827             : 
    1828          81 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
    1829             :                                            const char *pszDimName,
    1830             :                                            bool bVerboseError, int *pnGroupID)
    1831             : {
    1832          81 :     *pnGroupID = -1;
    1833          81 :     int nVarID = -1;
    1834             :     // First try to find a variable whose name is identical to the dimension
    1835             :     // name, and check that it is indeed indexed by this dimension
    1836          81 :     if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
    1837             :     {
    1838          67 :         int nDimCountOfVariable = 0;
    1839          67 :         nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
    1840          67 :         if (nDimCountOfVariable == 1)
    1841             :         {
    1842          67 :             int nDimIdOfVariable = -1;
    1843          67 :             nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
    1844          67 :             if (nDimIdOfVariable == nDimId)
    1845             :             {
    1846          67 :                 return nVarID;
    1847             :             }
    1848             :         }
    1849             :     }
    1850             : 
    1851             :     // Otherwise iterate over the variables to find potential candidates
    1852             :     // TODO: should be modified to search also in other groups using the same
    1853             :     //       logic than in NCDFResolveVar(), but maybe not needed if it's a
    1854             :     //       very rare case? and I think this is not CF compliant.
    1855          14 :     int nvars = 0;
    1856          14 :     CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
    1857             : 
    1858          14 :     int nCountCandidateVars = 0;
    1859          14 :     int nCandidateVarID = -1;
    1860          65 :     for (int k = 0; k < nvars; k++)
    1861             :     {
    1862          51 :         int nDimCountOfVariable = 0;
    1863          51 :         nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
    1864          51 :         if (nDimCountOfVariable == 1)
    1865             :         {
    1866          27 :             int nDimIdOfVariable = -1;
    1867          27 :             nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
    1868          27 :             if (nDimIdOfVariable == nDimId)
    1869             :             {
    1870           7 :                 nCountCandidateVars++;
    1871           7 :                 nCandidateVarID = k;
    1872             :             }
    1873             :         }
    1874             :     }
    1875          14 :     if (nCountCandidateVars > 1)
    1876             :     {
    1877           1 :         if (bVerboseError)
    1878             :         {
    1879           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1880             :                      "Several 1D variables are indexed by dimension %s",
    1881             :                      pszDimName);
    1882             :         }
    1883           1 :         *pnGroupID = -1;
    1884           1 :         return -1;
    1885             :     }
    1886          13 :     else if (nCandidateVarID < 0)
    1887             :     {
    1888           8 :         if (bVerboseError)
    1889             :         {
    1890           8 :             CPLError(CE_Warning, CPLE_AppDefined,
    1891             :                      "No 1D variable is indexed by dimension %s", pszDimName);
    1892             :         }
    1893             :     }
    1894          13 :     *pnGroupID = cdfid;
    1895          13 :     return nCandidateVarID;
    1896             : }
    1897             : 
    1898             : /************************************************************************/
    1899             : /*                    CreateMetadataFromAttributes()                    */
    1900             : /************************************************************************/
    1901             : 
    1902         498 : void netCDFRasterBand::CreateMetadataFromAttributes()
    1903             : {
    1904         498 :     char szVarName[NC_MAX_NAME + 1] = {};
    1905         498 :     int status = nc_inq_varname(cdfid, nZId, szVarName);
    1906         498 :     NCDF_ERR(status);
    1907             : 
    1908         498 :     GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
    1909             : 
    1910             :     // Get attribute metadata.
    1911         498 :     int nAtt = 0;
    1912         498 :     NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
    1913             : 
    1914        2120 :     for (int i = 0; i < nAtt; i++)
    1915             :     {
    1916        1622 :         char szMetaName[NC_MAX_NAME + 1] = {};
    1917        1622 :         status = nc_inq_attname(cdfid, nZId, i, szMetaName);
    1918        1622 :         if (status != NC_NOERR)
    1919          12 :             continue;
    1920             : 
    1921        1622 :         if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
    1922             :         {
    1923          12 :             continue;
    1924             :         }
    1925             : 
    1926        1610 :         char *pszMetaValue = nullptr;
    1927        1610 :         if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
    1928             :         {
    1929        1610 :             GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
    1930             :         }
    1931             :         else
    1932             :         {
    1933           0 :             CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
    1934             :         }
    1935             : 
    1936        1610 :         if (pszMetaValue)
    1937             :         {
    1938        1610 :             CPLFree(pszMetaValue);
    1939        1610 :             pszMetaValue = nullptr;
    1940             :         }
    1941             :     }
    1942         498 : }
    1943             : 
    1944             : /************************************************************************/
    1945             : /*                    CreateMetadataFromOtherVars()                     */
    1946             : /************************************************************************/
    1947             : 
    1948          53 : void netCDFRasterBand::CreateMetadataFromOtherVars()
    1949             : 
    1950             : {
    1951          53 :     CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
    1952          53 :     m_bCreateMetadataFromOtherVarsDone = true;
    1953             : 
    1954          53 :     netCDFDataset *l_poDS = cpl::down_cast<netCDFDataset *>(poDS);
    1955          53 :     const int nPamFlagsBackup = l_poDS->nPamFlags;
    1956             : 
    1957             :     // Compute all dimensions from Band number and save in Metadata.
    1958          53 :     int nd = 0;
    1959          53 :     nc_inq_varndims(cdfid, nZId, &nd);
    1960             :     // Compute multidimention band position.
    1961             :     //
    1962             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    1963             :     // if Data[2,3,4,x,y]
    1964             :     //
    1965             :     //  BandPos0 = (nBand) / (3*4)
    1966             :     //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)
    1967             :     //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)
    1968             : 
    1969          53 :     int Sum = 1;
    1970          53 :     if (nd == 3)
    1971             :     {
    1972           5 :         Sum *= panBandZLev[0];
    1973             :     }
    1974             : 
    1975             :     // Loop over non-spatial dimensions.
    1976          53 :     int Taken = 0;
    1977             : 
    1978          93 :     for (int i = 0; i < nd - 2; i++)
    1979             :     {
    1980             :         int result;
    1981          40 :         if (i != nd - 2 - 1)
    1982             :         {
    1983          18 :             Sum = 1;
    1984          37 :             for (int j = i + 1; j < nd - 2; j++)
    1985             :             {
    1986          19 :                 Sum *= panBandZLev[j];
    1987             :             }
    1988          18 :             result = static_cast<int>((nLevel - Taken) / Sum);
    1989             :         }
    1990             :         else
    1991             :         {
    1992          22 :             result = static_cast<int>((nLevel - Taken) % Sum);
    1993             :         }
    1994             : 
    1995          40 :         char szName[NC_MAX_NAME + 1] = {};
    1996          40 :         snprintf(szName, sizeof(szName), "%s",
    1997          40 :                  l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
    1998             : 
    1999             :         char szMetaName[NC_MAX_NAME + 1 + 32];
    2000          40 :         snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
    2001             : 
    2002          40 :         const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
    2003          40 :         const int nVarID = l_poDS->m_anExtraDimVarIds[i];
    2004          40 :         if (nVarID < 0)
    2005             :         {
    2006           2 :             GDALPamRasterBand::SetMetadataItem(szMetaName,
    2007             :                                                CPLSPrintf("%d", result + 1));
    2008             :         }
    2009             :         else
    2010             :         {
    2011             :             // TODO: Make sure all the status checks make sense.
    2012             : 
    2013          38 :             nc_type nVarType = NC_NAT;
    2014          38 :             /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
    2015             : 
    2016          38 :             int nDims = 0;
    2017          38 :             /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
    2018             : 
    2019          38 :             char szMetaTemp[256] = {};
    2020          38 :             if (nDims == 1)
    2021             :             {
    2022          38 :                 size_t count[1] = {1};
    2023          38 :                 size_t start[1] = {static_cast<size_t>(result)};
    2024             : 
    2025          38 :                 switch (nVarType)
    2026             :                 {
    2027           0 :                     case NC_BYTE:
    2028             :                         // TODO: Check for signed/unsigned byte.
    2029             :                         signed char cData;
    2030           0 :                         /* status = */ nc_get_vara_schar(nGroupID, nVarID,
    2031             :                                                          start, count, &cData);
    2032           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
    2033           0 :                         break;
    2034           0 :                     case NC_SHORT:
    2035             :                         short sData;
    2036           0 :                         /* status = */ nc_get_vara_short(nGroupID, nVarID,
    2037             :                                                          start, count, &sData);
    2038           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
    2039           0 :                         break;
    2040          19 :                     case NC_INT:
    2041             :                     {
    2042             :                         int nData;
    2043          19 :                         /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
    2044             :                                                        count, &nData);
    2045          19 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
    2046          19 :                         break;
    2047             :                     }
    2048           0 :                     case NC_FLOAT:
    2049             :                         float fData;
    2050           0 :                         /* status = */ nc_get_vara_float(nGroupID, nVarID,
    2051             :                                                          start, count, &fData);
    2052           0 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
    2053             :                                     fData);
    2054           0 :                         break;
    2055          18 :                     case NC_DOUBLE:
    2056             :                         double dfData;
    2057          18 :                         /* status = */ nc_get_vara_double(
    2058             :                             nGroupID, nVarID, start, count, &dfData);
    2059          18 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
    2060             :                                     dfData);
    2061          18 :                         break;
    2062           0 :                     case NC_UBYTE:
    2063             :                         unsigned char ucData;
    2064           0 :                         /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
    2065             :                                                          start, count, &ucData);
    2066           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
    2067           0 :                         break;
    2068           0 :                     case NC_USHORT:
    2069             :                         unsigned short usData;
    2070           0 :                         /* status = */ nc_get_vara_ushort(
    2071             :                             nGroupID, nVarID, start, count, &usData);
    2072           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
    2073           0 :                         break;
    2074           0 :                     case NC_UINT:
    2075             :                     {
    2076             :                         unsigned int unData;
    2077           0 :                         /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
    2078             :                                                         count, &unData);
    2079           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
    2080           0 :                         break;
    2081             :                     }
    2082           1 :                     case NC_INT64:
    2083             :                     {
    2084             :                         long long nData;
    2085           1 :                         /* status = */ nc_get_vara_longlong(
    2086             :                             nGroupID, nVarID, start, count, &nData);
    2087           1 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
    2088             :                                  nData);
    2089           1 :                         break;
    2090             :                     }
    2091           0 :                     case NC_UINT64:
    2092             :                     {
    2093             :                         unsigned long long unData;
    2094           0 :                         /* status = */ nc_get_vara_ulonglong(
    2095             :                             nGroupID, nVarID, start, count, &unData);
    2096           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
    2097             :                                  unData);
    2098           0 :                         break;
    2099             :                     }
    2100           0 :                     default:
    2101           0 :                         CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
    2102             :                                  szMetaTemp, nVarType);
    2103           0 :                         break;
    2104             :                 }
    2105             :             }
    2106             :             else
    2107             :             {
    2108           0 :                 snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
    2109             :             }
    2110             : 
    2111             :             // Save dimension value.
    2112             :             // NOTE: removed #original_units as not part of CF-1.
    2113             : 
    2114          38 :             GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
    2115             :         }
    2116             : 
    2117             :         // Avoid int32 overflow. Perhaps something more sensible to do here ?
    2118          40 :         if (result > 0 && Sum > INT_MAX / result)
    2119           0 :             break;
    2120          40 :         if (Taken > INT_MAX - result * Sum)
    2121           0 :             break;
    2122             : 
    2123          40 :         Taken += result * Sum;
    2124             :     }  // End loop non-spatial dimensions.
    2125             : 
    2126          53 :     l_poDS->nPamFlags = nPamFlagsBackup;
    2127          53 : }
    2128             : 
    2129             : /************************************************************************/
    2130             : /*                             CheckData()                              */
    2131             : /************************************************************************/
    2132             : template <class T>
    2133        5912 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
    2134             :                                  size_t nTmpBlockXSize, size_t nTmpBlockYSize,
    2135             :                                  bool bCheckIsNan)
    2136             : {
    2137        5912 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2138             : 
    2139             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2140             :     // the data this is because partial blocks are not arranged the same way in
    2141             :     // netcdf and gdal.
    2142        5912 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2143             :     {
    2144           6 :         T *ptrWrite = static_cast<T *>(pImage);
    2145           6 :         T *ptrRead = static_cast<T *>(pImageNC);
    2146          29 :         for (size_t j = 0; j < nTmpBlockYSize;
    2147          23 :              j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
    2148             :         {
    2149          23 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
    2150             :         }
    2151             :     }
    2152             : 
    2153             :     // Is valid data checking needed or requested?
    2154        5912 :     if (bValidRangeValid || bCheckIsNan)
    2155             :     {
    2156        1345 :         T *ptrImage = static_cast<T *>(pImage);
    2157        2744 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2158             :         {
    2159             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2160        1399 :             size_t k = j * nBlockXSize;
    2161       98618 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2162             :             {
    2163             :                 // Check for nodata and nan.
    2164       97219 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2165        6301 :                     continue;
    2166       90918 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2167             :                 {
    2168        5737 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2169        5737 :                     continue;
    2170             :                 }
    2171             :                 // Check for valid_range.
    2172       85181 :                 if (bValidRangeValid)
    2173             :                 {
    2174       40986 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2175       40986 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2176       40983 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2177       40983 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2178             :                     {
    2179           4 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2180             :                     }
    2181             :                 }
    2182             :             }
    2183             :         }
    2184             :     }
    2185             : 
    2186             :     // If minimum longitude is > 180, subtract 360 from all.
    2187             :     // If not, disable checking for further calls (check just once).
    2188             :     // Only check first and last block elements since lon must be monotonic.
    2189        5912 :     const bool bIsSigned = std::numeric_limits<T>::is_signed;
    2190        5581 :     if (bCheckLongitude && bIsSigned &&
    2191          11 :         !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
    2192          10 :         !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
    2193        2796 :                     m_dfNoDataValue) &&
    2194          10 :         std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
    2195             :     {
    2196           0 :         T *ptrImage = static_cast<T *>(pImage);
    2197           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2198             :         {
    2199           0 :             size_t k = j * nBlockXSize;
    2200           0 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2201             :             {
    2202           0 :                 if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2203           0 :                     ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
    2204             :             }
    2205             :         }
    2206             :     }
    2207             :     else
    2208             :     {
    2209        5912 :         bCheckLongitude = false;
    2210             :     }
    2211        5912 : }
    2212             : 
    2213             : /************************************************************************/
    2214             : /*                            CheckDataCpx()                            */
    2215             : /************************************************************************/
    2216             : template <class T>
    2217          25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
    2218             :                                     size_t nTmpBlockXSize,
    2219             :                                     size_t nTmpBlockYSize, bool bCheckIsNan)
    2220             : {
    2221          25 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2222             : 
    2223             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2224             :     // the data this is because partial blocks are not arranged the same way in
    2225             :     // netcdf and gdal.
    2226          25 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2227             :     {
    2228           0 :         T *ptrWrite = static_cast<T *>(pImage);
    2229           0 :         T *ptrRead = static_cast<T *>(pImageNC);
    2230           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++,
    2231           0 :                     ptrWrite += (2 * nBlockXSize),
    2232           0 :                     ptrRead += (2 * nTmpBlockXSize))
    2233             :         {
    2234           0 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
    2235             :         }
    2236             :     }
    2237             : 
    2238             :     // Is valid data checking needed or requested?
    2239          25 :     if (bValidRangeValid || bCheckIsNan)
    2240             :     {
    2241           0 :         T *ptrImage = static_cast<T *>(pImage);
    2242           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2243             :         {
    2244             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2245           0 :             size_t k = 2 * j * nBlockXSize;
    2246           0 :             for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
    2247             :             {
    2248             :                 // Check for nodata and nan.
    2249           0 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2250           0 :                     continue;
    2251           0 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2252             :                 {
    2253           0 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2254           0 :                     continue;
    2255             :                 }
    2256             :                 // Check for valid_range.
    2257           0 :                 if (bValidRangeValid)
    2258             :                 {
    2259           0 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2260           0 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2261           0 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2262           0 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2263             :                     {
    2264           0 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2265             :                     }
    2266             :                 }
    2267             :             }
    2268             :         }
    2269             :     }
    2270          25 : }
    2271             : 
    2272             : /************************************************************************/
    2273             : /*                          FetchNetcdfChunk()                          */
    2274             : /************************************************************************/
    2275             : 
    2276        5937 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
    2277             :                                         void *pImage)
    2278             : {
    2279        5937 :     size_t start[MAX_NC_DIMS] = {};
    2280        5937 :     size_t edge[MAX_NC_DIMS] = {};
    2281             : 
    2282        5937 :     start[nBandXPos] = xstart;
    2283        5937 :     edge[nBandXPos] = nBlockXSize;
    2284        5937 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2285           6 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2286        5937 :     if (nBandYPos >= 0)
    2287             :     {
    2288        5933 :         start[nBandYPos] = ystart;
    2289        5933 :         edge[nBandYPos] = nBlockYSize;
    2290        5933 :         if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2291           4 :             edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2292             :     }
    2293        5937 :     const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
    2294             : 
    2295             : #ifdef NCDF_DEBUG
    2296             :     CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
    2297             :              start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
    2298             :              edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
    2299             : #endif
    2300             : 
    2301        5937 :     int nd = 0;
    2302        5937 :     nc_inq_varndims(cdfid, nZId, &nd);
    2303        5937 :     if (nd == 3)
    2304             :     {
    2305        1078 :         start[panBandZPos[0]] = nLevel;  // z
    2306        1078 :         edge[panBandZPos[0]] = 1;
    2307             :     }
    2308             : 
    2309             :     // Compute multidimention band position.
    2310             :     //
    2311             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2312             :     // if Data[2,3,4,x,y]
    2313             :     //
    2314             :     //  BandPos0 = (nBand) / (3*4)
    2315             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2316             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2317        5937 :     if (nd > 3)
    2318             :     {
    2319         160 :         int Sum = -1;
    2320         160 :         int Taken = 0;
    2321         480 :         for (int i = 0; i < nd - 2; i++)
    2322             :         {
    2323         320 :             if (i != nd - 2 - 1)
    2324             :             {
    2325         160 :                 Sum = 1;
    2326         320 :                 for (int j = i + 1; j < nd - 2; j++)
    2327             :                 {
    2328         160 :                     Sum *= panBandZLev[j];
    2329             :                 }
    2330         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2331         160 :                 edge[panBandZPos[i]] = 1;
    2332             :             }
    2333             :             else
    2334             :             {
    2335         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2336         160 :                 edge[panBandZPos[i]] = 1;
    2337             :             }
    2338         320 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2339             :         }
    2340             :     }
    2341             : 
    2342             :     // Make sure we are in data mode.
    2343        5937 :     cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2344             : 
    2345             :     // If this block is not a full block in the x axis, we need to
    2346             :     // re-arrange the data because partial blocks are not arranged the
    2347             :     // same way in netcdf and gdal, so we first we read the netcdf data at
    2348             :     // the end of the gdal block buffer then re-arrange rows in CheckData().
    2349        5937 :     void *pImageNC = pImage;
    2350        5937 :     if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
    2351             :     {
    2352           6 :         pImageNC = static_cast<GByte *>(pImage) +
    2353           6 :                    ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
    2354          12 :                      edge[nBandXPos] * nYChunkSize) *
    2355           6 :                     GDALGetDataTypeSizeBytes(eDataType));
    2356             :     }
    2357             : 
    2358             :     // Read data according to type.
    2359             :     int status;
    2360        5937 :     if (eDataType == GDT_UInt8)
    2361             :     {
    2362        3105 :         if (bSignedData)
    2363             :         {
    2364           0 :             status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2365             :                                        static_cast<signed char *>(pImageNC));
    2366           0 :             if (status == NC_NOERR)
    2367           0 :                 CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2368             :                                        nYChunkSize, false);
    2369             :         }
    2370             :         else
    2371             :         {
    2372        3105 :             status = nc_get_vara_uchar(cdfid, nZId, start, edge,
    2373             :                                        static_cast<unsigned char *>(pImageNC));
    2374        3105 :             if (status == NC_NOERR)
    2375        3105 :                 CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
    2376             :                                          nYChunkSize, false);
    2377             :         }
    2378             :     }
    2379        2832 :     else if (eDataType == GDT_Int8)
    2380             :     {
    2381          60 :         status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2382             :                                    static_cast<signed char *>(pImageNC));
    2383          60 :         if (status == NC_NOERR)
    2384          60 :             CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2385             :                                    nYChunkSize, false);
    2386             :     }
    2387        2772 :     else if (nc_datatype == NC_SHORT)
    2388             :     {
    2389         465 :         status = nc_get_vara_short(cdfid, nZId, start, edge,
    2390             :                                    static_cast<short *>(pImageNC));
    2391         465 :         if (status == NC_NOERR)
    2392             :         {
    2393         465 :             if (eDataType == GDT_Int16)
    2394             :             {
    2395         462 :                 CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
    2396             :                                   nYChunkSize, false);
    2397             :             }
    2398             :             else
    2399             :             {
    2400           3 :                 CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
    2401             :                                    nYChunkSize, false);
    2402             :             }
    2403             :         }
    2404             :     }
    2405        2307 :     else if (eDataType == GDT_Int32)
    2406             :     {
    2407             : #if SIZEOF_UNSIGNED_LONG == 4
    2408             :         status = nc_get_vara_long(cdfid, nZId, start, edge,
    2409             :                                   static_cast<long *>(pImageNC));
    2410             :         if (status == NC_NOERR)
    2411             :             CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2412             :                             false);
    2413             : #else
    2414         912 :         status = nc_get_vara_int(cdfid, nZId, start, edge,
    2415             :                                  static_cast<int *>(pImageNC));
    2416         912 :         if (status == NC_NOERR)
    2417         912 :             CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2418             :                            false);
    2419             : #endif
    2420             :     }
    2421        1395 :     else if (eDataType == GDT_Float32)
    2422             :     {
    2423        1258 :         status = nc_get_vara_float(cdfid, nZId, start, edge,
    2424             :                                    static_cast<float *>(pImageNC));
    2425        1258 :         if (status == NC_NOERR)
    2426        1258 :             CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2427             :                              true);
    2428             :     }
    2429         137 :     else if (eDataType == GDT_Float64)
    2430             :     {
    2431          86 :         status = nc_get_vara_double(cdfid, nZId, start, edge,
    2432             :                                     static_cast<double *>(pImageNC));
    2433          86 :         if (status == NC_NOERR)
    2434          86 :             CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2435             :                               true);
    2436             :     }
    2437          51 :     else if (eDataType == GDT_UInt16)
    2438             :     {
    2439           6 :         status = nc_get_vara_ushort(cdfid, nZId, start, edge,
    2440             :                                     static_cast<unsigned short *>(pImageNC));
    2441           6 :         if (status == NC_NOERR)
    2442           6 :             CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
    2443             :                                       nYChunkSize, false);
    2444             :     }
    2445          45 :     else if (eDataType == GDT_UInt32)
    2446             :     {
    2447           6 :         status = nc_get_vara_uint(cdfid, nZId, start, edge,
    2448             :                                   static_cast<unsigned int *>(pImageNC));
    2449           6 :         if (status == NC_NOERR)
    2450           6 :             CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
    2451             :                                     nYChunkSize, false);
    2452             :     }
    2453          39 :     else if (eDataType == GDT_Int64)
    2454             :     {
    2455           7 :         status = nc_get_vara_longlong(cdfid, nZId, start, edge,
    2456             :                                       static_cast<long long *>(pImageNC));
    2457           7 :         if (status == NC_NOERR)
    2458           7 :             CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
    2459             :                                     nYChunkSize, false);
    2460             :     }
    2461          32 :     else if (eDataType == GDT_UInt64)
    2462             :     {
    2463             :         status =
    2464           7 :             nc_get_vara_ulonglong(cdfid, nZId, start, edge,
    2465             :                                   static_cast<unsigned long long *>(pImageNC));
    2466           7 :         if (status == NC_NOERR)
    2467           7 :             CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
    2468             :                                      nYChunkSize, false);
    2469             :     }
    2470          25 :     else if (eDataType == GDT_CInt16)
    2471             :     {
    2472           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2473           0 :         if (status == NC_NOERR)
    2474           0 :             CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2475             :                                 false);
    2476             :     }
    2477          25 :     else if (eDataType == GDT_CInt32)
    2478             :     {
    2479           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2480           0 :         if (status == NC_NOERR)
    2481           0 :             CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2482             :                               false);
    2483             :     }
    2484          25 :     else if (eDataType == GDT_CFloat32)
    2485             :     {
    2486          20 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2487          20 :         if (status == NC_NOERR)
    2488          20 :             CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2489             :                                 false);
    2490             :     }
    2491           5 :     else if (eDataType == GDT_CFloat64)
    2492             :     {
    2493           5 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2494           5 :         if (status == NC_NOERR)
    2495           5 :             CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2496             :                                  false);
    2497             :     }
    2498             : 
    2499             :     else
    2500           0 :         status = NC_EBADTYPE;
    2501             : 
    2502        5937 :     if (status != NC_NOERR)
    2503             :     {
    2504           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2505             :                  "netCDF chunk fetch failed: #%d (%s)", status,
    2506             :                  nc_strerror(status));
    2507           0 :         return false;
    2508             :     }
    2509        5937 :     return true;
    2510             : }
    2511             : 
    2512             : /************************************************************************/
    2513             : /*                             IReadBlock()                             */
    2514             : /************************************************************************/
    2515             : 
    2516        5937 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2517             :                                     void *pImage)
    2518             : 
    2519             : {
    2520       11874 :     CPLMutexHolderD(&hNCMutex);
    2521             : 
    2522             :     // Locate X, Y and Z position in the array.
    2523             : 
    2524        5937 :     size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2525        5937 :     size_t ystart = 0;
    2526             : 
    2527             :     // Check y order.
    2528        5937 :     if (nBandYPos >= 0)
    2529             :     {
    2530        5933 :         auto poGDS = cpl::down_cast<netCDFDataset *>(poDS);
    2531        5933 :         if (poGDS->bBottomUp)
    2532             :         {
    2533        5018 :             if (nBlockYSize == 1)
    2534             :             {
    2535        5005 :                 ystart = nRasterYSize - 1 - nBlockYOff;
    2536             :             }
    2537             :             else
    2538             :             {
    2539             :                 // in GDAL space
    2540          13 :                 ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2541             :                 const size_t yend =
    2542          26 :                     std::min(ystart + nBlockYSize - 1,
    2543          13 :                              static_cast<size_t>(nRasterYSize - 1));
    2544             :                 // in netCDF space
    2545          13 :                 const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
    2546          13 :                 const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
    2547          13 :                 const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
    2548          13 :                 const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
    2549             : 
    2550             :                 const auto firstKey = netCDFDataset::ChunkKey(
    2551          13 :                     nBlockXOff, nFirstChunkBlock, nBand);
    2552             :                 const auto secondKey =
    2553          13 :                     netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
    2554             : 
    2555             :                 // Retrieve data from the one or 2 needed netCDF chunks
    2556          13 :                 std::shared_ptr<std::vector<GByte>> firstChunk;
    2557          13 :                 std::shared_ptr<std::vector<GByte>> secondChunk;
    2558          13 :                 if (poGDS->poChunkCache)
    2559             :                 {
    2560          13 :                     poGDS->poChunkCache->tryGet(firstKey, firstChunk);
    2561          13 :                     if (firstKey != secondKey)
    2562           6 :                         poGDS->poChunkCache->tryGet(secondKey, secondChunk);
    2563             :                 }
    2564             :                 const size_t nChunkLineSize =
    2565          13 :                     static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
    2566          13 :                     nBlockXSize;
    2567          13 :                 const size_t nChunkSize = nChunkLineSize * nBlockYSize;
    2568          13 :                 if (!firstChunk)
    2569             :                 {
    2570          11 :                     firstChunk.reset(new std::vector<GByte>(nChunkSize));
    2571          11 :                     if (!FetchNetcdfChunk(xstart,
    2572          11 :                                           nFirstChunkBlock * nBlockYSize,
    2573          11 :                                           firstChunk.get()->data()))
    2574           0 :                         return CE_Failure;
    2575          11 :                     if (poGDS->poChunkCache)
    2576          11 :                         poGDS->poChunkCache->insert(firstKey, firstChunk);
    2577             :                 }
    2578          13 :                 if (!secondChunk && firstKey != secondKey)
    2579             :                 {
    2580           2 :                     secondChunk.reset(new std::vector<GByte>(nChunkSize));
    2581           2 :                     if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
    2582           2 :                                           secondChunk.get()->data()))
    2583           0 :                         return CE_Failure;
    2584           2 :                     if (poGDS->poChunkCache)
    2585           2 :                         poGDS->poChunkCache->insert(secondKey, secondChunk);
    2586             :                 }
    2587             : 
    2588             :                 // Assemble netCDF chunks into GDAL block
    2589          13 :                 GByte *pabyImage = static_cast<GByte *>(pImage);
    2590          13 :                 const size_t nFirstChunkBlockLine =
    2591          13 :                     nFirstChunkBlock * nBlockYSize;
    2592          13 :                 const size_t nLastChunkBlockLine =
    2593          13 :                     nLastChunkBlock * nBlockYSize;
    2594         146 :                 for (size_t iLine = ystart; iLine <= yend; iLine++)
    2595             :                 {
    2596         133 :                     const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
    2597         133 :                     const size_t nChunkY = nLineFromBottom / nBlockYSize;
    2598         133 :                     if (nChunkY == nFirstChunkBlock)
    2599             :                     {
    2600         121 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2601         121 :                                firstChunk.get()->data() +
    2602         121 :                                    (nLineFromBottom - nFirstChunkBlockLine) *
    2603             :                                        nChunkLineSize,
    2604             :                                nChunkLineSize);
    2605             :                     }
    2606             :                     else
    2607             :                     {
    2608          12 :                         CPLAssert(nChunkY == nLastChunkBlock);
    2609          12 :                         assert(secondChunk);
    2610          12 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2611          12 :                                secondChunk.get()->data() +
    2612          12 :                                    (nLineFromBottom - nLastChunkBlockLine) *
    2613             :                                        nChunkLineSize,
    2614             :                                nChunkLineSize);
    2615             :                     }
    2616             :                 }
    2617          13 :                 return CE_None;
    2618             :             }
    2619             :         }
    2620             :         else
    2621             :         {
    2622         915 :             ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2623             :         }
    2624             :     }
    2625             : 
    2626        5924 :     return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
    2627             : }
    2628             : 
    2629             : /************************************************************************/
    2630             : /*                            IWriteBlock()                             */
    2631             : /************************************************************************/
    2632             : 
    2633        6501 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
    2634             :                                      void *pImage)
    2635             : {
    2636       13002 :     CPLMutexHolderD(&hNCMutex);
    2637             : 
    2638             : #ifdef NCDF_DEBUG
    2639             :     if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
    2640             :         CPLDebug("GDAL_netCDF",
    2641             :                  "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
    2642             :                  nBlockXOff, nBlockYOff, nBand);
    2643             : #endif
    2644             : 
    2645        6501 :     int nd = 0;
    2646        6501 :     nc_inq_varndims(cdfid, nZId, &nd);
    2647             : 
    2648             :     // Locate X, Y and Z position in the array.
    2649             : 
    2650             :     size_t start[MAX_NC_DIMS];
    2651        6501 :     memset(start, 0, sizeof(start));
    2652        6501 :     start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2653             : 
    2654             :     // check y order.
    2655        6501 :     if (cpl::down_cast<netCDFDataset *>(poDS)->bBottomUp)
    2656             :     {
    2657        6437 :         if (nBlockYSize == 1)
    2658             :         {
    2659        6437 :             start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
    2660             :         }
    2661             :         else
    2662             :         {
    2663           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2664             :                      "nBlockYSize = %d, only 1 supported when "
    2665             :                      "writing bottom-up dataset",
    2666             :                      nBlockYSize);
    2667           0 :             return CE_Failure;
    2668             :         }
    2669             :     }
    2670             :     else
    2671             :     {
    2672          64 :         start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y
    2673             :     }
    2674             : 
    2675        6501 :     size_t edge[MAX_NC_DIMS] = {};
    2676             : 
    2677        6501 :     edge[nBandXPos] = nBlockXSize;
    2678        6501 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2679           0 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2680        6501 :     edge[nBandYPos] = nBlockYSize;
    2681        6501 :     if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2682           0 :         edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2683             : 
    2684        6501 :     if (nd == 3)
    2685             :     {
    2686         610 :         start[panBandZPos[0]] = nLevel;  // z
    2687         610 :         edge[panBandZPos[0]] = 1;
    2688             :     }
    2689             : 
    2690             :     // Compute multidimention band position.
    2691             :     //
    2692             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2693             :     // if Data[2,3,4,x,y]
    2694             :     //
    2695             :     //  BandPos0 = (nBand) / (3*4)
    2696             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2697             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2698        6501 :     if (nd > 3)
    2699             :     {
    2700         178 :         int Sum = -1;
    2701         178 :         int Taken = 0;
    2702         534 :         for (int i = 0; i < nd - 2; i++)
    2703             :         {
    2704         356 :             if (i != nd - 2 - 1)
    2705             :             {
    2706         178 :                 Sum = 1;
    2707         356 :                 for (int j = i + 1; j < nd - 2; j++)
    2708             :                 {
    2709         178 :                     Sum *= panBandZLev[j];
    2710             :                 }
    2711         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2712         178 :                 edge[panBandZPos[i]] = 1;
    2713             :             }
    2714             :             else
    2715             :             {
    2716         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2717         178 :                 edge[panBandZPos[i]] = 1;
    2718             :             }
    2719         356 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2720             :         }
    2721             :     }
    2722             : 
    2723             :     // Make sure we are in data mode.
    2724        6501 :     cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2725             : 
    2726             :     // Copy data according to type.
    2727        6501 :     int status = 0;
    2728        6501 :     if (eDataType == GDT_UInt8)
    2729             :     {
    2730        5942 :         if (bSignedData)
    2731           0 :             status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2732             :                                        static_cast<signed char *>(pImage));
    2733             :         else
    2734        5942 :             status = nc_put_vara_uchar(cdfid, nZId, start, edge,
    2735             :                                        static_cast<unsigned char *>(pImage));
    2736             :     }
    2737         559 :     else if (eDataType == GDT_Int8)
    2738             :     {
    2739          40 :         status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2740             :                                    static_cast<signed char *>(pImage));
    2741             :     }
    2742         519 :     else if (nc_datatype == NC_SHORT)
    2743             :     {
    2744         101 :         status = nc_put_vara_short(cdfid, nZId, start, edge,
    2745             :                                    static_cast<short *>(pImage));
    2746             :     }
    2747         418 :     else if (eDataType == GDT_Int32)
    2748             :     {
    2749         210 :         status = nc_put_vara_int(cdfid, nZId, start, edge,
    2750             :                                  static_cast<int *>(pImage));
    2751             :     }
    2752         208 :     else if (eDataType == GDT_Float32)
    2753             :     {
    2754         128 :         status = nc_put_vara_float(cdfid, nZId, start, edge,
    2755             :                                    static_cast<float *>(pImage));
    2756             :     }
    2757          80 :     else if (eDataType == GDT_Float64)
    2758             :     {
    2759          50 :         status = nc_put_vara_double(cdfid, nZId, start, edge,
    2760             :                                     static_cast<double *>(pImage));
    2761             :     }
    2762          42 :     else if (eDataType == GDT_UInt16 &&
    2763          12 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2764             :     {
    2765          12 :         status = nc_put_vara_ushort(cdfid, nZId, start, edge,
    2766             :                                     static_cast<unsigned short *>(pImage));
    2767             :     }
    2768          30 :     else if (eDataType == GDT_UInt32 &&
    2769          12 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2770             :     {
    2771          12 :         status = nc_put_vara_uint(cdfid, nZId, start, edge,
    2772             :                                   static_cast<unsigned int *>(pImage));
    2773             :     }
    2774           9 :     else if (eDataType == GDT_UInt64 &&
    2775           3 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2776             :     {
    2777             :         status =
    2778           3 :             nc_put_vara_ulonglong(cdfid, nZId, start, edge,
    2779             :                                   static_cast<unsigned long long *>(pImage));
    2780             :     }
    2781           6 :     else if (eDataType == GDT_Int64 &&
    2782           3 :              cpl::down_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2783             :     {
    2784           3 :         status = nc_put_vara_longlong(cdfid, nZId, start, edge,
    2785             :                                       static_cast<long long *>(pImage));
    2786             :     }
    2787             :     else
    2788             :     {
    2789           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2790             :                  "The NetCDF driver does not support GDAL data type %d",
    2791           0 :                  eDataType);
    2792           0 :         status = NC_EBADTYPE;
    2793             :     }
    2794        6501 :     NCDF_ERR(status);
    2795             : 
    2796        6501 :     if (status != NC_NOERR)
    2797             :     {
    2798           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2799             :                  "netCDF scanline write failed: %s", nc_strerror(status));
    2800           0 :         return CE_Failure;
    2801             :     }
    2802             : 
    2803        6501 :     return CE_None;
    2804             : }
    2805             : 
    2806             : /************************************************************************/
    2807             : /* ==================================================================== */
    2808             : /*                              netCDFDataset                           */
    2809             : /* ==================================================================== */
    2810             : /************************************************************************/
    2811             : 
    2812             : /************************************************************************/
    2813             : /*                           netCDFDataset()                            */
    2814             : /************************************************************************/
    2815             : 
    2816        1205 : netCDFDataset::netCDFDataset()
    2817             :     :
    2818             : // Basic dataset vars.
    2819             : #ifdef ENABLE_NCDUMP
    2820             :       bFileToDestroyAtClosing(false),
    2821             : #endif
    2822             :       cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
    2823             :       papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
    2824             :       bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
    2825             :       pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
    2826        1205 :       eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
    2827        1205 :       GeometryScribe(vcdf, this->generateLogName()),
    2828        1205 :       FieldScribe(vcdf, this->generateLogName()),
    2829        2410 :       bufManager(CPLGetUsablePhysicalRAM() / 5),
    2830             : 
    2831             :       // projection/GT.
    2832             :       nXDimID(-1), nYDimID(-1), bIsProjected(false),
    2833             :       bIsGeographic(false),  // Can be not projected, and also not geographic
    2834             :       // State vars.
    2835             :       bDefineMode(true), bAddedGridMappingRef(false),
    2836             : 
    2837             :       // Create vars.
    2838             :       papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
    2839             :       nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
    2840        3615 :       bSignedData(true)
    2841             : {
    2842        1205 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2843             : 
    2844             :     // Set buffers
    2845        1205 :     bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
    2846        1205 :     bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
    2847        1205 : }
    2848             : 
    2849             : /************************************************************************/
    2850             : /*                           ~netCDFDataset()                           */
    2851             : /************************************************************************/
    2852             : 
    2853        2312 : netCDFDataset::~netCDFDataset()
    2854             : 
    2855             : {
    2856        1205 :     netCDFDataset::Close();
    2857        2312 : }
    2858             : 
    2859             : /************************************************************************/
    2860             : /*                               Close()                                */
    2861             : /************************************************************************/
    2862             : 
    2863        2032 : CPLErr netCDFDataset::Close(GDALProgressFunc, void *)
    2864             : {
    2865        2032 :     CPLErr eErr = CE_None;
    2866        2032 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    2867             :     {
    2868        2410 :         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        1490 :         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        1205 :         if (netCDFDataset::FlushCache(true) != CE_None)
    2890           0 :             eErr = CE_Failure;
    2891             : 
    2892        1205 :         if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
    2893           0 :             eErr = CE_Failure;
    2894             : 
    2895        1207 :         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        1205 :         if (GetAccess() == GA_Update && !bAddedGridMappingRef)
    2900             :         {
    2901         308 :             if (!AddGridMappingRef())
    2902           0 :                 eErr = CE_Failure;
    2903             :         }
    2904             : 
    2905        1205 :         CSLDestroy(papszMetadata);
    2906        1205 :         CSLDestroy(papszSubDatasets);
    2907        1205 :         CSLDestroy(papszCreationOptions);
    2908             : 
    2909        1205 :         CPLFree(pszCFProjection);
    2910             : 
    2911        1205 :         if (cdfid > 0)
    2912             :         {
    2913             : #ifdef NCDF_DEBUG
    2914             :             CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
    2915             : #endif
    2916         685 :             int status = GDAL_nc_close(cdfid);
    2917             : #ifdef ENABLE_UFFD
    2918         685 :             NETCDF_UFFD_UNMAP(pCtx);
    2919             : #endif
    2920         685 :             NCDF_ERR(status);
    2921         685 :             if (status != NC_NOERR)
    2922           0 :                 eErr = CE_Failure;
    2923             :         }
    2924             : 
    2925        1205 :         if (fpVSIMEM)
    2926          15 :             VSIFCloseL(fpVSIMEM);
    2927             : 
    2928             : #ifdef ENABLE_NCDUMP
    2929        1205 :         if (bFileToDestroyAtClosing)
    2930           0 :             VSIUnlink(osFilename);
    2931             : #endif
    2932             : 
    2933        1205 :         if (GDALPamDataset::Close() != CE_None)
    2934           0 :             eErr = CE_Failure;
    2935             :     }
    2936        2032 :     return eErr;
    2937             : }
    2938             : 
    2939             : /************************************************************************/
    2940             : /*                           SetDefineMode()                            */
    2941             : /************************************************************************/
    2942       14555 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
    2943             : {
    2944             :     // Do nothing if already in new define mode
    2945             :     // or if dataset is in read-only mode or if dataset is true NC4 dataset.
    2946       15132 :     if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
    2947         577 :         eFormat == NCDF_FORMAT_NC4)
    2948       14125 :         return true;
    2949             : 
    2950         430 :     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
    2951         430 :              static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
    2952             : 
    2953         430 :     bDefineMode = bNewDefineMode;
    2954             : 
    2955             :     int status;
    2956         430 :     if (bDefineMode)
    2957         149 :         status = nc_redef(cdfid);
    2958             :     else
    2959         281 :         status = nc_enddef(cdfid);
    2960             : 
    2961         430 :     NCDF_ERR(status);
    2962         430 :     return status == NC_NOERR;
    2963             : }
    2964             : 
    2965             : /************************************************************************/
    2966             : /*                       GetMetadataDomainList()                        */
    2967             : /************************************************************************/
    2968             : 
    2969          27 : char **netCDFDataset::GetMetadataDomainList()
    2970             : {
    2971          27 :     char **papszDomains = BuildMetadataDomainList(
    2972             :         GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
    2973          28 :     for (const auto &kv : m_oMapDomainToJSon)
    2974           1 :         papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
    2975          27 :     return papszDomains;
    2976             : }
    2977             : 
    2978             : /************************************************************************/
    2979             : /*                            GetMetadata()                             */
    2980             : /************************************************************************/
    2981         403 : CSLConstList netCDFDataset::GetMetadata(const char *pszDomain)
    2982             : {
    2983         403 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
    2984          39 :         return papszSubDatasets;
    2985             : 
    2986         364 :     if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
    2987             :     {
    2988           1 :         auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
    2989           1 :         if (iter != m_oMapDomainToJSon.end())
    2990           1 :             return iter->second.List();
    2991             :     }
    2992             : 
    2993         363 :     return GDALDataset::GetMetadata(pszDomain);
    2994             : }
    2995             : 
    2996             : /************************************************************************/
    2997             : /*                          SetMetadataItem()                           */
    2998             : /************************************************************************/
    2999             : 
    3000          43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    3001             :                                       const char *pszDomain)
    3002             : {
    3003          85 :     if (GetAccess() == GA_Update &&
    3004          85 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    3005             :     {
    3006          42 :         std::string osName(pszName);
    3007             : 
    3008             :         // Same logic as in CopyMetadata()
    3009          42 :         if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
    3010           8 :             osName = osName.substr(strlen("NC_GLOBAL#"));
    3011          34 :         else if (strchr(osName.c_str(), '#') == nullptr)
    3012           5 :             osName = "GDAL_" + osName;
    3013             : 
    3014          84 :         if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
    3015          42 :             strchr(osName.c_str(), '#') != nullptr)
    3016             :         {
    3017             :             // do nothing
    3018          29 :             return CE_None;
    3019             :         }
    3020             :         else
    3021             :         {
    3022          13 :             SetDefineMode(true);
    3023             : 
    3024          13 :             if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
    3025          13 :                 return CE_Failure;
    3026             :         }
    3027             :     }
    3028             : 
    3029           1 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    3030             : }
    3031             : 
    3032             : /************************************************************************/
    3033             : /*                            SetMetadata()                             */
    3034             : /************************************************************************/
    3035             : 
    3036           8 : CPLErr netCDFDataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
    3037             : {
    3038          13 :     if (GetAccess() == GA_Update &&
    3039           5 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    3040             :     {
    3041             :         // We don't handle metadata item removal for now
    3042          50 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    3043             :              ++papszIter)
    3044             :         {
    3045          42 :             char *pszName = nullptr;
    3046          42 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    3047          42 :             if (pszName && pszValue)
    3048          42 :                 SetMetadataItem(pszName, pszValue);
    3049          42 :             CPLFree(pszName);
    3050             :         }
    3051           8 :         return CE_None;
    3052             :     }
    3053           0 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    3054             : }
    3055             : 
    3056             : /************************************************************************/
    3057             : /*                           GetSpatialRef()                            */
    3058             : /************************************************************************/
    3059             : 
    3060         230 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
    3061             : {
    3062         230 :     if (m_bHasProjection)
    3063         102 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3064             : 
    3065         128 :     return GDALPamDataset::GetSpatialRef();
    3066             : }
    3067             : 
    3068             : /************************************************************************/
    3069             : /*                           FetchCopyParam()                           */
    3070             : /************************************************************************/
    3071             : 
    3072         444 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
    3073             :                                      const char *pszParam, double dfDefault,
    3074             :                                      bool *pbFound) const
    3075             : 
    3076             : {
    3077             :     char *pszTemp =
    3078         444 :         CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
    3079         444 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
    3080         444 :     CPLFree(pszTemp);
    3081             : 
    3082         444 :     if (pbFound)
    3083             :     {
    3084         444 :         *pbFound = (pszValue != nullptr);
    3085             :     }
    3086             : 
    3087         444 :     if (pszValue)
    3088             :     {
    3089           0 :         return CPLAtofM(pszValue);
    3090             :     }
    3091             : 
    3092         444 :     return dfDefault;
    3093             : }
    3094             : 
    3095             : /************************************************************************/
    3096             : /*                       FetchStandardParallels()                       */
    3097             : /************************************************************************/
    3098             : 
    3099             : std::vector<std::string>
    3100           0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) const
    3101             : {
    3102             :     // cf-1.0 tags
    3103           0 :     const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
    3104             : 
    3105           0 :     std::vector<std::string> ret;
    3106           0 :     if (pszValue != nullptr)
    3107             :     {
    3108           0 :         CPLStringList aosValues;
    3109           0 :         if (pszValue[0] != '{' &&
    3110           0 :             CPLString(pszValue).Trim().find(' ') != std::string::npos)
    3111             :         {
    3112             :             // Some files like
    3113             :             // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
    3114             :             // do not use standard formatting for arrays, but just space
    3115             :             // separated syntax
    3116           0 :             aosValues = CSLTokenizeString2(pszValue, " ", 0);
    3117             :         }
    3118             :         else
    3119             :         {
    3120           0 :             aosValues = NCDFTokenizeArray(pszValue);
    3121             :         }
    3122           0 :         for (int i = 0; i < aosValues.size(); i++)
    3123             :         {
    3124           0 :             ret.push_back(aosValues[i]);
    3125             :         }
    3126             :     }
    3127             :     // Try gdal tags.
    3128             :     else
    3129             :     {
    3130           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
    3131             : 
    3132           0 :         if (pszValue != nullptr)
    3133           0 :             ret.push_back(pszValue);
    3134             : 
    3135           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
    3136             : 
    3137           0 :         if (pszValue != nullptr)
    3138           0 :             ret.push_back(pszValue);
    3139             :     }
    3140             : 
    3141           0 :     return ret;
    3142             : }
    3143             : 
    3144             : /************************************************************************/
    3145             : /*                             FetchAttr()                              */
    3146             : /************************************************************************/
    3147             : 
    3148        3958 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
    3149             :                                      const char *pszAttr) const
    3150             : 
    3151             : {
    3152        3958 :     char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
    3153        3958 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
    3154        3958 :     CPLFree(pszKey);
    3155        3958 :     return pszValue;
    3156             : }
    3157             : 
    3158        2600 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
    3159             :                                      const char *pszAttr) const
    3160             : 
    3161             : {
    3162        2600 :     char *pszVarFullName = nullptr;
    3163        2600 :     NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
    3164        2600 :     const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
    3165        2600 :     CPLFree(pszVarFullName);
    3166        2600 :     return pszValue;
    3167             : }
    3168             : 
    3169             : /************************************************************************/
    3170             : /*                         IsDifferenceBelow()                          */
    3171             : /************************************************************************/
    3172             : 
    3173        1115 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
    3174             : {
    3175        1115 :     const double dfAbsDiff = fabs(dfA - dfB);
    3176        1115 :     return dfAbsDiff <= dfError;
    3177             : }
    3178             : 
    3179             : /************************************************************************/
    3180             : /*                        SetProjectionFromVar()                        */
    3181             : /************************************************************************/
    3182         558 : void netCDFDataset::SetProjectionFromVar(
    3183             :     int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
    3184             :     std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
    3185             :     std::vector<std::string> *paosRemovedMDItems)
    3186             : {
    3187         558 :     bool bGotGeogCS = false;
    3188         558 :     bool bGotCfSRS = false;
    3189         558 :     bool bGotCfWktSRS = false;
    3190         558 :     bool bGotGdalSRS = false;
    3191         558 :     bool bGotCfGT = false;
    3192         558 :     bool bGotGdalGT = false;
    3193             : 
    3194             :     // These values from CF metadata.
    3195         558 :     OGRSpatialReference oSRS;
    3196         558 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3197         558 :     size_t xdim = nRasterXSize;
    3198         558 :     size_t ydim = nRasterYSize;
    3199             : 
    3200             :     // These values from GDAL metadata.
    3201         558 :     const char *pszWKT = nullptr;
    3202         558 :     const char *pszGeoTransform = nullptr;
    3203             : 
    3204         558 :     netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.
    3205             : 
    3206         558 :     CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
    3207             :              nVarId);
    3208             : 
    3209             :     // Get x/y range information.
    3210             : 
    3211             :     // Temp variables to use in SetGeoTransform() and SetProjection().
    3212         558 :     GDALGeoTransform tmpGT;
    3213             : 
    3214             :     // Look for grid_mapping metadata.
    3215         558 :     const char *pszValue = pszGivenGM;
    3216         558 :     CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may
    3217             :     // point to it
    3218         558 :     if (pszValue == nullptr)
    3219             :     {
    3220         515 :         pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
    3221         515 :         if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
    3222             :         {
    3223             :             // Expanded form of grid_mapping
    3224             :             // e.g. "crsOSGB: x y crsWGS84: lat lon"
    3225             :             // Pickup the grid_mapping whose coordinates are dimensions of the
    3226             :             // variable
    3227           6 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
    3228           3 :             if ((aosTokens.size() % 3) == 0)
    3229             :             {
    3230           3 :                 for (int i = 0; i < aosTokens.size() / 3; i++)
    3231             :                 {
    3232           3 :                     if (CSLFindString(poDS->papszDimName,
    3233           9 :                                       aosTokens[3 * i + 1]) >= 0 &&
    3234           3 :                         CSLFindString(poDS->papszDimName,
    3235           3 :                                       aosTokens[3 * i + 2]) >= 0)
    3236             :                     {
    3237           3 :                         osTmpGridMapping = aosTokens[3 * i];
    3238           6 :                         if (!osTmpGridMapping.empty() &&
    3239           3 :                             osTmpGridMapping.back() == ':')
    3240             :                         {
    3241           3 :                             osTmpGridMapping.resize(osTmpGridMapping.size() -
    3242             :                                                     1);
    3243             :                         }
    3244           3 :                         pszValue = osTmpGridMapping.c_str();
    3245           3 :                         break;
    3246             :                     }
    3247             :                 }
    3248             :             }
    3249             :         }
    3250             :     }
    3251         558 :     char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
    3252             : 
    3253         558 :     if (!EQUAL(pszGridMappingValue, ""))
    3254             :     {
    3255             :         // Read grid_mapping metadata.
    3256         239 :         int nProjGroupID = -1;
    3257         239 :         int nProjVarID = -1;
    3258         239 :         if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
    3259         239 :                            &nProjVarID) == CE_None)
    3260             :         {
    3261         238 :             poDS->ReadAttributes(nProjGroupID, nProjVarID);
    3262             : 
    3263             :             // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
    3264         238 :             CPLFree(pszGridMappingValue);
    3265         238 :             pszGridMappingValue = nullptr;
    3266         238 :             NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
    3267         238 :             if (pszGridMappingValue)
    3268             :             {
    3269         238 :                 CPLDebug("GDAL_netCDF", "got grid_mapping %s",
    3270             :                          pszGridMappingValue);
    3271         238 :                 pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
    3272         238 :                 if (!pszWKT)
    3273             :                 {
    3274          35 :                     pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
    3275             :                 }
    3276             :                 else
    3277             :                 {
    3278         203 :                     bGotGdalSRS = true;
    3279         203 :                     CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
    3280             :                 }
    3281         238 :                 if (pszWKT)
    3282             :                 {
    3283         208 :                     if (!bGotGdalSRS)
    3284             :                     {
    3285           5 :                         bGotCfWktSRS = true;
    3286           5 :                         CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3287             :                     }
    3288         208 :                     if (returnProjStr != nullptr)
    3289             :                     {
    3290          41 :                         (*returnProjStr) = std::string(pszWKT);
    3291             :                     }
    3292             :                     else
    3293             :                     {
    3294         167 :                         m_bAddedProjectionVarsDefs = true;
    3295         167 :                         m_bAddedProjectionVarsData = true;
    3296         334 :                         OGRSpatialReference oSRSTmp;
    3297         167 :                         oSRSTmp.SetAxisMappingStrategy(
    3298             :                             OAMS_TRADITIONAL_GIS_ORDER);
    3299         167 :                         oSRSTmp.importFromWkt(pszWKT);
    3300         167 :                         SetSpatialRefNoUpdate(&oSRSTmp);
    3301             :                     }
    3302             :                     pszGeoTransform =
    3303         208 :                         FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
    3304             :                 }
    3305             :             }
    3306             :             else
    3307             :             {
    3308           0 :                 pszGridMappingValue = CPLStrdup("");
    3309             :             }
    3310             :         }
    3311             :     }
    3312             : 
    3313             :     // Get information about the file.
    3314             :     //
    3315             :     // Was this file created by the GDAL netcdf driver?
    3316             :     // Was this file created by the newer (CF-conformant) driver?
    3317             :     //
    3318             :     // 1) If GDAL netcdf metadata is set, and version >= 1.9,
    3319             :     //    it was created with the new driver
    3320             :     // 2) Else, if spatial_ref and GeoTransform are present in the
    3321             :     //    grid_mapping variable, it was created by the old driver
    3322         558 :     pszValue = FetchAttr("NC_GLOBAL", "GDAL");
    3323             : 
    3324         558 :     if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
    3325             :     {
    3326         257 :         bIsGdalFile = true;
    3327         257 :         bIsGdalCfFile = true;
    3328             :     }
    3329         301 :     else if (pszWKT != nullptr && pszGeoTransform != nullptr)
    3330             :     {
    3331          24 :         bIsGdalFile = true;
    3332          24 :         bIsGdalCfFile = false;
    3333             :     }
    3334             : 
    3335             :     // Set default bottom-up default value.
    3336             :     // Y axis dimension and absence of GT can modify this value.
    3337             :     // Override with Config option GDAL_NETCDF_BOTTOMUP.
    3338             : 
    3339             :     // New driver is bottom-up by default.
    3340         558 :     if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
    3341          26 :         poDS->bBottomUp = false;
    3342             :     else
    3343         532 :         poDS->bBottomUp = true;
    3344             : 
    3345         558 :     CPLDebug("GDAL_netCDF",
    3346             :              "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
    3347         558 :              static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
    3348         558 :              static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
    3349             : 
    3350             :     // Read projection coordinates.
    3351             : 
    3352         558 :     int nGroupDimXID = -1;
    3353         558 :     int nVarDimXID = -1;
    3354         558 :     int nGroupDimYID = -1;
    3355         558 :     int nVarDimYID = -1;
    3356         558 :     if (sg != nullptr)
    3357             :     {
    3358          43 :         nGroupDimXID = sg->get_ncID();
    3359          43 :         nGroupDimYID = sg->get_ncID();
    3360          43 :         nVarDimXID = sg->getNodeCoordVars()[0];
    3361          43 :         nVarDimYID = sg->getNodeCoordVars()[1];
    3362             :     }
    3363             : 
    3364         558 :     if (!bReadSRSOnly)
    3365             :     {
    3366         368 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
    3367             :                        &nVarDimXID);
    3368         368 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
    3369             :                        &nVarDimYID);
    3370             :         // TODO: if above resolving fails we should also search for coordinate
    3371             :         // variables without same name than dimension using the same resolving
    3372             :         // logic. This should handle for example NASA Ocean Color L2 products.
    3373             : 
    3374             :         const bool bIgnoreXYAxisNameChecks =
    3375         736 :             CPLTestBool(CSLFetchNameValueDef(
    3376         368 :                 papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
    3377             :                 CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
    3378         368 :                                    "NO"))) ||
    3379             :             // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
    3380             :             // and transform attributes
    3381         368 :             (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
    3382         736 :              FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
    3383         367 :             FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
    3384             : 
    3385             :         // Check that they are 1D or 2D variables
    3386         368 :         if (nVarDimXID >= 0)
    3387             :         {
    3388         260 :             int ndims = -1;
    3389         260 :             nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
    3390         260 :             if (ndims == 0 || ndims > 2)
    3391           0 :                 nVarDimXID = -1;
    3392         260 :             else if (!bIgnoreXYAxisNameChecks)
    3393             :             {
    3394         258 :                 if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    3395         168 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
    3396             :                     // In case of inversion of X/Y
    3397         458 :                     !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
    3398          32 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
    3399             :                 {
    3400             :                     char szVarNameX[NC_MAX_NAME + 1];
    3401          32 :                     CPL_IGNORE_RET_VAL(
    3402          32 :                         nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    3403          32 :                     if (!(ndims == 1 &&
    3404          31 :                           (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
    3405          30 :                            EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
    3406             :                     {
    3407          31 :                         CPLDebug(
    3408             :                             "netCDF",
    3409             :                             "Georeferencing ignored due to non-specific "
    3410             :                             "enough X axis name. "
    3411             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3412             :                             "as configuration option to bypass this check");
    3413          31 :                         nVarDimXID = -1;
    3414             :                     }
    3415             :                 }
    3416             :             }
    3417             :         }
    3418             : 
    3419         368 :         if (nVarDimYID >= 0)
    3420             :         {
    3421         262 :             int ndims = -1;
    3422         262 :             nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
    3423         262 :             if (ndims == 0 || ndims > 2)
    3424           1 :                 nVarDimYID = -1;
    3425         261 :             else if (!bIgnoreXYAxisNameChecks)
    3426             :             {
    3427         259 :                 if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
    3428         169 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
    3429             :                     // In case of inversion of X/Y
    3430         461 :                     !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    3431          33 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
    3432             :                 {
    3433             :                     char szVarNameY[NC_MAX_NAME + 1];
    3434          33 :                     CPL_IGNORE_RET_VAL(
    3435          33 :                         nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    3436          33 :                     if (!(ndims == 1 &&
    3437          33 :                           (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
    3438          32 :                            EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
    3439             :                     {
    3440          32 :                         CPLDebug(
    3441             :                             "netCDF",
    3442             :                             "Georeferencing ignored due to non-specific "
    3443             :                             "enough Y axis name. "
    3444             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3445             :                             "as configuration option to bypass this check");
    3446          32 :                         nVarDimYID = -1;
    3447             :                     }
    3448             :                 }
    3449             :             }
    3450             :         }
    3451             : 
    3452         368 :         if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
    3453             :         {
    3454           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3455             :                      "1-pixel width/height files not supported, "
    3456             :                      "xdim: %ld ydim: %ld",
    3457             :                      static_cast<long>(xdim), static_cast<long>(ydim));
    3458           0 :             nVarDimXID = -1;
    3459           0 :             nVarDimYID = -1;
    3460             :         }
    3461             :     }
    3462             : 
    3463         558 :     const char *pszUnits = nullptr;
    3464         558 :     if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3465             :     {
    3466         272 :         const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
    3467         272 :         const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
    3468             :         // Normalize degrees_east/degrees_north to degrees
    3469             :         // Cf https://github.com/OSGeo/gdal/issues/11009
    3470         272 :         if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
    3471          79 :             pszUnitsX = "degrees";
    3472         272 :         if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
    3473          79 :             pszUnitsY = "degrees";
    3474             : 
    3475         272 :         if (pszUnitsX && pszUnitsY)
    3476             :         {
    3477         225 :             if (EQUAL(pszUnitsX, pszUnitsY))
    3478         222 :                 pszUnits = pszUnitsX;
    3479           3 :             else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3480             :             {
    3481           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3482             :                          "X axis unit (%s) is different from Y axis "
    3483             :                          "unit (%s). SRS will ignore axis unit and be "
    3484             :                          "likely wrong.",
    3485             :                          pszUnitsX, pszUnitsY);
    3486             :             }
    3487             :         }
    3488          47 :         else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3489             :         {
    3490           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3491             :                      "X axis unit is defined, but not Y one ."
    3492             :                      "SRS will ignore axis unit and be likely wrong.");
    3493             :         }
    3494          47 :         else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3495             :         {
    3496           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3497             :                      "Y axis unit is defined, but not X one ."
    3498             :                      "SRS will ignore axis unit and be likely wrong.");
    3499             :         }
    3500             :     }
    3501             : 
    3502         558 :     if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3503             :     {
    3504          31 :         CPLStringList aosGridMappingKeyValues;
    3505          31 :         const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
    3506         789 :         for (const char *const *papszIter = papszMetadata;
    3507         789 :              papszIter && *papszIter; ++papszIter)
    3508             :         {
    3509         758 :             if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
    3510         236 :                 (*papszIter)[nLenGridMappingValue] == '#')
    3511             :             {
    3512         236 :                 char *pszKey = nullptr;
    3513         472 :                 pszValue = CPLParseNameValue(
    3514         236 :                     *papszIter + nLenGridMappingValue + 1, &pszKey);
    3515         236 :                 if (pszKey && pszValue)
    3516         236 :                     aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
    3517         236 :                 CPLFree(pszKey);
    3518             :             }
    3519             :         }
    3520             : 
    3521          31 :         bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
    3522             :                          CF_PP_SEMI_MAJOR_AXIS) != nullptr;
    3523             : 
    3524          31 :         oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
    3525          31 :         bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
    3526             :     }
    3527             :     else
    3528             :     {
    3529             :         // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
    3530             :         // attribute hold on the variable of interest that contains a PROJ.4
    3531             :         // string
    3532         527 :         pszValue = FetchAttr(nGroupId, nVarId, "crs");
    3533         528 :         if (pszValue &&
    3534           1 :             (strstr(pszValue, "+proj=") != nullptr ||
    3535           0 :              strstr(pszValue, "GEOGCS") != nullptr ||
    3536           0 :              strstr(pszValue, "PROJCS") != nullptr ||
    3537         528 :              strstr(pszValue, "EPSG:") != nullptr) &&
    3538           1 :             oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
    3539             :         {
    3540           1 :             bGotCfSRS = true;
    3541             :         }
    3542             :     }
    3543             : 
    3544             :     // Set Projection from CF.
    3545         558 :     double dfLinearUnitsConvFactor = 1.0;
    3546         558 :     if ((bGotGeogCS || bGotCfSRS))
    3547             :     {
    3548          31 :         if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3549             :         {
    3550             :             // Set SRS Units.
    3551             : 
    3552             :             // Check units for x and y.
    3553          28 :             if (oSRS.IsProjected())
    3554             :             {
    3555          25 :                 dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
    3556             : 
    3557             :                 // If the user doesn't ask to preserve the axis unit,
    3558             :                 // then normalize to metre
    3559          31 :                 if (dfLinearUnitsConvFactor != 1.0 &&
    3560           6 :                     !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
    3561             :                                   false))
    3562             :                 {
    3563           5 :                     oSRS.SetLinearUnits("metre", 1.0);
    3564           5 :                     oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
    3565             :                 }
    3566             :                 else
    3567             :                 {
    3568          20 :                     dfLinearUnitsConvFactor = 1.0;
    3569             :                 }
    3570             :             }
    3571             :         }
    3572             : 
    3573             :         // Set projection.
    3574          31 :         char *pszTempProjection = nullptr;
    3575          31 :         oSRS.exportToWkt(&pszTempProjection);
    3576          31 :         if (pszTempProjection)
    3577             :         {
    3578          31 :             CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3579          31 :             if (returnProjStr != nullptr)
    3580             :             {
    3581           2 :                 (*returnProjStr) = std::string(pszTempProjection);
    3582             :             }
    3583             :             else
    3584             :             {
    3585          29 :                 m_bAddedProjectionVarsDefs = true;
    3586          29 :                 m_bAddedProjectionVarsData = true;
    3587          29 :                 SetSpatialRefNoUpdate(&oSRS);
    3588             :             }
    3589             :         }
    3590          31 :         CPLFree(pszTempProjection);
    3591             :     }
    3592             : 
    3593         558 :     if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
    3594             :         ydim > 0)
    3595             :     {
    3596             :         double *pdfXCoord =
    3597         229 :             static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
    3598             :         double *pdfYCoord =
    3599         229 :             static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
    3600             : 
    3601         229 :         size_t start[2] = {0, 0};
    3602         229 :         size_t edge[2] = {xdim, 0};
    3603         229 :         int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
    3604             :                                         pdfXCoord);
    3605         229 :         NCDF_ERR(status);
    3606             : 
    3607         229 :         edge[0] = ydim;
    3608         229 :         status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
    3609             :                                     pdfYCoord);
    3610         229 :         NCDF_ERR(status);
    3611             : 
    3612         229 :         nc_type nc_var_dimx_datatype = NC_NAT;
    3613             :         status =
    3614         229 :             nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
    3615         229 :         NCDF_ERR(status);
    3616             : 
    3617         229 :         nc_type nc_var_dimy_datatype = NC_NAT;
    3618             :         status =
    3619         229 :             nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
    3620         229 :         NCDF_ERR(status);
    3621             : 
    3622         229 :         if (!poDS->bSwitchedXY)
    3623             :         {
    3624             :             // Convert ]180,540] longitude values to ]-180,0].
    3625         317 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3626          90 :                 CPLTestBool(
    3627             :                     CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
    3628             :             {
    3629             :                 // If minimum longitude is > 180, subtract 360 from all.
    3630             :                 // Add a check on the maximum X value too, since
    3631             :                 // NCDFIsVarLongitude() is not very specific by default (see
    3632             :                 // https://github.com/OSGeo/gdal/issues/1440)
    3633          97 :                 if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
    3634           7 :                     std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
    3635             :                 {
    3636           0 :                     CPLDebug(
    3637             :                         "GDAL_netCDF",
    3638             :                         "Offsetting longitudes from ]180,540] to ]-180,180]. "
    3639             :                         "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
    3640           0 :                     for (size_t i = 0; i < xdim; i++)
    3641           0 :                         pdfXCoord[i] -= 360;
    3642             :                 }
    3643             :             }
    3644             :         }
    3645             : 
    3646             :         // Is pixel spacing uniform across the map?
    3647             : 
    3648             :         // Check Longitude.
    3649             : 
    3650         229 :         bool bLonSpacingOK = false;
    3651         229 :         if (xdim == 2)
    3652             :         {
    3653          29 :             bLonSpacingOK = true;
    3654             :         }
    3655             :         else
    3656             :         {
    3657         200 :             bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
    3658             : 
    3659             :             // fix longitudes if longitudes should increase from
    3660             :             // west to east, but west > east
    3661         280 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3662          80 :                 !bWestIsLeft)
    3663             :             {
    3664           2 :                 size_t ndecreases = 0;
    3665             : 
    3666             :                 // there is lon wrap if longitudes increase
    3667             :                 // with one single decrease
    3668         107 :                 for (size_t i = 1; i < xdim; i++)
    3669             :                 {
    3670         105 :                     if (pdfXCoord[i] < pdfXCoord[i - 1])
    3671           1 :                         ndecreases++;
    3672             :                 }
    3673             : 
    3674           2 :                 if (ndecreases == 1)
    3675             :                 {
    3676           1 :                     CPLDebug("GDAL_netCDF", "longitude wrap detected");
    3677           4 :                     for (size_t i = 0; i < xdim; i++)
    3678             :                     {
    3679           3 :                         if (pdfXCoord[i] > pdfXCoord[xdim - 1])
    3680           1 :                             pdfXCoord[i] -= 360;
    3681             :                     }
    3682             :                 }
    3683             :             }
    3684             : 
    3685         200 :             const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
    3686         200 :             const double dfSpacingMiddle =
    3687         200 :                 pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
    3688         200 :             const double dfSpacingLast =
    3689         200 :                 pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
    3690             : 
    3691         200 :             CPLDebug("GDAL_netCDF",
    3692             :                      "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3693             :                      "dfSpacingLast: %f",
    3694             :                      static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
    3695             :                      dfSpacingLast);
    3696             : #ifdef NCDF_DEBUG
    3697             :             CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
    3698             :                      pdfXCoord[1], pdfXCoord[xdim / 2],
    3699             :                      pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
    3700             :                      pdfXCoord[xdim - 1]);
    3701             : #endif
    3702             : 
    3703             :             // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
    3704             :             // requires a 0.02% tolerance, so let's settle for 0.05%
    3705             : 
    3706             :             // For float variables, increase to 0.2% (as seen in
    3707             :             // https://github.com/OSGeo/gdal/issues/3663)
    3708         200 :             const double dfEpsRel =
    3709         200 :                 nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3710             : 
    3711             :             const double dfEps =
    3712             :                 dfEpsRel *
    3713         400 :                 std::max(fabs(dfSpacingBegin),
    3714         200 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3715         394 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3716         394 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3717         194 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3718             :             {
    3719         194 :                 bLonSpacingOK = true;
    3720             :             }
    3721           6 :             else if (CPLTestBool(CPLGetConfigOption(
    3722             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3723             :             {
    3724           0 :                 bLonSpacingOK = true;
    3725           0 :                 CPLDebug(
    3726             :                     "GDAL_netCDF",
    3727             :                     "Longitude/X is not equally spaced, but will be considered "
    3728             :                     "as such because of "
    3729             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3730             :             }
    3731             :         }
    3732             : 
    3733         229 :         if (bLonSpacingOK == false)
    3734             :         {
    3735           6 :             CPLDebug(
    3736             :                 "GDAL_netCDF", "%s",
    3737             :                 "Longitude/X is not equally spaced (with a 0.05% tolerance). "
    3738             :                 "You may set the "
    3739             :                 "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3740             :                 "option to YES to ignore this check");
    3741             :         }
    3742             : 
    3743             :         // Check Latitude.
    3744         229 :         bool bLatSpacingOK = false;
    3745             : 
    3746         229 :         if (ydim == 2)
    3747             :         {
    3748          49 :             bLatSpacingOK = true;
    3749             :         }
    3750             :         else
    3751             :         {
    3752         180 :             const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
    3753         180 :             const double dfSpacingMiddle =
    3754         180 :                 pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
    3755             : 
    3756         180 :             const double dfSpacingLast =
    3757         180 :                 pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
    3758             : 
    3759         180 :             CPLDebug("GDAL_netCDF",
    3760             :                      "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3761             :                      "dfSpacingLast: %f",
    3762             :                      (long)ydim, dfSpacingBegin, dfSpacingMiddle,
    3763             :                      dfSpacingLast);
    3764             : #ifdef NCDF_DEBUG
    3765             :             CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
    3766             :                      pdfYCoord[1], pdfYCoord[ydim / 2],
    3767             :                      pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
    3768             :                      pdfYCoord[ydim - 1]);
    3769             : #endif
    3770             : 
    3771         180 :             const double dfEpsRel =
    3772         180 :                 nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3773             : 
    3774             :             const double dfEps =
    3775             :                 dfEpsRel *
    3776         360 :                 std::max(fabs(dfSpacingBegin),
    3777         180 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3778         358 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3779         358 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3780         169 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3781             :             {
    3782         169 :                 bLatSpacingOK = true;
    3783             :             }
    3784          11 :             else if (CPLTestBool(CPLGetConfigOption(
    3785             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3786             :             {
    3787           0 :                 bLatSpacingOK = true;
    3788           0 :                 CPLDebug(
    3789             :                     "GDAL_netCDF",
    3790             :                     "Latitude/Y is not equally spaced, but will be considered "
    3791             :                     "as such because of "
    3792             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3793             :             }
    3794          11 :             else if (!oSRS.IsProjected() &&
    3795          11 :                      fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
    3796          30 :                      fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
    3797           8 :                      fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
    3798             :             {
    3799           8 :                 bLatSpacingOK = true;
    3800           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3801             :                          "Latitude grid not spaced evenly.  "
    3802             :                          "Setting projection for grid spacing is "
    3803             :                          "within 0.1 degrees threshold.");
    3804             : 
    3805           8 :                 CPLDebug("GDAL_netCDF",
    3806             :                          "Latitude grid not spaced evenly, but within 0.1 "
    3807             :                          "degree threshold (probably a Gaussian grid).  "
    3808             :                          "Saving original latitude values in Y_VALUES "
    3809             :                          "geolocation metadata");
    3810           8 :                 Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
    3811             :             }
    3812             : 
    3813         180 :             if (bLatSpacingOK == false)
    3814             :             {
    3815           3 :                 CPLDebug(
    3816             :                     "GDAL_netCDF", "%s",
    3817             :                     "Latitude/Y is not equally spaced (with a 0.05% "
    3818             :                     "tolerance). "
    3819             :                     "You may set the "
    3820             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3821             :                     "option to YES to ignore this check");
    3822             :             }
    3823             :         }
    3824             : 
    3825         229 :         if (bLonSpacingOK && bLatSpacingOK)
    3826             :         {
    3827             :             // We have gridded data so we can set the Georeferencing info.
    3828             : 
    3829             :             // Enable GeoTransform.
    3830             : 
    3831             :             // In the following "actual_range" and "node_offset"
    3832             :             // are attributes used by netCDF files created by GMT.
    3833             :             // If we find them we know how to proceed. Else, use
    3834             :             // the original algorithm.
    3835         222 :             bGotCfGT = true;
    3836             : 
    3837         222 :             int node_offset = 0;
    3838             :             const bool bUseActualRange =
    3839         222 :                 NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset",
    3840         222 :                                   &node_offset) == CE_None;
    3841             : 
    3842         222 :             double adfActualRange[2] = {0.0, 0.0};
    3843         222 :             double xMinMax[2] = {0.0, 0.0};
    3844         222 :             double yMinMax[2] = {0.0, 0.0};
    3845             : 
    3846             :             const auto RoundMinMaxForFloatVals =
    3847          60 :                 [](double &dfMin, double &dfMax, int nIntervals)
    3848             :             {
    3849             :                 // Helps for a case where longitudes range from
    3850             :                 // -179.99 to 180.0 with a 0.01 degree spacing.
    3851             :                 // However as this is encoded in a float array,
    3852             :                 // -179.99 is actually read as -179.99000549316406 as
    3853             :                 // a double. Try to detect that and correct the rounding
    3854             : 
    3855          88 :                 const auto IsAlmostInteger = [](double dfVal)
    3856             :                 {
    3857          88 :                     constexpr double THRESHOLD_INTEGER = 1e-3;
    3858          88 :                     return std::fabs(dfVal - std::round(dfVal)) <=
    3859          88 :                            THRESHOLD_INTEGER;
    3860             :                 };
    3861             : 
    3862          60 :                 const double dfSpacing = (dfMax - dfMin) / nIntervals;
    3863          60 :                 if (dfSpacing > 0)
    3864             :                 {
    3865          48 :                     const double dfInvSpacing = 1.0 / dfSpacing;
    3866          48 :                     if (IsAlmostInteger(dfInvSpacing))
    3867             :                     {
    3868          20 :                         const double dfRoundedSpacing =
    3869          20 :                             1.0 / std::round(dfInvSpacing);
    3870          20 :                         const double dfMinDivRoundedSpacing =
    3871          20 :                             dfMin / dfRoundedSpacing;
    3872          20 :                         const double dfMaxDivRoundedSpacing =
    3873          20 :                             dfMax / dfRoundedSpacing;
    3874          40 :                         if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
    3875          20 :                             IsAlmostInteger(dfMaxDivRoundedSpacing))
    3876             :                         {
    3877          20 :                             const double dfRoundedMin =
    3878          20 :                                 std::round(dfMinDivRoundedSpacing) *
    3879             :                                 dfRoundedSpacing;
    3880          20 :                             const double dfRoundedMax =
    3881          20 :                                 std::round(dfMaxDivRoundedSpacing) *
    3882             :                                 dfRoundedSpacing;
    3883          20 :                             if (static_cast<float>(dfMin) ==
    3884          20 :                                     static_cast<float>(dfRoundedMin) &&
    3885           8 :                                 static_cast<float>(dfMax) ==
    3886           8 :                                     static_cast<float>(dfRoundedMax))
    3887             :                             {
    3888           7 :                                 dfMin = dfRoundedMin;
    3889           7 :                                 dfMax = dfRoundedMax;
    3890             :                             }
    3891             :                         }
    3892             :                     }
    3893             :                 }
    3894          60 :             };
    3895             : 
    3896         225 :             if (bUseActualRange &&
    3897           3 :                 !nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
    3898             :                                    adfActualRange))
    3899             :             {
    3900           1 :                 xMinMax[0] = adfActualRange[0];
    3901           1 :                 xMinMax[1] = adfActualRange[1];
    3902             : 
    3903             :                 // Present xMinMax[] in the same order as padfXCoord
    3904           1 :                 if ((xMinMax[0] - xMinMax[1]) *
    3905           1 :                         (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
    3906             :                     0)
    3907             :                 {
    3908           0 :                     std::swap(xMinMax[0], xMinMax[1]);
    3909             :                 }
    3910             :             }
    3911             :             else
    3912             :             {
    3913         221 :                 xMinMax[0] = pdfXCoord[0];
    3914         221 :                 xMinMax[1] = pdfXCoord[xdim - 1];
    3915         221 :                 node_offset = 0;
    3916             : 
    3917         221 :                 if (nc_var_dimx_datatype == NC_FLOAT)
    3918             :                 {
    3919          30 :                     RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
    3920          30 :                                             poDS->nRasterXSize - 1);
    3921             :                 }
    3922             :             }
    3923             : 
    3924         225 :             if (bUseActualRange &&
    3925           3 :                 !nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
    3926             :                                    adfActualRange))
    3927             :             {
    3928           1 :                 yMinMax[0] = adfActualRange[0];
    3929           1 :                 yMinMax[1] = adfActualRange[1];
    3930             : 
    3931             :                 // Present yMinMax[] in the same order as pdfYCoord
    3932           1 :                 if ((yMinMax[0] - yMinMax[1]) *
    3933           1 :                         (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
    3934             :                     0)
    3935             :                 {
    3936           0 :                     std::swap(yMinMax[0], yMinMax[1]);
    3937             :                 }
    3938             :             }
    3939             :             else
    3940             :             {
    3941         221 :                 yMinMax[0] = pdfYCoord[0];
    3942         221 :                 yMinMax[1] = pdfYCoord[ydim - 1];
    3943         221 :                 node_offset = 0;
    3944             : 
    3945         221 :                 if (nc_var_dimy_datatype == NC_FLOAT)
    3946             :                 {
    3947          30 :                     RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
    3948          30 :                                             poDS->nRasterYSize - 1);
    3949             :                 }
    3950             :             }
    3951             : 
    3952         222 :             double dfCoordOffset = 0.0;
    3953         222 :             double dfCoordScale = 1.0;
    3954         222 :             if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
    3955         226 :                                    &dfCoordOffset) &&
    3956           4 :                 !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
    3957             :                                    &dfCoordScale))
    3958             :             {
    3959           4 :                 xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
    3960           4 :                 xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
    3961             :             }
    3962             : 
    3963         222 :             if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
    3964         226 :                                    &dfCoordOffset) &&
    3965           4 :                 !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
    3966             :                                    &dfCoordScale))
    3967             :             {
    3968           4 :                 yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
    3969           4 :                 yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
    3970             :             }
    3971             : 
    3972             :             // Check for reverse order of y-coordinate.
    3973         222 :             if (!bSwitchedXY)
    3974             :             {
    3975         220 :                 poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
    3976         220 :                 if (!poDS->bBottomUp)
    3977             :                 {
    3978          32 :                     std::swap(yMinMax[0], yMinMax[1]);
    3979             :                 }
    3980             :             }
    3981             : 
    3982             :             // Geostationary satellites can specify units in (micro)radians
    3983             :             // So we check if they do, and if so convert to linear units
    3984             :             // (meters)
    3985         222 :             const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
    3986         222 :             if (pszProjName != nullptr)
    3987             :             {
    3988          24 :                 if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
    3989             :                 {
    3990             :                     double satelliteHeight =
    3991           3 :                         oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
    3992           3 :                     size_t nAttlen = 0;
    3993             :                     char szUnits[NC_MAX_NAME + 1];
    3994           3 :                     szUnits[0] = '\0';
    3995           3 :                     nc_type nAttype = NC_NAT;
    3996           3 :                     nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
    3997             :                                &nAttlen);
    3998           6 :                     if (nAttlen < sizeof(szUnits) &&
    3999           3 :                         nc_get_att_text(nGroupId, nVarDimXID, "units",
    4000             :                                         szUnits) == NC_NOERR)
    4001             :                     {
    4002           3 :                         szUnits[nAttlen] = '\0';
    4003           3 :                         if (EQUAL(szUnits, "microradian"))
    4004             :                         {
    4005           1 :                             xMinMax[0] =
    4006           1 :                                 xMinMax[0] * satelliteHeight * 0.000001;
    4007           1 :                             xMinMax[1] =
    4008           1 :                                 xMinMax[1] * satelliteHeight * 0.000001;
    4009             :                         }
    4010           2 :                         else if (EQUAL(szUnits, "rad") ||
    4011           1 :                                  EQUAL(szUnits, "radian"))
    4012             :                         {
    4013           2 :                             xMinMax[0] = xMinMax[0] * satelliteHeight;
    4014           2 :                             xMinMax[1] = xMinMax[1] * satelliteHeight;
    4015             :                         }
    4016             :                     }
    4017           3 :                     szUnits[0] = '\0';
    4018           3 :                     nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
    4019             :                                &nAttlen);
    4020           6 :                     if (nAttlen < sizeof(szUnits) &&
    4021           3 :                         nc_get_att_text(nGroupId, nVarDimYID, "units",
    4022             :                                         szUnits) == NC_NOERR)
    4023             :                     {
    4024           3 :                         szUnits[nAttlen] = '\0';
    4025           3 :                         if (EQUAL(szUnits, "microradian"))
    4026             :                         {
    4027           1 :                             yMinMax[0] =
    4028           1 :                                 yMinMax[0] * satelliteHeight * 0.000001;
    4029           1 :                             yMinMax[1] =
    4030           1 :                                 yMinMax[1] * satelliteHeight * 0.000001;
    4031             :                         }
    4032           2 :                         else if (EQUAL(szUnits, "rad") ||
    4033           1 :                                  EQUAL(szUnits, "radian"))
    4034             :                         {
    4035           2 :                             yMinMax[0] = yMinMax[0] * satelliteHeight;
    4036           2 :                             yMinMax[1] = yMinMax[1] * satelliteHeight;
    4037             :                         }
    4038             :                     }
    4039             :                 }
    4040             :             }
    4041             : 
    4042         222 :             tmpGT[0] = xMinMax[0];
    4043         444 :             tmpGT[1] = (xMinMax[1] - xMinMax[0]) /
    4044         222 :                        (poDS->nRasterXSize + (node_offset - 1));
    4045         222 :             tmpGT[2] = 0;
    4046         222 :             if (bSwitchedXY)
    4047             :             {
    4048           2 :                 tmpGT[3] = yMinMax[0];
    4049           2 :                 tmpGT[4] = 0;
    4050           2 :                 tmpGT[5] = (yMinMax[1] - yMinMax[0]) /
    4051           2 :                            (poDS->nRasterYSize + (node_offset - 1));
    4052             :             }
    4053             :             else
    4054             :             {
    4055         220 :                 tmpGT[3] = yMinMax[1];
    4056         220 :                 tmpGT[4] = 0;
    4057         220 :                 tmpGT[5] = (yMinMax[0] - yMinMax[1]) /
    4058         220 :                            (poDS->nRasterYSize + (node_offset - 1));
    4059             :             }
    4060             : 
    4061             :             // Compute the center of the pixel.
    4062         222 :             if (!node_offset)
    4063             :             {
    4064             :                 // Otherwise its already the pixel center.
    4065         222 :                 tmpGT[0] -= (tmpGT[1] / 2);
    4066         222 :                 tmpGT[3] -= (tmpGT[5] / 2);
    4067             :             }
    4068             :         }
    4069             : 
    4070             :         const auto AreSRSEqualThroughProj4String =
    4071           2 :             [](const OGRSpatialReference &oSRS1,
    4072             :                const OGRSpatialReference &oSRS2)
    4073             :         {
    4074           2 :             char *pszProj4Str1 = nullptr;
    4075           2 :             oSRS1.exportToProj4(&pszProj4Str1);
    4076             : 
    4077           2 :             char *pszProj4Str2 = nullptr;
    4078           2 :             oSRS2.exportToProj4(&pszProj4Str2);
    4079             : 
    4080             :             {
    4081           2 :                 char *pszTmp = strstr(pszProj4Str1, "+datum=");
    4082           2 :                 if (pszTmp)
    4083           0 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4084             :             }
    4085             : 
    4086             :             {
    4087           2 :                 char *pszTmp = strstr(pszProj4Str2, "+datum=");
    4088           2 :                 if (pszTmp)
    4089           2 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4090             :             }
    4091             : 
    4092           2 :             bool bRet = false;
    4093           2 :             if (pszProj4Str1 && pszProj4Str2 &&
    4094           2 :                 EQUAL(pszProj4Str1, pszProj4Str2))
    4095             :             {
    4096           1 :                 bRet = true;
    4097             :             }
    4098             : 
    4099           2 :             CPLFree(pszProj4Str1);
    4100           2 :             CPLFree(pszProj4Str2);
    4101           2 :             return bRet;
    4102             :         };
    4103             : 
    4104         229 :         if (dfLinearUnitsConvFactor != 1.0)
    4105             :         {
    4106          35 :             for (int i = 0; i < 6; ++i)
    4107          30 :                 tmpGT[i] *= dfLinearUnitsConvFactor;
    4108             : 
    4109           5 :             if (paosRemovedMDItems)
    4110             :             {
    4111             :                 char szVarNameX[NC_MAX_NAME + 1];
    4112           5 :                 CPL_IGNORE_RET_VAL(
    4113           5 :                     nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4114             : 
    4115             :                 char szVarNameY[NC_MAX_NAME + 1];
    4116           5 :                 CPL_IGNORE_RET_VAL(
    4117           5 :                     nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4118             : 
    4119           5 :                 paosRemovedMDItems->push_back(
    4120             :                     CPLSPrintf("%s#units", szVarNameX));
    4121           5 :                 paosRemovedMDItems->push_back(
    4122             :                     CPLSPrintf("%s#units", szVarNameY));
    4123             :             }
    4124             :         }
    4125             : 
    4126             :         // If there is a global "geospatial_bounds_crs" attribute, check that it
    4127             :         // is consistent with the SRS, and if so, use it as the SRS
    4128             :         const char *pszGBCRS =
    4129         229 :             FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
    4130         229 :         if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
    4131             :         {
    4132           4 :             OGRSpatialReference oSRSFromGBCRS;
    4133           2 :             oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4134           2 :             if (oSRSFromGBCRS.SetFromUserInput(
    4135             :                     pszGBCRS,
    4136             :                     OGRSpatialReference::
    4137           4 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
    4138           2 :                 AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
    4139             :             {
    4140           1 :                 oSRS = std::move(oSRSFromGBCRS);
    4141           1 :                 SetSpatialRefNoUpdate(&oSRS);
    4142             :             }
    4143             :         }
    4144             : 
    4145         229 :         CPLFree(pdfXCoord);
    4146         229 :         CPLFree(pdfYCoord);
    4147             :     }  // end if(has dims)
    4148             : 
    4149             :     // Process custom GeoTransform GDAL value.
    4150         558 :     if (!EQUAL(pszGridMappingValue, ""))
    4151             :     {
    4152         239 :         if (pszGeoTransform != nullptr)
    4153             :         {
    4154             :             const CPLStringList aosGeoTransform(
    4155         256 :                 CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS));
    4156         128 :             if (aosGeoTransform.size() == 6)
    4157             :             {
    4158         128 :                 bool bUseGeoTransformFromAttribute = true;
    4159             : 
    4160         128 :                 GDALGeoTransform gtFromAttribute;
    4161         896 :                 for (int i = 0; i < 6; i++)
    4162             :                 {
    4163         768 :                     gtFromAttribute[i] = CPLAtof(aosGeoTransform[i]);
    4164             :                 }
    4165             : 
    4166             :                 // When GDAL writes a raster that is north-up oriented, it
    4167             :                 // writes the "GeoTransform" attribute unmodified, that is with
    4168             :                 // gt.yscale < 0, but the first line is actually the southern-most
    4169             :                 // one, consistently with the values of the "y" coordinate
    4170             :                 // variable. This is wrong... but we have always done that, so
    4171             :                 // this is hard to fix now.
    4172             :                 // However there are datasets like
    4173             :                 // https://public.hub.geosphere.at/datahub/resources/spartacus-v2-1d-1km/filelisting/TN/SPARTACUS2-DAILY_TN_2026.nc
    4174             :                 // that correctly use a positive gt.yscale value. So make sure to not emit
    4175             :                 // a warning when comparing against the geotransform derived from
    4176             :                 // the x/y coordinates.
    4177         128 :                 GDALGeoTransform gtFromAttributeNorthUp = gtFromAttribute;
    4178         133 :                 if (gtFromAttributeNorthUp.yscale > 0 &&
    4179           5 :                     gtFromAttributeNorthUp.IsAxisAligned())
    4180             :                 {
    4181           1 :                     gtFromAttributeNorthUp.yorig +=
    4182           1 :                         poDS->nRasterYSize * gtFromAttributeNorthUp.yscale;
    4183           1 :                     gtFromAttributeNorthUp.yscale =
    4184           1 :                         -gtFromAttributeNorthUp.yscale;
    4185             :                 }
    4186             : 
    4187         128 :                 if (bGotCfGT)
    4188             :                 {
    4189          98 :                     constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6;
    4190          98 :                     double dfMaxAbsoluteError = 0.0;
    4191         686 :                     for (int i = 0; i < 6; i++)
    4192             :                     {
    4193             :                         double dfAbsoluteError =
    4194         588 :                             std::abs(tmpGT[i] - gtFromAttributeNorthUp[i]);
    4195         588 :                         if (dfAbsoluteError >
    4196         588 :                             std::abs(gtFromAttributeNorthUp[i] *
    4197             :                                      GT_RELERROR_WARN_THRESHOLD))
    4198             :                         {
    4199           3 :                             dfMaxAbsoluteError =
    4200           3 :                                 std::max(dfMaxAbsoluteError, dfAbsoluteError);
    4201             :                         }
    4202             :                     }
    4203             : 
    4204          98 :                     if (dfMaxAbsoluteError > 0)
    4205             :                     {
    4206           3 :                         bUseGeoTransformFromAttribute = false;
    4207           3 :                         CPLError(CE_Warning, CPLE_AppDefined,
    4208             :                                  "GeoTransform read from attribute of %s "
    4209             :                                  "variable differs from value calculated from "
    4210             :                                  "dimension variables (max diff = %g). Using "
    4211             :                                  "value calculated from dimension variables.",
    4212             :                                  pszGridMappingValue, dfMaxAbsoluteError);
    4213             :                     }
    4214             :                 }
    4215             : 
    4216         128 :                 if (bUseGeoTransformFromAttribute)
    4217             :                 {
    4218         125 :                     if (bGotCfGT)
    4219             :                     {
    4220          95 :                         tmpGT = gtFromAttributeNorthUp;
    4221          95 :                         if (gtFromAttributeNorthUp.IsAxisAligned())
    4222             :                         {
    4223          95 :                             poDS->bBottomUp = true;
    4224             :                         }
    4225             :                     }
    4226             :                     else
    4227             :                     {
    4228          30 :                         tmpGT = gtFromAttribute;
    4229             :                     }
    4230         125 :                     bGotGdalGT = true;
    4231             :                 }
    4232             :             }
    4233             :         }
    4234             :         else
    4235             :         {
    4236             :             // Look for corner array values.
    4237             :             // CPLDebug("GDAL_netCDF",
    4238             :             //           "looking for geotransform corners");
    4239         111 :             bool bGotNN = false;
    4240         111 :             double dfNN = FetchCopyParam(pszGridMappingValue,
    4241             :                                          "Northernmost_Northing", 0, &bGotNN);
    4242             : 
    4243         111 :             bool bGotSN = false;
    4244         111 :             double dfSN = FetchCopyParam(pszGridMappingValue,
    4245             :                                          "Southernmost_Northing", 0, &bGotSN);
    4246             : 
    4247         111 :             bool bGotEE = false;
    4248         111 :             double dfEE = FetchCopyParam(pszGridMappingValue,
    4249             :                                          "Easternmost_Easting", 0, &bGotEE);
    4250             : 
    4251         111 :             bool bGotWE = false;
    4252         111 :             double dfWE = FetchCopyParam(pszGridMappingValue,
    4253             :                                          "Westernmost_Easting", 0, &bGotWE);
    4254             : 
    4255             :             // Only set the GeoTransform if we got all the values.
    4256         111 :             if (bGotNN && bGotSN && bGotEE && bGotWE)
    4257             :             {
    4258           0 :                 bGotGdalGT = true;
    4259             : 
    4260           0 :                 tmpGT[0] = dfWE;
    4261           0 :                 tmpGT[1] = (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
    4262           0 :                 tmpGT[2] = 0.0;
    4263           0 :                 tmpGT[3] = dfNN;
    4264           0 :                 tmpGT[4] = 0.0;
    4265           0 :                 tmpGT[5] = (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
    4266             :                 // Compute the center of the pixel.
    4267           0 :                 tmpGT[0] = dfWE - (tmpGT[1] / 2);
    4268           0 :                 tmpGT[3] = dfNN - (tmpGT[5] / 2);
    4269             :             }
    4270             :         }  // (pszGeoTransform != NULL)
    4271             : 
    4272         239 :         if (bGotGdalSRS && !bGotGdalGT)
    4273          78 :             CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
    4274             :     }
    4275             : 
    4276         558 :     if (bGotCfGT || bGotGdalGT)
    4277             :     {
    4278         252 :         CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
    4279         252 :                  static_cast<int>(poDS->bBottomUp));
    4280             :     }
    4281             : 
    4282         558 :     if (!pszWKT && !bGotCfSRS)
    4283             :     {
    4284             :         // Some netCDF files have a srid attribute (#6613) like
    4285             :         // urn:ogc:def:crs:EPSG::6931
    4286         319 :         const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
    4287         319 :         if (pszSRID != nullptr)
    4288             :         {
    4289           0 :             oSRS.Clear();
    4290           0 :             if (oSRS.SetFromUserInput(
    4291             :                     pszSRID,
    4292             :                     OGRSpatialReference::
    4293           0 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    4294             :             {
    4295           0 :                 char *pszWKTExport = nullptr;
    4296           0 :                 CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
    4297           0 :                 oSRS.exportToWkt(&pszWKTExport);
    4298           0 :                 if (returnProjStr != nullptr)
    4299             :                 {
    4300           0 :                     (*returnProjStr) = std::string(pszWKTExport);
    4301             :                 }
    4302             :                 else
    4303             :                 {
    4304           0 :                     m_bAddedProjectionVarsDefs = true;
    4305           0 :                     m_bAddedProjectionVarsData = true;
    4306           0 :                     SetSpatialRefNoUpdate(&oSRS);
    4307             :                 }
    4308           0 :                 CPLFree(pszWKTExport);
    4309             :             }
    4310             :         }
    4311             :     }
    4312             : 
    4313         558 :     CPLFree(pszGridMappingValue);
    4314             : 
    4315         558 :     if (bReadSRSOnly)
    4316         190 :         return;
    4317             : 
    4318             :     // Determines the SRS to be used by the geolocation array, if any
    4319         736 :     std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
    4320         368 :     if (!m_oSRS.IsEmpty())
    4321             :     {
    4322         290 :         OGRSpatialReference oGeogCRS;
    4323         145 :         oGeogCRS.CopyGeogCSFrom(&m_oSRS);
    4324         145 :         char *pszWKTTmp = nullptr;
    4325         145 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
    4326         145 :         if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
    4327             :         {
    4328         145 :             osGeolocWKT = pszWKTTmp;
    4329             :         }
    4330         145 :         CPLFree(pszWKTTmp);
    4331             :     }
    4332             : 
    4333             :     // Process geolocation arrays from CF "coordinates" attribute.
    4334         736 :     std::string osGeolocXName, osGeolocYName;
    4335         368 :     if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
    4336         368 :                              osGeolocYName))
    4337             :     {
    4338          61 :         bool bCanCancelGT = true;
    4339          61 :         if ((nVarDimXID != -1) && (nVarDimYID != -1))
    4340             :         {
    4341             :             char szVarNameX[NC_MAX_NAME + 1];
    4342          44 :             CPL_IGNORE_RET_VAL(
    4343          44 :                 nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4344             :             char szVarNameY[NC_MAX_NAME + 1];
    4345          44 :             CPL_IGNORE_RET_VAL(
    4346          44 :                 nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4347          44 :             bCanCancelGT =
    4348          44 :                 !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
    4349             :         }
    4350         100 :         if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
    4351          39 :             !bSwitchedXY)
    4352             :         {
    4353          37 :             bGotCfGT = false;
    4354             :         }
    4355             :     }
    4356         125 :     else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
    4357         435 :              (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
    4358           3 :              ((!bSwitchedXY &&
    4359           3 :                NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    4360           1 :                NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
    4361           2 :               (bSwitchedXY &&
    4362           0 :                NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    4363           0 :                NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
    4364             :     {
    4365             :         // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
    4366             :         // which is indexed by lat, lon variables, but lat has irregular
    4367             :         // spacing.
    4368           1 :         const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
    4369           1 :         const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
    4370           1 :         if (bSwitchedXY)
    4371             :         {
    4372           0 :             std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4373           0 :             GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4374             :         }
    4375             : 
    4376           1 :         CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4377             :                  pszGeolocXFullName, pszGeolocYFullName);
    4378             : 
    4379           1 :         GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4380             :                                         "GEOLOCATION");
    4381             : 
    4382           2 :         CPLString osTMP;
    4383           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4384           1 :                      pszGeolocXFullName);
    4385             : 
    4386           1 :         GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4387           1 :         GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4388           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4389           1 :                      pszGeolocYFullName);
    4390             : 
    4391           1 :         GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4392           1 :         GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4393             : 
    4394           1 :         GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4395           1 :         GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4396             : 
    4397           1 :         GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4398           1 :         GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4399             : 
    4400           1 :         GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4401             :                                         "PIXEL_CENTER", "GEOLOCATION");
    4402             :     }
    4403             : 
    4404             :     // Set GeoTransform if we got a complete one - after projection has been set
    4405         368 :     if (bGotCfGT || bGotGdalGT)
    4406             :     {
    4407         212 :         m_bAddedProjectionVarsDefs = true;
    4408         212 :         m_bAddedProjectionVarsData = true;
    4409         212 :         SetGeoTransformNoUpdate(tmpGT);
    4410             :     }
    4411             : 
    4412             :     // Debugging reports.
    4413         368 :     CPLDebug("GDAL_netCDF",
    4414             :              "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
    4415             :              "bGotGdalSRS=%d bGotGdalGT=%d",
    4416             :              static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
    4417             :              static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
    4418             :              static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
    4419             : 
    4420         368 :     if (!bGotCfGT && !bGotGdalGT)
    4421         156 :         CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
    4422             : 
    4423         368 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
    4424         156 :         CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
    4425             : 
    4426             :     // wish of 6195
    4427             :     // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
    4428         368 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
    4429             :     {
    4430         223 :         if (bGotCfGT || bGotGdalGT)
    4431             :         {
    4432         134 :             bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
    4433          67 :                 papszOpenOptions, "ASSUME_LONGLAT",
    4434             :                 CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
    4435             : 
    4436           2 :             if (bAssumedLongLat && tmpGT[0] >= -180 && tmpGT[0] < 360 &&
    4437           2 :                 (tmpGT[0] + tmpGT[1] * poDS->GetRasterXSize()) <= 360 &&
    4438          71 :                 tmpGT[3] <= 90 && tmpGT[3] > -90 &&
    4439           2 :                 (tmpGT[3] + tmpGT[5] * poDS->GetRasterYSize()) >= -90)
    4440             :             {
    4441             : 
    4442           2 :                 poDS->bIsGeographic = true;
    4443           2 :                 char *pszTempProjection = nullptr;
    4444             :                 // seems odd to use 4326 so OGC:CRS84
    4445           2 :                 oSRS.SetFromUserInput("OGC:CRS84");
    4446           2 :                 oSRS.exportToWkt(&pszTempProjection);
    4447           2 :                 if (returnProjStr != nullptr)
    4448             :                 {
    4449           0 :                     (*returnProjStr) = std::string(pszTempProjection);
    4450             :                 }
    4451             :                 else
    4452             :                 {
    4453           2 :                     m_bAddedProjectionVarsDefs = true;
    4454           2 :                     m_bAddedProjectionVarsData = true;
    4455           2 :                     SetSpatialRefNoUpdate(&oSRS);
    4456             :                 }
    4457           2 :                 CPLFree(pszTempProjection);
    4458             : 
    4459           2 :                 CPLDebug("netCDF",
    4460             :                          "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
    4461             :                          "none otherwise available and geotransform within "
    4462             :                          "suitable bounds. "
    4463             :                          "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
    4464             :                          "option or "
    4465             :                          "    ASSUME_LONGLAT=NO as open option to bypass this "
    4466             :                          "assumption.");
    4467             :             }
    4468             :         }
    4469             :     }
    4470             : 
    4471             : // Search for Well-known GeogCS if got only CF WKT
    4472             : // Disabled for now, as a named datum also include control points
    4473             : // (see mailing list and bug#4281
    4474             : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
    4475             : 
    4476             : // Disabled for now, but could be set in a config option.
    4477             : #if 0
    4478             :     bool bLookForWellKnownGCS = false;  // This could be a Config Option.
    4479             : 
    4480             :     if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
    4481             :     {
    4482             :         // ET - Could use a more exhaustive method by scanning all EPSG codes in
    4483             :         // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
    4484             :         // for comparing two WKT".
    4485             :         // This code could be contributed to a new function.
    4486             :         // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
    4487             :         //     const OGRSpatialReference *poOther) */
    4488             :         CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
    4489             :         const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
    4490             :         char *pszWKGCS = NULL;
    4491             :         oSRS.exportToPrettyWkt(&pszWKGCS);
    4492             :         for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
    4493             :         {
    4494             :             pszWKGCS = CPLStrdup(pszWKGCSList[i]);
    4495             :             OGRSpatialReference oSRSTmp;
    4496             :             oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
    4497             :             // Set datum to unknown, bug #4281.
    4498             :             if( oSRSTmp.GetAttrNode("DATUM" ) )
    4499             :                 oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
    4500             :             // Could use OGRSpatialReference::StripCTParms(), but let's keep
    4501             :             // TOWGS84.
    4502             :             oSRSTmp.GetRoot()->StripNodes("AXIS");
    4503             :             oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
    4504             :             oSRSTmp.GetRoot()->StripNodes("EXTENSION");
    4505             : 
    4506             :             oSRSTmp.exportToPrettyWkt(&pszWKGCS);
    4507             :             if( oSRS.IsSameGeogCS(&oSRSTmp) )
    4508             :             {
    4509             :                 oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
    4510             :                 oSRS.exportToWkt(&(pszTempProjection));
    4511             :                 SetProjection(pszTempProjection);
    4512             :                 CPLFree(pszTempProjection);
    4513             :             }
    4514             :         }
    4515             :     }
    4516             : #endif
    4517             : }
    4518             : 
    4519         147 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
    4520             :                                          bool bReadSRSOnly)
    4521             : {
    4522         147 :     SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
    4523             :                          nullptr, nullptr);
    4524         147 : }
    4525             : 
    4526         296 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
    4527             : {
    4528             :     // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
    4529             :     // and https://github.com/OSGeo/gdal/issues/7605
    4530             : 
    4531             :     // Check for a structure like:
    4532             :     /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
    4533             :         dimensions:
    4534             :             number_of_lines = 3248 ;
    4535             :             pixels_per_line = 3200 ;
    4536             :             [...]
    4537             :             pixel_control_points = 3200 ;
    4538             :         [...]
    4539             :         group: geophysical_data {
    4540             :           variables:
    4541             :             short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId
    4542             :                 [...]
    4543             :         }
    4544             :         group: navigation_data {
    4545             :           variables:
    4546             :             float longitude(number_of_lines, pixel_control_points) ;
    4547             :                 [...]
    4548             :             float latitude(number_of_lines, pixel_control_points) ;
    4549             :                 [...]
    4550             :         }
    4551             :     }
    4552             :     */
    4553             :     // Note that the longitude and latitude arrays are not indexed by the
    4554             :     // same dimensions. Handle only the case where
    4555             :     // pixel_control_points == pixels_per_line
    4556             :     // If there was a subsampling of the geolocation arrays, we'd need to
    4557             :     // add more logic.
    4558             : 
    4559         592 :     std::string osGroupName;
    4560         296 :     osGroupName.resize(NC_MAX_NAME);
    4561         296 :     NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
    4562         296 :     osGroupName.resize(strlen(osGroupName.data()));
    4563         296 :     if (osGroupName != "geophysical_data")
    4564         295 :         return false;
    4565             : 
    4566           1 :     int nVarDims = 0;
    4567           1 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4568           1 :     if (nVarDims != 2)
    4569           0 :         return false;
    4570             : 
    4571           1 :     int nNavigationDataGrpId = 0;
    4572           1 :     if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
    4573             :         NC_NOERR)
    4574           0 :         return false;
    4575             : 
    4576             :     std::array<int, 2> anVarDimIds;
    4577           1 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4578             : 
    4579           1 :     int nLongitudeId = 0;
    4580           1 :     int nLatitudeId = 0;
    4581           1 :     if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
    4582           2 :             NC_NOERR ||
    4583           1 :         nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
    4584             :             NC_NOERR)
    4585             :     {
    4586           0 :         return false;
    4587             :     }
    4588             : 
    4589           1 :     int nDimsLongitude = 0;
    4590           1 :     NCDF_ERR(
    4591             :         nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
    4592           1 :     int nDimsLatitude = 0;
    4593           1 :     NCDF_ERR(
    4594             :         nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
    4595           1 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4596             :     {
    4597           0 :         return false;
    4598             :     }
    4599             : 
    4600             :     std::array<int, 2> anDimLongitudeIds;
    4601           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
    4602             :                              anDimLongitudeIds.data()));
    4603             :     std::array<int, 2> anDimLatitudeIds;
    4604           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
    4605             :                              anDimLatitudeIds.data()));
    4606           1 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4607             :     {
    4608           0 :         return false;
    4609             :     }
    4610             : 
    4611             :     std::array<size_t, 2> anSizeVarDimIds;
    4612             :     std::array<size_t, 2> anSizeLongLatIds;
    4613           2 :     if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
    4614           1 :               NC_NOERR &&
    4615           1 :           nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
    4616           1 :               NC_NOERR &&
    4617           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
    4618           1 :               NC_NOERR &&
    4619           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
    4620             :               NC_NOERR &&
    4621           1 :           anSizeVarDimIds == anSizeLongLatIds))
    4622             :     {
    4623           0 :         return false;
    4624             :     }
    4625             : 
    4626           1 :     const char *pszGeolocXFullName = "/navigation_data/longitude";
    4627           1 :     const char *pszGeolocYFullName = "/navigation_data/latitude";
    4628             : 
    4629           1 :     if (bSwitchedXY)
    4630             :     {
    4631           0 :         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4632           0 :         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4633             :     }
    4634             : 
    4635           1 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4636             :              pszGeolocXFullName, pszGeolocYFullName);
    4637             : 
    4638           1 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4639             :                                     "GEOLOCATION");
    4640             : 
    4641           1 :     CPLString osTMP;
    4642           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4643             : 
    4644           1 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4645           1 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4646           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4647             : 
    4648           1 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4649           1 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4650             : 
    4651           1 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4652           1 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4653             : 
    4654           1 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4655           1 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4656             : 
    4657           1 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4658             :                                     "GEOLOCATION");
    4659           1 :     return true;
    4660             : }
    4661             : 
    4662         295 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
    4663             : {
    4664             :     // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
    4665             : 
    4666             :     // Check for a structure like:
    4667             :     /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
    4668             :         dimensions:
    4669             :             downtrack = 1280 ;
    4670             :             crosstrack = 1242 ;
    4671             :             bands = 285 ;
    4672             :             [...]
    4673             : 
    4674             :         variables:
    4675             :             float reflectance(downtrack, crosstrack, bands) ;
    4676             : 
    4677             :         group: location {
    4678             :           variables:
    4679             :                 double lon(downtrack, crosstrack) ;
    4680             :                         lon:_FillValue = -9999. ;
    4681             :                         lon:long_name = "Longitude (WGS-84)" ;
    4682             :                         lon:units = "degrees east" ;
    4683             :                 double lat(downtrack, crosstrack) ;
    4684             :                         lat:_FillValue = -9999. ;
    4685             :                         lat:long_name = "Latitude (WGS-84)" ;
    4686             :                         lat:units = "degrees north" ;
    4687             :           } // group location
    4688             : 
    4689             :     }
    4690             :     or
    4691             :     netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
    4692             :         dimensions:
    4693             :                 downtrack = 1664 ;
    4694             :                 crosstrack = 1242 ;
    4695             :                 [...]
    4696             :         variables:
    4697             :                 float group_1_band_depth(downtrack, crosstrack) ;
    4698             :                         group_1_band_depth:_FillValue = -9999.f ;
    4699             :                         group_1_band_depth:long_name = "Group 1 Band Depth" ;
    4700             :                         group_1_band_depth:units = "unitless" ;
    4701             :                 [...]
    4702             :         group: location {
    4703             :           variables:
    4704             :                 double lon(downtrack, crosstrack) ;
    4705             :                         lon:_FillValue = -9999. ;
    4706             :                         lon:long_name = "Longitude (WGS-84)" ;
    4707             :                         lon:units = "degrees east" ;
    4708             :                 double lat(downtrack, crosstrack) ;
    4709             :                         lat:_FillValue = -9999. ;
    4710             :                         lat:long_name = "Latitude (WGS-84)" ;
    4711             :                         lat:units = "degrees north" ;
    4712             :         }
    4713             :     */
    4714             : 
    4715         295 :     int nVarDims = 0;
    4716         295 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4717         295 :     if (nVarDims != 2 && nVarDims != 3)
    4718          14 :         return false;
    4719             : 
    4720         281 :     int nLocationGrpId = 0;
    4721         281 :     if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
    4722          60 :         return false;
    4723             : 
    4724             :     std::array<int, 3> anVarDimIds;
    4725         221 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4726         221 :     if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
    4727          21 :         return false;
    4728             : 
    4729         200 :     int nLongitudeId = 0;
    4730         200 :     int nLatitudeId = 0;
    4731         238 :     if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
    4732          38 :         nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
    4733             :     {
    4734         162 :         return false;
    4735             :     }
    4736             : 
    4737          38 :     int nDimsLongitude = 0;
    4738          38 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
    4739          38 :     int nDimsLatitude = 0;
    4740          38 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
    4741          38 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4742             :     {
    4743          34 :         return false;
    4744             :     }
    4745             : 
    4746             :     std::array<int, 2> anDimLongitudeIds;
    4747           4 :     NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
    4748             :                              anDimLongitudeIds.data()));
    4749             :     std::array<int, 2> anDimLatitudeIds;
    4750           4 :     NCDF_ERR(
    4751             :         nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
    4752           4 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4753             :     {
    4754           0 :         return false;
    4755             :     }
    4756             : 
    4757           8 :     if (anDimLongitudeIds[0] != anVarDimIds[0] ||
    4758           4 :         anDimLongitudeIds[1] != anVarDimIds[1])
    4759             :     {
    4760           0 :         return false;
    4761             :     }
    4762             : 
    4763           4 :     const char *pszGeolocXFullName = "/location/lon";
    4764           4 :     const char *pszGeolocYFullName = "/location/lat";
    4765             : 
    4766           4 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4767             :              pszGeolocXFullName, pszGeolocYFullName);
    4768             : 
    4769           4 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4770             :                                     "GEOLOCATION");
    4771             : 
    4772           4 :     CPLString osTMP;
    4773           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4774             : 
    4775           4 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4776           4 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4777           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4778             : 
    4779           4 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4780           4 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4781             : 
    4782           4 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4783           4 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4784             : 
    4785           4 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4786           4 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4787             : 
    4788           4 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4789             :                                     "GEOLOCATION");
    4790           4 :     return true;
    4791             : }
    4792             : 
    4793         368 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
    4794             :                                         const std::string &osGeolocWKT,
    4795             :                                         std::string &osGeolocXNameOut,
    4796             :                                         std::string &osGeolocYNameOut)
    4797             : {
    4798         368 :     bool bAddGeoloc = false;
    4799         368 :     char *pszCoordinates = nullptr;
    4800             : 
    4801             :     // If there is no explicit "coordinates" attribute, check if there are
    4802             :     // "lon" and "lat" 2D variables whose dimensions are the last
    4803             :     // 2 ones of the variable of interest.
    4804         368 :     if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
    4805             :         CE_None)
    4806             :     {
    4807         317 :         CPLFree(pszCoordinates);
    4808         317 :         pszCoordinates = nullptr;
    4809             : 
    4810         317 :         int nVarDims = 0;
    4811         317 :         NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4812         317 :         if (nVarDims >= 2)
    4813             :         {
    4814         634 :             std::vector<int> anVarDimIds(nVarDims);
    4815         317 :             NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4816             : 
    4817         317 :             int nLongitudeId = 0;
    4818         317 :             int nLatitudeId = 0;
    4819         389 :             if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
    4820          72 :                 nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
    4821             :             {
    4822          72 :                 int nDimsLongitude = 0;
    4823          72 :                 NCDF_ERR(
    4824             :                     nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
    4825          72 :                 int nDimsLatitude = 0;
    4826          72 :                 NCDF_ERR(
    4827             :                     nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
    4828          72 :                 if (nDimsLongitude == 2 && nDimsLatitude == 2)
    4829             :                 {
    4830          42 :                     std::vector<int> anDimLongitudeIds(2);
    4831          21 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
    4832             :                                              anDimLongitudeIds.data()));
    4833          42 :                     std::vector<int> anDimLatitudeIds(2);
    4834          21 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
    4835             :                                              anDimLatitudeIds.data()));
    4836          21 :                     if (anDimLongitudeIds == anDimLatitudeIds &&
    4837          42 :                         anVarDimIds[anVarDimIds.size() - 2] ==
    4838          63 :                             anDimLongitudeIds[0] &&
    4839          42 :                         anVarDimIds[anVarDimIds.size() - 1] ==
    4840          21 :                             anDimLongitudeIds[1])
    4841             :                     {
    4842          21 :                         pszCoordinates = CPLStrdup("lon lat");
    4843             :                     }
    4844             :                 }
    4845             :             }
    4846             :         }
    4847             :     }
    4848             : 
    4849         368 :     if (pszCoordinates)
    4850             :     {
    4851             :         // Get X and Y geolocation names from coordinates attribute.
    4852             :         const CPLStringList aosCoordinates(
    4853         144 :             NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    4854          72 :         if (aosCoordinates.size() >= 2)
    4855             :         {
    4856             :             char szGeolocXName[NC_MAX_NAME + 1];
    4857             :             char szGeolocYName[NC_MAX_NAME + 1];
    4858          69 :             szGeolocXName[0] = '\0';
    4859          69 :             szGeolocYName[0] = '\0';
    4860             : 
    4861             :             // Test that each variable is longitude/latitude.
    4862         220 :             for (int i = 0; i < aosCoordinates.size(); i++)
    4863             :             {
    4864         151 :                 if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
    4865             :                 {
    4866          58 :                     int nOtherGroupId = -1;
    4867          58 :                     int nOtherVarId = -1;
    4868             :                     // Check that the variable actually exists
    4869             :                     // Needed on Sentinel-3 products
    4870          58 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4871          58 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4872             :                     {
    4873          56 :                         snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
    4874             :                                  aosCoordinates[i]);
    4875             :                     }
    4876             :                 }
    4877          93 :                 else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
    4878             :                 {
    4879          58 :                     int nOtherGroupId = -1;
    4880          58 :                     int nOtherVarId = -1;
    4881             :                     // Check that the variable actually exists
    4882             :                     // Needed on Sentinel-3 products
    4883          58 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4884          58 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4885             :                     {
    4886          56 :                         snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
    4887             :                                  aosCoordinates[i]);
    4888             :                     }
    4889             :                 }
    4890             :             }
    4891             :             // Add GEOLOCATION metadata.
    4892          69 :             if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
    4893             :             {
    4894          56 :                 osGeolocXNameOut = szGeolocXName;
    4895          56 :                 osGeolocYNameOut = szGeolocYName;
    4896             : 
    4897          56 :                 char *pszGeolocXFullName = nullptr;
    4898          56 :                 char *pszGeolocYFullName = nullptr;
    4899          56 :                 if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
    4900         112 :                                            &pszGeolocXFullName) == CE_None &&
    4901          56 :                     NCDFResolveVarFullName(nGroupId, szGeolocYName,
    4902             :                                            &pszGeolocYFullName) == CE_None)
    4903             :                 {
    4904          56 :                     if (bSwitchedXY)
    4905             :                     {
    4906           2 :                         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4907           2 :                         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
    4908             :                                                         "GEOLOCATION");
    4909             :                     }
    4910             : 
    4911          56 :                     bAddGeoloc = true;
    4912          56 :                     CPLDebug("GDAL_netCDF",
    4913             :                              "using variables %s and %s for GEOLOCATION",
    4914             :                              pszGeolocXFullName, pszGeolocYFullName);
    4915             : 
    4916          56 :                     GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4917             :                                                     "GEOLOCATION");
    4918             : 
    4919         112 :                     CPLString osTMP;
    4920          56 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4921          56 :                                  pszGeolocXFullName);
    4922             : 
    4923          56 :                     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
    4924             :                                                     "GEOLOCATION");
    4925          56 :                     GDALPamDataset::SetMetadataItem("X_BAND", "1",
    4926             :                                                     "GEOLOCATION");
    4927          56 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4928          56 :                                  pszGeolocYFullName);
    4929             : 
    4930          56 :                     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
    4931             :                                                     "GEOLOCATION");
    4932          56 :                     GDALPamDataset::SetMetadataItem("Y_BAND", "1",
    4933             :                                                     "GEOLOCATION");
    4934             : 
    4935          56 :                     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
    4936             :                                                     "GEOLOCATION");
    4937          56 :                     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
    4938             :                                                     "GEOLOCATION");
    4939             : 
    4940          56 :                     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
    4941             :                                                     "GEOLOCATION");
    4942          56 :                     GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
    4943             :                                                     "GEOLOCATION");
    4944             : 
    4945          56 :                     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4946             :                                                     "PIXEL_CENTER",
    4947             :                                                     "GEOLOCATION");
    4948             :                 }
    4949             :                 else
    4950             :                 {
    4951           0 :                     CPLDebug("GDAL_netCDF",
    4952             :                              "cannot resolve location of "
    4953             :                              "lat/lon variables specified by the coordinates "
    4954             :                              "attribute [%s]",
    4955             :                              pszCoordinates);
    4956             :                 }
    4957          56 :                 CPLFree(pszGeolocXFullName);
    4958          56 :                 CPLFree(pszGeolocYFullName);
    4959             :             }
    4960             :             else
    4961             :             {
    4962          13 :                 CPLDebug("GDAL_netCDF",
    4963             :                          "coordinates attribute [%s] is unsupported",
    4964             :                          pszCoordinates);
    4965             :             }
    4966             :         }
    4967             :         else
    4968             :         {
    4969           3 :             CPLDebug("GDAL_netCDF",
    4970             :                      "coordinates attribute [%s] with %d element(s) is "
    4971             :                      "unsupported",
    4972             :                      pszCoordinates, aosCoordinates.size());
    4973             :         }
    4974             :     }
    4975             : 
    4976             :     else
    4977             :     {
    4978         296 :         bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
    4979             : 
    4980         296 :         if (!bAddGeoloc)
    4981         295 :             bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
    4982             :     }
    4983             : 
    4984         368 :     CPLFree(pszCoordinates);
    4985             : 
    4986         368 :     return bAddGeoloc;
    4987             : }
    4988             : 
    4989           8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
    4990             :                                        const char *szDimName)
    4991             : {
    4992             :     // Get values.
    4993           8 :     char *pszVarValues = nullptr;
    4994           8 :     CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
    4995           8 :     if (eErr != CE_None)
    4996           0 :         return eErr;
    4997             : 
    4998             :     // Write metadata.
    4999           8 :     char szTemp[NC_MAX_NAME + 1 + 32] = {};
    5000           8 :     snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
    5001           8 :     GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
    5002             : 
    5003           8 :     CPLFree(pszVarValues);
    5004             : 
    5005           8 :     return CE_None;
    5006             : }
    5007             : 
    5008           0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
    5009             :                                         int &nVarLen)
    5010             : {
    5011           0 :     nVarLen = 0;
    5012             : 
    5013             :     // Get Y_VALUES as tokens.
    5014             :     const CPLStringList aosValues(
    5015           0 :         NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")));
    5016           0 :     if (aosValues.empty())
    5017           0 :         return nullptr;
    5018             : 
    5019             :     // Initialize and fill array.
    5020           0 :     nVarLen = aosValues.size();
    5021             :     double *pdfVarValues =
    5022           0 :         static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
    5023             : 
    5024           0 :     for (int i = 0, j = 0; i < nVarLen; i++)
    5025             :     {
    5026           0 :         if (!bBottomUp)
    5027           0 :             j = nVarLen - 1 - i;
    5028             :         else
    5029           0 :             j = i;  // Invert latitude values.
    5030           0 :         char *pszTemp = nullptr;
    5031           0 :         pdfVarValues[j] = CPLStrtod(aosValues[i], &pszTemp);
    5032             :     }
    5033             : 
    5034           0 :     return pdfVarValues;
    5035             : }
    5036             : 
    5037             : /************************************************************************/
    5038             : /*                       SetSpatialRefNoUpdate()                        */
    5039             : /************************************************************************/
    5040             : 
    5041         281 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
    5042             : {
    5043         281 :     m_oSRS.Clear();
    5044         281 :     if (poSRS)
    5045         274 :         m_oSRS = *poSRS;
    5046         281 :     m_bHasProjection = true;
    5047         281 : }
    5048             : 
    5049             : /************************************************************************/
    5050             : /*                           SetSpatialRef()                            */
    5051             : /************************************************************************/
    5052             : 
    5053          82 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    5054             : {
    5055         164 :     CPLMutexHolderD(&hNCMutex);
    5056             : 
    5057          82 :     if (GetAccess() != GA_Update || m_bHasProjection)
    5058             :     {
    5059           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5060             :                  "netCDFDataset::_SetProjection() should only be called once "
    5061             :                  "in update mode!");
    5062           0 :         return CE_Failure;
    5063             :     }
    5064             : 
    5065          82 :     if (m_bHasGeoTransform)
    5066             :     {
    5067          32 :         SetSpatialRefNoUpdate(poSRS);
    5068             : 
    5069             :         // For NC4/NC4C, writing both projection variables and data,
    5070             :         // followed by redefining nodata value, cancels the projection
    5071             :         // info from the Band variable, so for now only write the
    5072             :         // variable definitions, and write data at the end.
    5073             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5074          32 :         return AddProjectionVars(true, nullptr, nullptr);
    5075             :     }
    5076             : 
    5077          50 :     SetSpatialRefNoUpdate(poSRS);
    5078             : 
    5079          50 :     return CE_None;
    5080             : }
    5081             : 
    5082             : /************************************************************************/
    5083             : /*                      SetGeoTransformNoUpdate()                       */
    5084             : /************************************************************************/
    5085             : 
    5086         295 : void netCDFDataset::SetGeoTransformNoUpdate(const GDALGeoTransform &gt)
    5087             : {
    5088         295 :     m_gt = gt;
    5089         295 :     m_bHasGeoTransform = true;
    5090         295 : }
    5091             : 
    5092             : /************************************************************************/
    5093             : /*                          SetGeoTransform()                           */
    5094             : /************************************************************************/
    5095             : 
    5096          83 : CPLErr netCDFDataset::SetGeoTransform(const GDALGeoTransform &gt)
    5097             : {
    5098         166 :     CPLMutexHolderD(&hNCMutex);
    5099             : 
    5100          83 :     if (GetAccess() != GA_Update || m_bHasGeoTransform)
    5101             :     {
    5102           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5103             :                  "netCDFDataset::SetGeoTransform() should only be called once "
    5104             :                  "in update mode!");
    5105           0 :         return CE_Failure;
    5106             :     }
    5107             : 
    5108          83 :     CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", gt.xorig,
    5109          83 :              gt.xscale, gt.xrot, gt.yorig, gt.yrot, gt.yscale);
    5110             : 
    5111          83 :     SetGeoTransformNoUpdate(gt);
    5112             : 
    5113          83 :     if (m_bHasProjection)
    5114             :     {
    5115             : 
    5116             :         // For NC4/NC4C, writing both projection variables and data,
    5117             :         // followed by redefining nodata value, cancels the projection
    5118             :         // info from the Band variable, so for now only write the
    5119             :         // variable definitions, and write data at the end.
    5120             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5121           3 :         return AddProjectionVars(true, nullptr, nullptr);
    5122             :     }
    5123             : 
    5124          80 :     return CE_None;
    5125             : }
    5126             : 
    5127             : /************************************************************************/
    5128             : /*                        NCDFWriteSRSVariable()                        */
    5129             : /************************************************************************/
    5130             : 
    5131         136 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
    5132             :                          char **ppszCFProjection, bool bWriteGDALTags,
    5133             :                          const std::string &srsVarName)
    5134             : {
    5135         136 :     char *pszCFProjection = nullptr;
    5136         136 :     char **papszKeyValues = nullptr;
    5137         136 :     poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
    5138             : 
    5139         136 :     if (bWriteGDALTags)
    5140             :     {
    5141         135 :         const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
    5142         135 :         if (pszWKT)
    5143             :         {
    5144             :             // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
    5145         135 :             papszKeyValues =
    5146         135 :                 CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
    5147             :         }
    5148             :     }
    5149             : 
    5150         136 :     const int nValues = CSLCount(papszKeyValues);
    5151             : 
    5152             :     int NCDFVarID;
    5153         272 :     std::string varNameRadix(pszCFProjection);
    5154         136 :     int nCounter = 2;
    5155             :     while (true)
    5156             :     {
    5157         138 :         NCDFVarID = -1;
    5158         138 :         nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
    5159         138 :         if (NCDFVarID < 0)
    5160         133 :             break;
    5161             : 
    5162           5 :         int nbAttr = 0;
    5163           5 :         NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
    5164           5 :         bool bSame = nbAttr == nValues;
    5165          41 :         for (int i = 0; bSame && (i < nbAttr); i++)
    5166             :         {
    5167             :             char szAttrName[NC_MAX_NAME + 1];
    5168          38 :             szAttrName[0] = 0;
    5169          38 :             NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
    5170             : 
    5171             :             const char *pszValue =
    5172          38 :                 CSLFetchNameValue(papszKeyValues, szAttrName);
    5173          38 :             if (!pszValue)
    5174             :             {
    5175           0 :                 bSame = false;
    5176           2 :                 break;
    5177             :             }
    5178             : 
    5179          38 :             nc_type atttype = NC_NAT;
    5180          38 :             size_t attlen = 0;
    5181          38 :             NCDF_ERR(
    5182             :                 nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
    5183          38 :             if (atttype != NC_CHAR && atttype != NC_DOUBLE)
    5184             :             {
    5185           0 :                 bSame = false;
    5186           0 :                 break;
    5187             :             }
    5188          38 :             if (atttype == NC_CHAR)
    5189             :             {
    5190          15 :                 if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
    5191             :                 {
    5192           0 :                     bSame = false;
    5193           0 :                     break;
    5194             :                 }
    5195          15 :                 std::string val;
    5196          15 :                 val.resize(attlen);
    5197          15 :                 nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
    5198          15 :                 if (val != pszValue)
    5199             :                 {
    5200           0 :                     bSame = false;
    5201           0 :                     break;
    5202             :                 }
    5203             :             }
    5204             :             else
    5205             :             {
    5206             :                 const CPLStringList aosTokens(
    5207          23 :                     CSLTokenizeString2(pszValue, ",", 0));
    5208          23 :                 if (static_cast<size_t>(aosTokens.size()) != attlen)
    5209             :                 {
    5210           0 :                     bSame = false;
    5211           0 :                     break;
    5212             :                 }
    5213             :                 double vals[2];
    5214          23 :                 nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
    5215          44 :                 if (vals[0] != CPLAtof(aosTokens[0]) ||
    5216          21 :                     (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
    5217             :                 {
    5218           2 :                     bSame = false;
    5219           2 :                     break;
    5220             :                 }
    5221             :             }
    5222             :         }
    5223           5 :         if (bSame)
    5224             :         {
    5225           3 :             *ppszCFProjection = pszCFProjection;
    5226           3 :             CSLDestroy(papszKeyValues);
    5227           3 :             return NCDFVarID;
    5228             :         }
    5229           2 :         CPLFree(pszCFProjection);
    5230           2 :         pszCFProjection =
    5231           2 :             CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
    5232           2 :         nCounter++;
    5233           2 :     }
    5234             : 
    5235         133 :     *ppszCFProjection = pszCFProjection;
    5236             : 
    5237             :     const char *pszVarName;
    5238             : 
    5239         133 :     if (srsVarName != "")
    5240             :     {
    5241          38 :         pszVarName = srsVarName.c_str();
    5242             :     }
    5243             :     else
    5244             :     {
    5245          95 :         pszVarName = pszCFProjection;
    5246             :     }
    5247             : 
    5248         133 :     int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
    5249         133 :     NCDF_ERR(status);
    5250        1303 :     for (int i = 0; i < nValues; ++i)
    5251             :     {
    5252        1170 :         char *pszKey = nullptr;
    5253        1170 :         const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
    5254        1170 :         if (pszKey && pszValue)
    5255             :         {
    5256        2340 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
    5257        1170 :             double adfValues[2] = {0, 0};
    5258        1170 :             const int nDoubleCount = std::min(2, aosTokens.size());
    5259        1170 :             if (!(aosTokens.size() == 2 &&
    5260        2339 :                   CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
    5261        1169 :                 CPLGetValueType(pszValue) == CPL_VALUE_STRING)
    5262             :             {
    5263         531 :                 status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
    5264             :                                          strlen(pszValue), pszValue);
    5265             :             }
    5266             :             else
    5267             :             {
    5268        1279 :                 for (int j = 0; j < nDoubleCount; ++j)
    5269         640 :                     adfValues[j] = CPLAtof(aosTokens[j]);
    5270         639 :                 status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
    5271             :                                            nDoubleCount, adfValues);
    5272             :             }
    5273        1170 :             NCDF_ERR(status);
    5274             :         }
    5275        1170 :         CPLFree(pszKey);
    5276             :     }
    5277             : 
    5278         133 :     CSLDestroy(papszKeyValues);
    5279         133 :     return NCDFVarID;
    5280             : }
    5281             : 
    5282             : /************************************************************************/
    5283             : /*                   NCDFWriteLonLatVarsAttributes()                    */
    5284             : /************************************************************************/
    5285             : 
    5286         103 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
    5287             :                                    int nVarLatID)
    5288             : {
    5289             : 
    5290             :     try
    5291             :     {
    5292         103 :         vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
    5293         103 :         vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
    5294         103 :         vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
    5295         103 :         vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
    5296         103 :         vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
    5297         103 :         vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
    5298             :     }
    5299           0 :     catch (nccfdriver::SG_Exception &e)
    5300             :     {
    5301           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5302             :     }
    5303         103 : }
    5304             : 
    5305             : /************************************************************************/
    5306             : /*                  NCDFWriteRLonRLatVarsAttributes()                   */
    5307             : /************************************************************************/
    5308             : 
    5309           0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
    5310             :                                      int nVarRLonID, int nVarRLatID)
    5311             : {
    5312             :     try
    5313             :     {
    5314           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
    5315           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
    5316             :                               "latitude in rotated pole grid");
    5317           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
    5318           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
    5319             : 
    5320           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
    5321           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
    5322             :                               "longitude in rotated pole grid");
    5323           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
    5324           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
    5325             :     }
    5326           0 :     catch (nccfdriver::SG_Exception &e)
    5327             :     {
    5328           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5329             :     }
    5330           0 : }
    5331             : 
    5332             : /************************************************************************/
    5333             : /*                       NCDFGetProjectedCFUnit()                       */
    5334             : /************************************************************************/
    5335             : 
    5336          44 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
    5337             : {
    5338          44 :     char *pszUnitsToWrite = nullptr;
    5339          44 :     poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
    5340          44 :     std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
    5341          44 :     CPLFree(pszUnitsToWrite);
    5342          88 :     return osRet;
    5343             : }
    5344             : 
    5345             : /************************************************************************/
    5346             : /*                     NCDFWriteXYVarsAttributes()                      */
    5347             : /************************************************************************/
    5348             : 
    5349          31 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
    5350             :                                int nVarYID, const OGRSpatialReference *poSRS)
    5351             : {
    5352          62 :     const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
    5353             : 
    5354             :     try
    5355             :     {
    5356          31 :         vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
    5357          31 :         vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
    5358          31 :         if (!osUnitsToWrite.empty())
    5359          31 :             vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
    5360          31 :         vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
    5361          31 :         vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
    5362          31 :         if (!osUnitsToWrite.empty())
    5363          31 :             vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
    5364             :     }
    5365           0 :     catch (nccfdriver::SG_Exception &e)
    5366             :     {
    5367           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5368             :     }
    5369          31 : }
    5370             : 
    5371             : /************************************************************************/
    5372             : /*                         AddProjectionVars()                          */
    5373             : /************************************************************************/
    5374             : 
    5375         176 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
    5376             :                                         GDALProgressFunc pfnProgress,
    5377             :                                         void *pProgressData)
    5378             : {
    5379         176 :     if (nCFVersion >= 1.8)
    5380           0 :         return CE_None;  // do nothing
    5381             : 
    5382         176 :     bool bWriteGridMapping = false;
    5383         176 :     bool bWriteLonLat = false;
    5384         176 :     bool bHasGeoloc = false;
    5385         176 :     bool bWriteGDALTags = false;
    5386         176 :     bool bWriteGeoTransform = false;
    5387             : 
    5388             :     // For GEOLOCATION information.
    5389         176 :     GDALDatasetUniquePtr poDS_X;
    5390         176 :     GDALDatasetUniquePtr poDS_Y;
    5391         176 :     GDALRasterBand *poBand_X = nullptr;
    5392         176 :     GDALRasterBand *poBand_Y = nullptr;
    5393             : 
    5394         352 :     OGRSpatialReference oSRS(m_oSRS);
    5395         176 :     if (!m_oSRS.IsEmpty())
    5396             :     {
    5397         150 :         if (oSRS.IsProjected())
    5398          62 :             bIsProjected = true;
    5399          88 :         else if (oSRS.IsGeographic())
    5400          88 :             bIsGeographic = true;
    5401             :     }
    5402             : 
    5403         176 :     if (bDefsOnly)
    5404             :     {
    5405          88 :         char *pszProjection = nullptr;
    5406          88 :         m_oSRS.exportToWkt(&pszProjection);
    5407          88 :         CPLDebug("GDAL_netCDF",
    5408             :                  "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
    5409          88 :                  pszProjection ? pszProjection : "(null)",
    5410          88 :                  static_cast<int>(bIsProjected),
    5411          88 :                  static_cast<int>(bIsGeographic));
    5412          88 :         CPLFree(pszProjection);
    5413             : 
    5414          88 :         if (!m_bHasGeoTransform)
    5415           5 :             CPLDebug("GDAL_netCDF",
    5416             :                      "netCDFDataset::AddProjectionVars() called, "
    5417             :                      "but GeoTransform has not yet been defined!");
    5418             : 
    5419          88 :         if (!m_bHasProjection)
    5420           6 :             CPLDebug("GDAL_netCDF",
    5421             :                      "netCDFDataset::AddProjectionVars() called, "
    5422             :                      "but Projection has not yet been defined!");
    5423             :     }
    5424             : 
    5425             :     // Check GEOLOCATION information.
    5426             :     CSLConstList papszGeolocationInfo =
    5427         176 :         netCDFDataset::GetMetadata("GEOLOCATION");
    5428         176 :     if (papszGeolocationInfo != nullptr)
    5429             :     {
    5430             :         // Look for geolocation datasets.
    5431             :         const char *pszDSName =
    5432          10 :             CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
    5433          10 :         if (pszDSName != nullptr)
    5434          10 :             poDS_X.reset(GDALDataset::Open(
    5435             :                 pszDSName,
    5436             :                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
    5437          10 :         pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
    5438          10 :         if (pszDSName != nullptr)
    5439          10 :             poDS_Y.reset(GDALDataset::Open(
    5440             :                 pszDSName,
    5441             :                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_SHARED));
    5442             : 
    5443          10 :         if (poDS_X != nullptr && poDS_Y != nullptr)
    5444             :         {
    5445          10 :             int nBand = std::max(1, atoi(CSLFetchNameValueDef(
    5446          10 :                                         papszGeolocationInfo, "X_BAND", "0")));
    5447          10 :             poBand_X = poDS_X->GetRasterBand(nBand);
    5448          10 :             nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
    5449          10 :                                                           "Y_BAND", "0")));
    5450          10 :             poBand_Y = poDS_Y->GetRasterBand(nBand);
    5451             : 
    5452             :             // If geoloc bands are found, do basic validation based on their
    5453             :             // dimensions.
    5454          10 :             if (poBand_X != nullptr && poBand_Y != nullptr)
    5455             :             {
    5456          10 :                 const int nXSize_XBand = poBand_X->GetXSize();
    5457          10 :                 const int nYSize_XBand = poBand_X->GetYSize();
    5458          10 :                 const int nXSize_YBand = poBand_Y->GetXSize();
    5459          10 :                 const int nYSize_YBand = poBand_Y->GetYSize();
    5460             : 
    5461             :                 // TODO 1D geolocation arrays not implemented.
    5462          10 :                 if (nYSize_XBand == 1 && nYSize_YBand == 1)
    5463             :                 {
    5464           0 :                     bHasGeoloc = false;
    5465           0 :                     CPLDebug("GDAL_netCDF",
    5466             :                              "1D GEOLOCATION arrays not supported yet");
    5467             :                 }
    5468             :                 // 2D bands must have same sizes as the raster bands.
    5469          10 :                 else if (nXSize_XBand != nRasterXSize ||
    5470          10 :                          nYSize_XBand != nRasterYSize ||
    5471          10 :                          nXSize_YBand != nRasterXSize ||
    5472          10 :                          nYSize_YBand != nRasterYSize)
    5473             :                 {
    5474           0 :                     bHasGeoloc = false;
    5475           0 :                     CPLDebug("GDAL_netCDF",
    5476             :                              "GEOLOCATION array sizes (%dx%d %dx%d) differ "
    5477             :                              "from raster (%dx%d), not supported",
    5478             :                              nXSize_XBand, nYSize_XBand, nXSize_YBand,
    5479             :                              nYSize_YBand, nRasterXSize, nRasterYSize);
    5480             :                 }
    5481             :                 else
    5482             :                 {
    5483          10 :                     bHasGeoloc = true;
    5484          10 :                     CPLDebug("GDAL_netCDF",
    5485             :                              "dataset has GEOLOCATION information, will try to "
    5486             :                              "write it");
    5487             :                 }
    5488             :             }
    5489             :         }
    5490             :     }
    5491             : 
    5492             :     // Process projection options.
    5493         176 :     if (bIsProjected)
    5494             :     {
    5495             :         bool bIsCfProjection =
    5496          62 :             oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
    5497          62 :         bWriteGridMapping = true;
    5498          62 :         bWriteGDALTags = CPL_TO_BOOL(
    5499          62 :             CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
    5500             :         // Force WRITE_GDAL_TAGS if is not a CF projection.
    5501          62 :         if (!bWriteGDALTags && !bIsCfProjection)
    5502           0 :             bWriteGDALTags = true;
    5503          62 :         if (bWriteGDALTags)
    5504          62 :             bWriteGeoTransform = true;
    5505             : 
    5506             :         // Write lon/lat: default is NO, except if has geolocation.
    5507             :         // With IF_NEEDED: write if has geoloc or is not CF projection.
    5508             :         const char *pszValue =
    5509          62 :             CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
    5510          62 :         if (pszValue)
    5511             :         {
    5512           6 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5513             :             {
    5514           0 :                 bWriteLonLat = bHasGeoloc || !bIsCfProjection;
    5515             :             }
    5516             :             else
    5517             :             {
    5518           6 :                 bWriteLonLat = CPLTestBool(pszValue);
    5519             :             }
    5520             :         }
    5521             :         else
    5522             :         {
    5523          56 :             bWriteLonLat = bHasGeoloc;
    5524             :         }
    5525             : 
    5526             :         // Save value of pszCFCoordinates for later.
    5527          62 :         if (bWriteLonLat)
    5528             :         {
    5529           8 :             pszCFCoordinates = NCDF_LONLAT;
    5530             :         }
    5531             :     }
    5532             :     else
    5533             :     {
    5534             :         // Files without a Datum will not have a grid_mapping variable and
    5535             :         // geographic information.
    5536         114 :         bWriteGridMapping = bIsGeographic;
    5537             : 
    5538         114 :         if (bHasGeoloc)
    5539             :         {
    5540           8 :             bWriteLonLat = true;
    5541             :         }
    5542             :         else
    5543             :         {
    5544         106 :             bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
    5545         106 :                 papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
    5546         106 :             if (bWriteGDALTags)
    5547          88 :                 bWriteGeoTransform = true;
    5548             : 
    5549         106 :             const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
    5550             :                                                         "WRITE_LONLAT", "YES");
    5551         106 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5552           0 :                 bWriteLonLat = true;
    5553             :             else
    5554         106 :                 bWriteLonLat = CPLTestBool(pszValue);
    5555             :             //  Don't write lon/lat if no source geotransform.
    5556         106 :             if (!m_bHasGeoTransform)
    5557           0 :                 bWriteLonLat = false;
    5558             :             // If we don't write lon/lat, set dimnames to X/Y and write gdal
    5559             :             // tags.
    5560         106 :             if (!bWriteLonLat)
    5561             :             {
    5562           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5563             :                          "creating geographic file without lon/lat values!");
    5564           0 :                 if (m_bHasGeoTransform)
    5565             :                 {
    5566           0 :                     bWriteGDALTags = true;  // Not desirable if no geotransform.
    5567           0 :                     bWriteGeoTransform = true;
    5568             :                 }
    5569             :             }
    5570             :         }
    5571             :     }
    5572             : 
    5573             :     // Make sure we write grid_mapping if we need to write GDAL tags.
    5574         176 :     if (bWriteGDALTags)
    5575         150 :         bWriteGridMapping = true;
    5576             : 
    5577             :     // bottom-up value: new driver is bottom-up by default.
    5578             :     // Override with WRITE_BOTTOMUP.
    5579         176 :     bBottomUp = CPL_TO_BOOL(
    5580         176 :         CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
    5581             : 
    5582         176 :     if (bDefsOnly)
    5583             :     {
    5584          88 :         CPLDebug(
    5585             :             "GDAL_netCDF",
    5586             :             "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
    5587             :             "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
    5588          88 :             static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
    5589             :             static_cast<int>(bWriteGridMapping),
    5590             :             static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
    5591          88 :             static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
    5592             :     }
    5593             : 
    5594             :     // Exit if nothing to do.
    5595         176 :     if (!bIsProjected && !bWriteLonLat)
    5596           0 :         return CE_None;
    5597             : 
    5598             :     // Define dimension names.
    5599             : 
    5600         176 :     constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
    5601             : 
    5602         176 :     if (bDefsOnly)
    5603             :     {
    5604          88 :         int nVarLonID = -1;
    5605          88 :         int nVarLatID = -1;
    5606          88 :         int nVarXID = -1;
    5607          88 :         int nVarYID = -1;
    5608             : 
    5609          88 :         m_bAddedProjectionVarsDefs = true;
    5610             : 
    5611             :         // Make sure we are in define mode.
    5612          88 :         SetDefineMode(true);
    5613             : 
    5614             :         // Write projection attributes.
    5615          88 :         if (bWriteGridMapping)
    5616             :         {
    5617          75 :             const int NCDFVarID = NCDFWriteSRSVariable(
    5618             :                 cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
    5619          75 :             if (NCDFVarID < 0)
    5620           0 :                 return CE_Failure;
    5621             : 
    5622             :             // Optional GDAL custom projection tags.
    5623          75 :             if (bWriteGDALTags && bWriteGeoTransform && m_bHasGeoTransform)
    5624             :             {
    5625          74 :                 GDALGeoTransform gt(m_gt);
    5626          74 :                 if (!bBottomUp)
    5627             :                 {
    5628             :                     // Change origin from top to bottom and sign of coefficients
    5629             :                     // indexed by row
    5630           2 :                     gt.yorig += nRasterYSize * gt.yscale;
    5631           2 :                     gt.xorig += nRasterYSize * gt.xrot;
    5632           2 :                     gt.xrot = -gt.xrot;
    5633           2 :                     gt.yscale = -gt.yscale;
    5634             :                 }
    5635         148 :                 std::string osGeoTransform = gt.ToString(" ");
    5636          74 :                 CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
    5637             :                          osGeoTransform.c_str());
    5638             : 
    5639          74 :                 const int status = nc_put_att_text(
    5640             :                     cdfid, NCDFVarID, NCDF_GEOTRANSFORM, osGeoTransform.size(),
    5641             :                     osGeoTransform.c_str());
    5642          74 :                 NCDF_ERR(status);
    5643             :             }
    5644             : 
    5645             :             // Write projection variable to band variable.
    5646             :             // Need to call later if there are no bands.
    5647          75 :             AddGridMappingRef();
    5648             :         }  // end if( bWriteGridMapping )
    5649             : 
    5650             :         // Write CF Projection vars.
    5651             : 
    5652          88 :         const bool bIsRotatedPole =
    5653         163 :             pszCFProjection != nullptr &&
    5654          75 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5655             : 
    5656          88 :         if (m_bHasGeoTransform && !m_gt.IsAxisAligned())
    5657             :         {
    5658             :             // Do not write X/Y coordinate arrays
    5659             :         }
    5660             : 
    5661          84 :         else if (bIsRotatedPole)
    5662             :         {
    5663             :             // Rename dims to rlat/rlon.
    5664             :             papszDimName
    5665           0 :                 .Clear();  // If we add other dims one day, this has to change
    5666           0 :             papszDimName.AddString(NCDF_DIMNAME_RLAT);
    5667           0 :             papszDimName.AddString(NCDF_DIMNAME_RLON);
    5668             : 
    5669           0 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
    5670           0 :             NCDF_ERR(status);
    5671           0 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
    5672           0 :             NCDF_ERR(status);
    5673             :         }
    5674             :         // Rename dimensions if lon/lat.
    5675          84 :         else if (!bIsProjected && !bHasGeoloc)
    5676             :         {
    5677             :             // Rename dims to lat/lon.
    5678             :             papszDimName
    5679          53 :                 .Clear();  // If we add other dims one day, this has to change
    5680          53 :             papszDimName.AddString(NCDF_DIMNAME_LAT);
    5681          53 :             papszDimName.AddString(NCDF_DIMNAME_LON);
    5682             : 
    5683          53 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
    5684          53 :             NCDF_ERR(status);
    5685          53 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
    5686          53 :             NCDF_ERR(status);
    5687             :         }
    5688             : 
    5689             :         // Write X/Y attributes.
    5690             :         else /* if( bIsProjected || bHasGeoloc ) */
    5691             :         {
    5692             :             // X
    5693             :             int anXDims[1];
    5694          31 :             anXDims[0] = nXDimID;
    5695          31 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5696             :                      CF_PROJ_X_VAR_NAME, NC_DOUBLE);
    5697          31 :             int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
    5698             :                                     anXDims, &nVarXID);
    5699          31 :             NCDF_ERR(status);
    5700             : 
    5701             :             // Y
    5702             :             int anYDims[1];
    5703          31 :             anYDims[0] = nYDimID;
    5704          31 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5705             :                      CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
    5706          31 :             status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
    5707             :                                 anYDims, &nVarYID);
    5708          31 :             NCDF_ERR(status);
    5709             : 
    5710          31 :             if (bIsProjected)
    5711             :             {
    5712          27 :                 NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
    5713             :             }
    5714             :             else
    5715             :             {
    5716           4 :                 CPLAssert(bHasGeoloc);
    5717             :                 try
    5718             :                 {
    5719           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
    5720           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
    5721             :                                           "x-coordinate in Cartesian system");
    5722           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
    5723           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
    5724           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
    5725             :                                           "y-coordinate in Cartesian system");
    5726           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
    5727             : 
    5728           4 :                     pszCFCoordinates = NCDF_LONLAT;
    5729             :                 }
    5730           0 :                 catch (nccfdriver::SG_Exception &e)
    5731             :                 {
    5732           0 :                     CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5733           0 :                     return CE_Failure;
    5734             :                 }
    5735             :             }
    5736             :         }
    5737             : 
    5738             :         // Write lat/lon attributes if needed.
    5739          88 :         if (bWriteLonLat)
    5740             :         {
    5741          61 :             int anLatDims[2] = {0, 0};
    5742          61 :             int anLonDims[2] = {0, 0};
    5743          61 :             int nLatDims = -1;
    5744          61 :             int nLonDims = -1;
    5745             : 
    5746             :             // Get information.
    5747          61 :             if (bHasGeoloc)
    5748             :             {
    5749             :                 // Geoloc
    5750           5 :                 nLatDims = 2;
    5751           5 :                 anLatDims[0] = nYDimID;
    5752           5 :                 anLatDims[1] = nXDimID;
    5753           5 :                 nLonDims = 2;
    5754           5 :                 anLonDims[0] = nYDimID;
    5755           5 :                 anLonDims[1] = nXDimID;
    5756             :             }
    5757          56 :             else if (bIsProjected)
    5758             :             {
    5759             :                 // Projected
    5760           3 :                 nLatDims = 2;
    5761           3 :                 anLatDims[0] = nYDimID;
    5762           3 :                 anLatDims[1] = nXDimID;
    5763           3 :                 nLonDims = 2;
    5764           3 :                 anLonDims[0] = nYDimID;
    5765           3 :                 anLonDims[1] = nXDimID;
    5766             :             }
    5767             :             else
    5768             :             {
    5769             :                 // Geographic
    5770          53 :                 nLatDims = 1;
    5771          53 :                 anLatDims[0] = nYDimID;
    5772          53 :                 nLonDims = 1;
    5773          53 :                 anLonDims[0] = nXDimID;
    5774             :             }
    5775             : 
    5776          61 :             nc_type eLonLatType = NC_NAT;
    5777          61 :             if (bIsProjected)
    5778             :             {
    5779           4 :                 eLonLatType = NC_FLOAT;
    5780           8 :                 const char *pszValue = CSLFetchNameValueDef(
    5781           4 :                     papszCreationOptions, "TYPE_LONLAT", "FLOAT");
    5782           4 :                 if (EQUAL(pszValue, "DOUBLE"))
    5783           0 :                     eLonLatType = NC_DOUBLE;
    5784             :             }
    5785             :             else
    5786             :             {
    5787          57 :                 eLonLatType = NC_DOUBLE;
    5788         114 :                 const char *pszValue = CSLFetchNameValueDef(
    5789          57 :                     papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
    5790          57 :                 if (EQUAL(pszValue, "FLOAT"))
    5791           0 :                     eLonLatType = NC_FLOAT;
    5792             :             }
    5793             : 
    5794             :             // Def vars and attributes.
    5795             :             {
    5796          61 :                 const char *pszVarName =
    5797          61 :                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
    5798          61 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5799             :                                         nLatDims, anLatDims, &nVarLatID);
    5800          61 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5801             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
    5802          61 :                 NCDF_ERR(status);
    5803          61 :                 DefVarDeflate(nVarLatID, false);  // Don't set chunking.
    5804             :             }
    5805             : 
    5806             :             {
    5807          61 :                 const char *pszVarName =
    5808          61 :                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
    5809          61 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5810             :                                         nLonDims, anLonDims, &nVarLonID);
    5811          61 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5812             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
    5813          61 :                 NCDF_ERR(status);
    5814          61 :                 DefVarDeflate(nVarLonID, false);  // Don't set chunking.
    5815             :             }
    5816             : 
    5817          61 :             if (bIsRotatedPole)
    5818           0 :                 NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
    5819             :                                                 nVarLatID);
    5820             :             else
    5821          61 :                 NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
    5822             :         }
    5823             :     }
    5824             : 
    5825         176 :     if (!bDefsOnly)
    5826             :     {
    5827          88 :         m_bAddedProjectionVarsData = true;
    5828             : 
    5829          88 :         int nVarXID = -1;
    5830          88 :         int nVarYID = -1;
    5831             : 
    5832          88 :         nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
    5833          88 :         nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
    5834             : 
    5835          88 :         int nVarLonID = -1;
    5836          88 :         int nVarLatID = -1;
    5837             : 
    5838          88 :         const bool bIsRotatedPole =
    5839         163 :             pszCFProjection != nullptr &&
    5840          75 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5841          88 :         nc_inq_varid(cdfid,
    5842             :                      bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
    5843             :                      &nVarLonID);
    5844          88 :         nc_inq_varid(cdfid,
    5845             :                      bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
    5846             :                      &nVarLatID);
    5847             : 
    5848             :         // Get projection values.
    5849             : 
    5850          88 :         if (bIsProjected)
    5851             :         {
    5852           0 :             std::unique_ptr<OGRSpatialReference> poLatLonSRS;
    5853           0 :             std::unique_ptr<OGRCoordinateTransformation> poTransform;
    5854             : 
    5855             :             size_t startX[1];
    5856             :             size_t countX[1];
    5857             :             size_t startY[1];
    5858             :             size_t countY[1];
    5859             : 
    5860          31 :             CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
    5861             : 
    5862             :             std::unique_ptr<double, decltype(&VSIFree)> adXValKeeper(
    5863             :                 static_cast<double *>(
    5864          62 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5865          31 :                 VSIFree);
    5866             :             std::unique_ptr<double, decltype(&VSIFree)> adYValKeeper(
    5867             :                 static_cast<double *>(
    5868          62 :                     VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))),
    5869          31 :                 VSIFree);
    5870          31 :             double *padXVal = adXValKeeper.get();
    5871          31 :             double *padYVal = adYValKeeper.get();
    5872          31 :             if (!padXVal || !padYVal)
    5873             :             {
    5874           0 :                 return CE_Failure;
    5875             :             }
    5876             : 
    5877             :             // Make sure we are in data mode.
    5878          31 :             SetDefineMode(false);
    5879             : 
    5880          31 :             int status = NC_NOERR;
    5881             : 
    5882          31 :             if (m_gt.IsAxisAligned())
    5883             :             {
    5884             :                 // Get Y values.
    5885          27 :                 const double dfY0 =
    5886          27 :                     (!bBottomUp) ? m_gt.yorig :
    5887             :                                  // Invert latitude values.
    5888          27 :                         m_gt.yorig + (m_gt.yscale * nRasterYSize);
    5889          27 :                 const double dfDY = m_gt.yscale;
    5890             : 
    5891        1478 :                 for (int j = 0; j < nRasterYSize; j++)
    5892             :                 {
    5893             :                     // The data point is centered inside the pixel.
    5894        1451 :                     if (!bBottomUp)
    5895           0 :                         padYVal[j] = dfY0 + (j + 0.5) * dfDY;
    5896             :                     else  // Invert latitude values.
    5897        1451 :                         padYVal[j] = dfY0 - (j + 0.5) * dfDY;
    5898             :                 }
    5899          27 :                 startX[0] = 0;
    5900          27 :                 countX[0] = nRasterXSize;
    5901             : 
    5902             :                 // Get X values.
    5903          27 :                 const double dfX0 = m_gt.xorig;
    5904          27 :                 const double dfDX = m_gt.xscale;
    5905             : 
    5906        1519 :                 for (int i = 0; i < nRasterXSize; i++)
    5907             :                 {
    5908             :                     // The data point is centered inside the pixel.
    5909        1492 :                     padXVal[i] = dfX0 + (i + 0.5) * dfDX;
    5910             :                 }
    5911          27 :                 startY[0] = 0;
    5912          27 :                 countY[0] = nRasterYSize;
    5913             : 
    5914             :                 // Write X/Y values.
    5915             : 
    5916          27 :                 CPLDebug("GDAL_netCDF", "Writing X values");
    5917             :                 status =
    5918          27 :                     nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
    5919          27 :                 NCDF_ERR(status);
    5920             : 
    5921          27 :                 CPLDebug("GDAL_netCDF", "Writing Y values");
    5922             :                 status =
    5923          27 :                     nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
    5924          27 :                 NCDF_ERR(status);
    5925             :             }
    5926             : 
    5927          31 :             if (pfnProgress)
    5928          27 :                 pfnProgress(0.20, nullptr, pProgressData);
    5929             : 
    5930             :             // Write lon/lat arrays (CF coordinates) if requested.
    5931             : 
    5932             :             // Get OGR transform if GEOLOCATION is not available.
    5933          31 :             if (bWriteLonLat && !bHasGeoloc)
    5934             :             {
    5935           3 :                 poLatLonSRS.reset(m_oSRS.CloneGeogCS());
    5936           3 :                 if (poLatLonSRS != nullptr)
    5937             :                 {
    5938           3 :                     poLatLonSRS->SetAxisMappingStrategy(
    5939             :                         OAMS_TRADITIONAL_GIS_ORDER);
    5940           3 :                     poTransform.reset(OGRCreateCoordinateTransformation(
    5941           3 :                         &m_oSRS, poLatLonSRS.get()));
    5942             :                 }
    5943             :                 // If no OGR transform, then don't write CF lon/lat.
    5944           3 :                 if (poTransform == nullptr)
    5945             :                 {
    5946           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5947             :                              "Unable to get Coordinate Transform");
    5948           0 :                     bWriteLonLat = false;
    5949             :                 }
    5950             :             }
    5951             : 
    5952          31 :             if (bWriteLonLat)
    5953             :             {
    5954           4 :                 if (!bHasGeoloc)
    5955           3 :                     CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
    5956             :                 else
    5957           1 :                     CPLDebug("GDAL_netCDF",
    5958             :                              "Writing (lon,lat) from GEOLOCATION arrays");
    5959             : 
    5960           4 :                 bool bOK = true;
    5961           4 :                 double dfProgress = 0.2;
    5962             : 
    5963           4 :                 size_t start[] = {0, 0};
    5964           4 :                 size_t count[] = {1, (size_t)nRasterXSize};
    5965             :                 std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
    5966             :                     static_cast<double *>(
    5967           8 :                         VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5968           4 :                     VSIFree);
    5969             :                 std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    5970             :                     static_cast<double *>(
    5971           8 :                         VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    5972           4 :                     VSIFree);
    5973           4 :                 double *padLonVal = adLonValKeeper.get();
    5974           4 :                 double *padLatVal = adLatValKeeper.get();
    5975           4 :                 if (!padLonVal || !padLatVal)
    5976             :                 {
    5977           0 :                     return CE_Failure;
    5978             :                 }
    5979             : 
    5980         103 :                 for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
    5981             :                      j++)
    5982             :                 {
    5983          99 :                     start[0] = j;
    5984             : 
    5985             :                     // Get values from geotransform.
    5986          99 :                     if (!bHasGeoloc)
    5987             :                     {
    5988             :                         // Fill values to transform.
    5989          60 :                         if (m_gt.IsAxisAligned())
    5990             :                         {
    5991         420 :                             for (int i = 0; i < nRasterXSize; i++)
    5992             :                             {
    5993         400 :                                 padLatVal[i] = padYVal[j];
    5994         400 :                                 padLonVal[i] = padXVal[i];
    5995             :                             }
    5996             :                         }
    5997             :                         else
    5998             :                         {
    5999         840 :                             for (int i = 0; i < nRasterXSize; i++)
    6000             :                             {
    6001         800 :                                 if (!bBottomUp)
    6002             :                                 {
    6003         400 :                                     padLatVal[i] = m_gt.yorig +
    6004         400 :                                                    (i + 0.5) * m_gt.yrot +
    6005         400 :                                                    (j + 0.5) * m_gt.yscale;
    6006         400 :                                     padLonVal[i] = m_gt.xorig +
    6007         400 :                                                    (i + 0.5) * m_gt.xscale +
    6008         400 :                                                    (j + 0.5) * m_gt.xrot;
    6009             :                                 }
    6010             :                                 else
    6011             :                                 {
    6012         400 :                                     padLatVal[i] =
    6013         400 :                                         m_gt.yorig + (i + 0.5) * m_gt.yrot +
    6014         400 :                                         (nRasterYSize - j - 0.5) * m_gt.yscale;
    6015         400 :                                     padLonVal[i] =
    6016         400 :                                         m_gt.xorig + (i + 0.5) * m_gt.xscale +
    6017         400 :                                         (nRasterYSize - j - 0.5) * m_gt.xrot;
    6018             :                                 }
    6019             :                             }
    6020             :                         }
    6021             : 
    6022             :                         // Do the transform.
    6023         120 :                         bOK = CPL_TO_BOOL(poTransform->Transform(
    6024          60 :                             nRasterXSize, padLonVal, padLatVal, nullptr));
    6025          60 :                         if (!bOK)
    6026             :                         {
    6027           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    6028             :                                      "Unable to Transform (X,Y) to (lon,lat).");
    6029             :                         }
    6030             :                     }
    6031             :                     // Get values from geoloc arrays.
    6032             :                     else
    6033             :                     {
    6034          39 :                         CPLErr eErr = poBand_Y->RasterIO(
    6035             :                             GF_Read, 0, j, nRasterXSize, 1, padLatVal,
    6036             :                             nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
    6037          39 :                         if (eErr == CE_None)
    6038             :                         {
    6039          39 :                             eErr = poBand_X->RasterIO(
    6040             :                                 GF_Read, 0, j, nRasterXSize, 1, padLonVal,
    6041             :                                 nRasterXSize, 1, GDT_Float64, 0, 0, nullptr);
    6042             :                         }
    6043             : 
    6044          39 :                         if (eErr == CE_None)
    6045             :                         {
    6046          39 :                             bOK = true;
    6047             :                         }
    6048             :                         else
    6049             :                         {
    6050           0 :                             bOK = false;
    6051           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    6052             :                                      "Unable to get scanline %d", j);
    6053             :                         }
    6054             :                     }
    6055             : 
    6056             :                     // Write data.
    6057          99 :                     if (bOK)
    6058             :                     {
    6059          99 :                         status = nc_put_vara_double(cdfid, nVarLatID, start,
    6060             :                                                     count, padLatVal);
    6061          99 :                         NCDF_ERR(status);
    6062          99 :                         status = nc_put_vara_double(cdfid, nVarLonID, start,
    6063             :                                                     count, padLonVal);
    6064          99 :                         NCDF_ERR(status);
    6065             :                     }
    6066             : 
    6067          99 :                     if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6068          99 :                         (j % (nRasterYSize / 10) == 0))
    6069             :                     {
    6070          43 :                         dfProgress += 0.08;
    6071          43 :                         pfnProgress(dfProgress, nullptr, pProgressData);
    6072             :                     }
    6073             :                 }
    6074             :             }
    6075             :         }  // Projected
    6076             : 
    6077             :         // If not projected/geographic and has geoloc
    6078          57 :         else if (!bIsGeographic && bHasGeoloc && m_gt.IsAxisAligned())
    6079             :         {
    6080             :             // Use
    6081             :             // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
    6082             : 
    6083           4 :             bool bOK = true;
    6084           4 :             double dfProgress = 0.2;
    6085             : 
    6086             :             // Make sure we are in data mode.
    6087           4 :             SetDefineMode(false);
    6088             : 
    6089             :             size_t startX[1];
    6090             :             size_t countX[1];
    6091             :             size_t startY[1];
    6092             :             size_t countY[1];
    6093           4 :             startX[0] = 0;
    6094           4 :             countX[0] = nRasterXSize;
    6095             : 
    6096           4 :             startY[0] = 0;
    6097           4 :             countY[0] = nRasterYSize;
    6098             : 
    6099           4 :             std::vector<double> adfXVal;
    6100           4 :             std::vector<double> adfYVal;
    6101             :             try
    6102             :             {
    6103           4 :                 adfXVal.resize(nRasterXSize);
    6104           4 :                 adfYVal.resize(nRasterYSize);
    6105             :             }
    6106           0 :             catch (const std::exception &)
    6107             :             {
    6108           0 :                 CPLError(CE_Failure, CPLE_OutOfMemory,
    6109             :                          "Out of memory allocating temporary array");
    6110           0 :                 return CE_Failure;
    6111             :             }
    6112          16 :             for (int i = 0; i < nRasterXSize; i++)
    6113          12 :                 adfXVal[i] = i;
    6114          12 :             for (int i = 0; i < nRasterYSize; i++)
    6115           8 :                 adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
    6116             : 
    6117           4 :             CPLDebug("GDAL_netCDF", "Writing X values");
    6118           4 :             int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
    6119           4 :                                             adfXVal.data());
    6120           4 :             NCDF_ERR(status);
    6121             : 
    6122           4 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    6123           4 :             status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
    6124           4 :                                         adfYVal.data());
    6125           4 :             NCDF_ERR(status);
    6126             : 
    6127           4 :             if (pfnProgress)
    6128           0 :                 pfnProgress(0.20, nullptr, pProgressData);
    6129             : 
    6130           4 :             size_t start[] = {0, 0};
    6131           4 :             size_t count[] = {1, (size_t)nRasterXSize};
    6132             : 
    6133             :             std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(
    6134             :                 static_cast<double *>(
    6135           8 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6136           4 :                 VSIFree);
    6137             :             std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    6138             :                 static_cast<double *>(
    6139           8 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6140           4 :                 VSIFree);
    6141           4 :             double *padLonVal = adLonValKeeper.get();
    6142           4 :             double *padLatVal = adLatValKeeper.get();
    6143           4 :             if (!padLonVal || !padLatVal)
    6144             :             {
    6145           0 :                 return CE_Failure;
    6146             :             }
    6147             : 
    6148          12 :             for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
    6149             :             {
    6150           8 :                 start[0] = j;
    6151             : 
    6152           8 :                 CPLErr eErr = poBand_Y->RasterIO(
    6153           8 :                     GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
    6154             :                     nRasterXSize, 1, padLatVal, nRasterXSize, 1, GDT_Float64, 0,
    6155             :                     0, nullptr);
    6156           8 :                 if (eErr == CE_None)
    6157             :                 {
    6158           8 :                     eErr = poBand_X->RasterIO(
    6159           8 :                         GF_Read, 0, bBottomUp ? nRasterYSize - 1 - j : j,
    6160             :                         nRasterXSize, 1, padLonVal, nRasterXSize, 1,
    6161             :                         GDT_Float64, 0, 0, nullptr);
    6162             :                 }
    6163             : 
    6164           8 :                 if (eErr == CE_None)
    6165             :                 {
    6166           8 :                     bOK = true;
    6167             :                 }
    6168             :                 else
    6169             :                 {
    6170           0 :                     bOK = false;
    6171           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    6172             :                              "Unable to get scanline %d", j);
    6173             :                 }
    6174             : 
    6175             :                 // Write data.
    6176           8 :                 if (bOK)
    6177             :                 {
    6178           8 :                     status = nc_put_vara_double(cdfid, nVarLatID, start, count,
    6179             :                                                 padLatVal);
    6180           8 :                     NCDF_ERR(status);
    6181           8 :                     status = nc_put_vara_double(cdfid, nVarLonID, start, count,
    6182             :                                                 padLonVal);
    6183           8 :                     NCDF_ERR(status);
    6184             :                 }
    6185             : 
    6186           8 :                 if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6187           0 :                     (j % (nRasterYSize / 10) == 0))
    6188             :                 {
    6189           0 :                     dfProgress += 0.08;
    6190           0 :                     pfnProgress(dfProgress, nullptr, pProgressData);
    6191             :                 }
    6192             :             }
    6193             :         }
    6194             : 
    6195             :         // If not projected, assume geographic to catch grids without Datum.
    6196          53 :         else if (bWriteLonLat)
    6197             :         {
    6198             :             // Get latitude values.
    6199          53 :             const double dfY0 = (!bBottomUp) ? m_gt.yorig :
    6200             :                                              // Invert latitude values.
    6201          53 :                                     m_gt.yorig + (m_gt.yscale * nRasterYSize);
    6202          53 :             const double dfDY = m_gt.yscale;
    6203             : 
    6204             :             std::unique_ptr<double, decltype(&VSIFree)> adLatValKeeper(nullptr,
    6205          53 :                                                                        VSIFree);
    6206          53 :             double *padLatVal = nullptr;
    6207             :             // Override lat values with the ones in GEOLOCATION/Y_VALUES.
    6208          53 :             if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
    6209             :                 nullptr)
    6210             :             {
    6211           0 :                 int nTemp = 0;
    6212           0 :                 adLatValKeeper.reset(Get1DGeolocation("Y_VALUES", nTemp));
    6213           0 :                 padLatVal = adLatValKeeper.get();
    6214             :                 // Make sure we got the correct amount, if not fallback to GT */
    6215             :                 // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
    6216           0 :                 if (nTemp == nRasterYSize)
    6217             :                 {
    6218           0 :                     CPLDebug(
    6219             :                         "GDAL_netCDF",
    6220             :                         "Using Y_VALUES geolocation metadata for lat values");
    6221             :                 }
    6222             :                 else
    6223             :                 {
    6224           0 :                     CPLDebug("GDAL_netCDF",
    6225             :                              "Got %d elements from Y_VALUES geolocation "
    6226             :                              "metadata, need %d",
    6227             :                              nTemp, nRasterYSize);
    6228           0 :                     padLatVal = nullptr;
    6229             :                 }
    6230             :             }
    6231             : 
    6232          53 :             if (padLatVal == nullptr)
    6233             :             {
    6234          53 :                 adLatValKeeper.reset(static_cast<double *>(
    6235          53 :                     VSI_MALLOC2_VERBOSE(nRasterYSize, sizeof(double))));
    6236          53 :                 padLatVal = adLatValKeeper.get();
    6237          53 :                 if (!padLatVal)
    6238             :                 {
    6239           0 :                     return CE_Failure;
    6240             :                 }
    6241        7105 :                 for (int i = 0; i < nRasterYSize; i++)
    6242             :                 {
    6243             :                     // The data point is centered inside the pixel.
    6244        7052 :                     if (!bBottomUp)
    6245           0 :                         padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
    6246             :                     else  // Invert latitude values.
    6247        7052 :                         padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
    6248             :                 }
    6249             :             }
    6250             : 
    6251          53 :             size_t startLat[1] = {0};
    6252          53 :             size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
    6253             : 
    6254             :             // Get longitude values.
    6255          53 :             const double dfX0 = m_gt.xorig;
    6256          53 :             const double dfDX = m_gt.xscale;
    6257             : 
    6258             :             std::unique_ptr<double, decltype(&VSIFree)> adLonValKeeper(
    6259             :                 static_cast<double *>(
    6260         106 :                     VSI_MALLOC2_VERBOSE(nRasterXSize, sizeof(double))),
    6261          53 :                 VSIFree);
    6262          53 :             double *padLonVal = adLonValKeeper.get();
    6263          53 :             if (!padLonVal)
    6264             :             {
    6265           0 :                 return CE_Failure;
    6266             :             }
    6267        7157 :             for (int i = 0; i < nRasterXSize; i++)
    6268             :             {
    6269             :                 // The data point is centered inside the pixel.
    6270        7104 :                 padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
    6271             :             }
    6272             : 
    6273          53 :             size_t startLon[1] = {0};
    6274          53 :             size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
    6275             : 
    6276             :             // Write latitude and longitude values.
    6277             : 
    6278             :             // Make sure we are in data mode.
    6279          53 :             SetDefineMode(false);
    6280             : 
    6281             :             // Write values.
    6282          53 :             CPLDebug("GDAL_netCDF", "Writing lat values");
    6283             : 
    6284          53 :             int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
    6285             :                                             countLat, padLatVal);
    6286          53 :             NCDF_ERR(status);
    6287             : 
    6288          53 :             CPLDebug("GDAL_netCDF", "Writing lon values");
    6289          53 :             status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
    6290             :                                         padLonVal);
    6291          53 :             NCDF_ERR(status);
    6292             : 
    6293             :         }  // Not projected.
    6294             : 
    6295          88 :         if (pfnProgress)
    6296          47 :             pfnProgress(1.00, nullptr, pProgressData);
    6297             :     }
    6298             : 
    6299         176 :     return CE_None;
    6300             : }
    6301             : 
    6302             : // Write Projection variable to band variable.
    6303             : // Moved from AddProjectionVars() for cases when bands are added after
    6304             : // projection.
    6305         447 : bool netCDFDataset::AddGridMappingRef()
    6306             : {
    6307         447 :     bool bRet = true;
    6308         447 :     bool bOldDefineMode = bDefineMode;
    6309             : 
    6310         650 :     if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
    6311         203 :         ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
    6312         195 :          (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
    6313             :     {
    6314          79 :         bAddedGridMappingRef = true;
    6315             : 
    6316             :         // Make sure we are in define mode.
    6317          79 :         SetDefineMode(true);
    6318             : 
    6319         204 :         for (int i = 1; i <= nBands; i++)
    6320             :         {
    6321             :             const int nVarId =
    6322         125 :                 cpl::down_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
    6323             : 
    6324         125 :             if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
    6325             :             {
    6326             :                 int status =
    6327         242 :                     nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
    6328         121 :                                     strlen(pszCFProjection), pszCFProjection);
    6329         121 :                 NCDF_ERR(status);
    6330         121 :                 if (status != NC_NOERR)
    6331           0 :                     bRet = false;
    6332             :             }
    6333         125 :             if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
    6334             :             {
    6335             :                 int status =
    6336           8 :                     nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
    6337             :                                     strlen(pszCFCoordinates), pszCFCoordinates);
    6338           8 :                 NCDF_ERR(status);
    6339           8 :                 if (status != NC_NOERR)
    6340           0 :                     bRet = false;
    6341             :             }
    6342             :         }
    6343             : 
    6344             :         // Go back to previous define mode.
    6345          79 :         SetDefineMode(bOldDefineMode);
    6346             :     }
    6347         447 :     return bRet;
    6348             : }
    6349             : 
    6350             : /************************************************************************/
    6351             : /*                          GetGeoTransform()                           */
    6352             : /************************************************************************/
    6353             : 
    6354         129 : CPLErr netCDFDataset::GetGeoTransform(GDALGeoTransform &gt) const
    6355             : 
    6356             : {
    6357         129 :     gt = m_gt;
    6358         129 :     if (m_bHasGeoTransform)
    6359          97 :         return CE_None;
    6360             : 
    6361          32 :     return GDALPamDataset::GetGeoTransform(gt);
    6362             : }
    6363             : 
    6364             : /************************************************************************/
    6365             : /*                                rint()                                */
    6366             : /************************************************************************/
    6367             : 
    6368           0 : double netCDFDataset::rint(double dfX)
    6369             : {
    6370           0 :     return std::round(dfX);
    6371             : }
    6372             : 
    6373             : /************************************************************************/
    6374             : /*                        NCDFReadIsoMetadata()                         */
    6375             : /************************************************************************/
    6376             : 
    6377          16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
    6378             : {
    6379          16 :     int nbAttr = 0;
    6380          16 :     NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
    6381             : 
    6382          32 :     std::map<std::string, CPLJSONArray> oMapNameToArray;
    6383          40 :     for (int l = 0; l < nbAttr; l++)
    6384             :     {
    6385             :         char szAttrName[NC_MAX_NAME + 1];
    6386          24 :         szAttrName[0] = 0;
    6387          24 :         NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
    6388             : 
    6389          24 :         char *pszMetaValue = nullptr;
    6390          24 :         if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
    6391             :         {
    6392          24 :             nc_type nAttrType = NC_NAT;
    6393          24 :             size_t nAttrLen = 0;
    6394             : 
    6395          24 :             NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
    6396             :                                 &nAttrLen));
    6397             : 
    6398          24 :             std::string osAttrName(szAttrName);
    6399          24 :             const auto sharpPos = osAttrName.find('#');
    6400          24 :             if (sharpPos == std::string::npos)
    6401             :             {
    6402          16 :                 if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
    6403           4 :                     obj.Add(osAttrName, CPLAtof(pszMetaValue));
    6404             :                 else
    6405          12 :                     obj.Add(osAttrName, pszMetaValue);
    6406             :             }
    6407             :             else
    6408             :             {
    6409           8 :                 osAttrName.resize(sharpPos);
    6410           8 :                 auto iter = oMapNameToArray.find(osAttrName);
    6411           8 :                 if (iter == oMapNameToArray.end())
    6412             :                 {
    6413           8 :                     CPLJSONArray array;
    6414           4 :                     obj.Add(osAttrName, array);
    6415           4 :                     oMapNameToArray[osAttrName] = array;
    6416           4 :                     array.Add(pszMetaValue);
    6417             :                 }
    6418             :                 else
    6419             :                 {
    6420           4 :                     iter->second.Add(pszMetaValue);
    6421             :                 }
    6422             :             }
    6423          24 :             CPLFree(pszMetaValue);
    6424          24 :             pszMetaValue = nullptr;
    6425             :         }
    6426             :     }
    6427             : 
    6428          16 :     int nSubGroups = 0;
    6429          16 :     int *panSubGroupIds = nullptr;
    6430          16 :     NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
    6431          16 :     oMapNameToArray.clear();
    6432          28 :     for (int i = 0; i < nSubGroups; i++)
    6433             :     {
    6434          24 :         CPLJSONObject subObj;
    6435          12 :         NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
    6436             : 
    6437          24 :         std::string osGroupName;
    6438          12 :         osGroupName.resize(NC_MAX_NAME);
    6439          12 :         NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
    6440          12 :         osGroupName.resize(strlen(osGroupName.data()));
    6441          12 :         const auto sharpPos = osGroupName.find('#');
    6442          12 :         if (sharpPos == std::string::npos)
    6443             :         {
    6444           4 :             obj.Add(osGroupName, subObj);
    6445             :         }
    6446             :         else
    6447             :         {
    6448           8 :             osGroupName.resize(sharpPos);
    6449           8 :             auto iter = oMapNameToArray.find(osGroupName);
    6450           8 :             if (iter == oMapNameToArray.end())
    6451             :             {
    6452           8 :                 CPLJSONArray array;
    6453           4 :                 obj.Add(osGroupName, array);
    6454           4 :                 oMapNameToArray[osGroupName] = array;
    6455           4 :                 array.Add(subObj);
    6456             :             }
    6457             :             else
    6458             :             {
    6459           4 :                 iter->second.Add(subObj);
    6460             :             }
    6461             :         }
    6462             :     }
    6463          16 :     CPLFree(panSubGroupIds);
    6464          16 : }
    6465             : 
    6466           4 : std::string NCDFReadMetadataAsJson(int cdfid)
    6467             : {
    6468           8 :     CPLJSONDocument oDoc;
    6469           8 :     CPLJSONObject oRoot = oDoc.GetRoot();
    6470           4 :     NCDFReadMetadataAsJson(cdfid, oRoot);
    6471           8 :     return oDoc.SaveAsString();
    6472             : }
    6473             : 
    6474             : /************************************************************************/
    6475             : /*                           ReadAttributes()                           */
    6476             : /************************************************************************/
    6477        1896 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
    6478             : 
    6479             : {
    6480        1896 :     char *pszVarFullName = nullptr;
    6481        1896 :     ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
    6482             : 
    6483             :     // For metadata in Sentinel 5
    6484        1896 :     if (STARTS_WITH(pszVarFullName, "/METADATA/"))
    6485             :     {
    6486           6 :         for (const char *key :
    6487             :              {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
    6488           8 :               "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
    6489             :         {
    6490          14 :             if (var == NC_GLOBAL &&
    6491           7 :                 strcmp(pszVarFullName,
    6492             :                        CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
    6493             :             {
    6494           1 :                 CPLFree(pszVarFullName);
    6495           1 :                 CPLStringList aosList;
    6496           2 :                 aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
    6497           1 :                                       .replaceAll("\\/", '/'));
    6498           1 :                 m_oMapDomainToJSon[key] = std::move(aosList);
    6499           1 :                 return CE_None;
    6500             :             }
    6501             :         }
    6502             :     }
    6503        1895 :     if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
    6504             :     {
    6505           0 :         CPLFree(pszVarFullName);
    6506           0 :         CPLStringList aosList;
    6507             :         aosList.AddString(
    6508           0 :             CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
    6509           0 :         m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
    6510           0 :         return CE_None;
    6511             :     }
    6512             : 
    6513        1895 :     size_t nMetaNameSize =
    6514        1895 :         sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
    6515        1895 :     char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
    6516             : 
    6517        1895 :     int nbAttr = 0;
    6518        1895 :     NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
    6519             : 
    6520        9609 :     for (int l = 0; l < nbAttr; l++)
    6521             :     {
    6522             :         char szAttrName[NC_MAX_NAME + 1];
    6523        7714 :         szAttrName[0] = 0;
    6524        7714 :         NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
    6525        7714 :         snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
    6526             :                  szAttrName);
    6527             : 
    6528        7714 :         char *pszMetaTemp = nullptr;
    6529        7714 :         if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
    6530             :         {
    6531        7713 :             papszMetadata =
    6532        7713 :                 CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
    6533        7713 :             CPLFree(pszMetaTemp);
    6534        7713 :             pszMetaTemp = nullptr;
    6535             :         }
    6536             :         else
    6537             :         {
    6538           1 :             CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
    6539             :         }
    6540             :     }
    6541             : 
    6542        1895 :     CPLFree(pszVarFullName);
    6543        1895 :     CPLFree(pszMetaName);
    6544             : 
    6545        1895 :     if (var == NC_GLOBAL)
    6546             :     {
    6547             :         // Recurse on sub-groups.
    6548         545 :         int nSubGroups = 0;
    6549         545 :         int *panSubGroupIds = nullptr;
    6550         545 :         NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
    6551         577 :         for (int i = 0; i < nSubGroups; i++)
    6552             :         {
    6553          32 :             ReadAttributes(panSubGroupIds[i], var);
    6554             :         }
    6555         545 :         CPLFree(panSubGroupIds);
    6556             :     }
    6557             : 
    6558        1895 :     return CE_None;
    6559             : }
    6560             : 
    6561             : /************************************************************************/
    6562             : /*                netCDFDataset::CreateSubDatasetList()                 */
    6563             : /************************************************************************/
    6564          59 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
    6565             : {
    6566             :     char szVarStdName[NC_MAX_NAME + 1];
    6567          59 :     int *ponDimIds = nullptr;
    6568             :     nc_type nAttype;
    6569             :     size_t nAttlen;
    6570             : 
    6571          59 :     netCDFDataset *poDS = this;
    6572             : 
    6573             :     int nVarCount;
    6574          59 :     nc_inq_nvars(nGroupId, &nVarCount);
    6575             : 
    6576          59 :     const bool bListAllArrays = CPLTestBool(
    6577          59 :         CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    6578             : 
    6579         358 :     for (int nVar = 0; nVar < nVarCount; nVar++)
    6580             :     {
    6581             : 
    6582             :         int nDims;
    6583         299 :         nc_inq_varndims(nGroupId, nVar, &nDims);
    6584             : 
    6585         299 :         if ((bListAllArrays && nDims > 0) || nDims >= 2)
    6586             :         {
    6587         174 :             ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
    6588         174 :             nc_inq_vardimid(nGroupId, nVar, ponDimIds);
    6589             : 
    6590             :             // Create Sub dataset list.
    6591         174 :             CPLString osDim;
    6592         535 :             for (int i = 0; i < nDims; i++)
    6593             :             {
    6594             :                 size_t nDimLen;
    6595         361 :                 nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
    6596         361 :                 if (!osDim.empty())
    6597         187 :                     osDim += 'x';
    6598         361 :                 osDim += CPLSPrintf("%d", (int)nDimLen);
    6599             :             }
    6600         174 :             CPLFree(ponDimIds);
    6601             : 
    6602             :             nc_type nVarType;
    6603         174 :             nc_inq_vartype(nGroupId, nVar, &nVarType);
    6604         174 :             const char *pszType = "";
    6605         174 :             switch (nVarType)
    6606             :             {
    6607          42 :                 case NC_BYTE:
    6608          42 :                     pszType = "8-bit integer";
    6609          42 :                     break;
    6610           2 :                 case NC_CHAR:
    6611           2 :                     pszType = "8-bit character";
    6612           2 :                     break;
    6613           6 :                 case NC_SHORT:
    6614           6 :                     pszType = "16-bit integer";
    6615           6 :                     break;
    6616          10 :                 case NC_INT:
    6617          10 :                     pszType = "32-bit integer";
    6618          10 :                     break;
    6619          62 :                 case NC_FLOAT:
    6620          62 :                     pszType = "32-bit floating-point";
    6621          62 :                     break;
    6622          34 :                 case NC_DOUBLE:
    6623          34 :                     pszType = "64-bit floating-point";
    6624          34 :                     break;
    6625           4 :                 case NC_UBYTE:
    6626           4 :                     pszType = "8-bit unsigned integer";
    6627           4 :                     break;
    6628           1 :                 case NC_USHORT:
    6629           1 :                     pszType = "16-bit unsigned integer";
    6630           1 :                     break;
    6631           1 :                 case NC_UINT:
    6632           1 :                     pszType = "32-bit unsigned integer";
    6633           1 :                     break;
    6634           1 :                 case NC_INT64:
    6635           1 :                     pszType = "64-bit integer";
    6636           1 :                     break;
    6637           1 :                 case NC_UINT64:
    6638           1 :                     pszType = "64-bit unsigned integer";
    6639           1 :                     break;
    6640          10 :                 default:
    6641          10 :                     break;
    6642             :             }
    6643             : 
    6644         174 :             char *pszName = nullptr;
    6645         174 :             if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
    6646           0 :                 continue;
    6647             : 
    6648         174 :             nSubDatasets++;
    6649             : 
    6650         174 :             nAttlen = 0;
    6651         174 :             nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
    6652         348 :             if (nAttlen < sizeof(szVarStdName) &&
    6653         174 :                 nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
    6654             :                     NC_NOERR)
    6655             :             {
    6656          64 :                 szVarStdName[nAttlen] = '\0';
    6657             :             }
    6658             :             else
    6659             :             {
    6660         110 :                 snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
    6661             :             }
    6662             : 
    6663             :             char szTemp[NC_MAX_NAME + 1];
    6664         174 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
    6665             :                      nSubDatasets);
    6666             : 
    6667         174 :             if (strchr(pszName, ' ') || strchr(pszName, ':'))
    6668             :             {
    6669           1 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6670             :                     poDS->papszSubDatasets, szTemp,
    6671             :                     CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
    6672             :                                pszName));
    6673             :             }
    6674             :             else
    6675             :             {
    6676         173 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6677             :                     poDS->papszSubDatasets, szTemp,
    6678             :                     CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
    6679             :                                pszName));
    6680             :             }
    6681             : 
    6682         174 :             CPLFree(pszName);
    6683             : 
    6684         174 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
    6685             :                      nSubDatasets);
    6686             : 
    6687         174 :             poDS->papszSubDatasets =
    6688         174 :                 CSLSetNameValue(poDS->papszSubDatasets, szTemp,
    6689             :                                 CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
    6690             :                                            szVarStdName, pszType));
    6691             :         }
    6692             :     }
    6693             : 
    6694             :     // Recurse on sub groups.
    6695          59 :     int nSubGroups = 0;
    6696          59 :     int *panSubGroupIds = nullptr;
    6697          59 :     NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
    6698          66 :     for (int i = 0; i < nSubGroups; i++)
    6699             :     {
    6700           7 :         CreateSubDatasetList(panSubGroupIds[i]);
    6701             :     }
    6702          59 :     CPLFree(panSubGroupIds);
    6703          59 : }
    6704             : 
    6705             : /************************************************************************/
    6706             : /*                           TestCapability()                           */
    6707             : /************************************************************************/
    6708             : 
    6709         249 : int netCDFDataset::TestCapability(const char *pszCap) const
    6710             : {
    6711         249 :     if (EQUAL(pszCap, ODsCCreateLayer))
    6712             :     {
    6713         225 :         return eAccess == GA_Update && nBands == 0 &&
    6714         219 :                (eMultipleLayerBehavior != SINGLE_LAYER ||
    6715         230 :                 this->GetLayerCount() == 0 || bSGSupport);
    6716             :     }
    6717         136 :     else if (EQUAL(pszCap, ODsCZGeometries))
    6718           2 :         return true;
    6719             : 
    6720         134 :     return false;
    6721             : }
    6722             : 
    6723             : /************************************************************************/
    6724             : /*                              GetLayer()                              */
    6725             : /************************************************************************/
    6726             : 
    6727         385 : const OGRLayer *netCDFDataset::GetLayer(int nIdx) const
    6728             : {
    6729         385 :     if (nIdx < 0 || nIdx >= this->GetLayerCount())
    6730           2 :         return nullptr;
    6731         383 :     return papoLayers[nIdx].get();
    6732             : }
    6733             : 
    6734             : /************************************************************************/
    6735             : /*                            ICreateLayer()                            */
    6736             : /************************************************************************/
    6737             : 
    6738          60 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
    6739             :                                       const OGRGeomFieldDefn *poGeomFieldDefn,
    6740             :                                       CSLConstList papszOptions)
    6741             : {
    6742          60 :     int nLayerCDFId = cdfid;
    6743          60 :     if (!TestCapability(ODsCCreateLayer))
    6744           0 :         return nullptr;
    6745             : 
    6746          60 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    6747             :     const auto poSpatialRef =
    6748          60 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6749             : 
    6750         120 :     CPLString osNetCDFLayerName(pszName);
    6751          60 :     const netCDFWriterConfigLayer *poLayerConfig = nullptr;
    6752          60 :     if (oWriterConfig.m_bIsValid)
    6753             :     {
    6754             :         std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
    6755           2 :             oLayerIter = oWriterConfig.m_oLayers.find(pszName);
    6756           2 :         if (oLayerIter != oWriterConfig.m_oLayers.end())
    6757             :         {
    6758           1 :             poLayerConfig = &(oLayerIter->second);
    6759           1 :             osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
    6760             :         }
    6761             :     }
    6762             : 
    6763          60 :     netCDFDataset *poLayerDataset = nullptr;
    6764          60 :     if (eMultipleLayerBehavior == SEPARATE_FILES)
    6765             :     {
    6766           3 :         if (CPLLaunderForFilenameSafe(osNetCDFLayerName.c_str(), nullptr) !=
    6767             :             osNetCDFLayerName)
    6768             :         {
    6769           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6770             :                      "Illegal characters in '%s' to form a valid filename",
    6771             :                      osNetCDFLayerName.c_str());
    6772           1 :             return nullptr;
    6773             :         }
    6774           2 :         CPLStringList aosDatasetOptions;
    6775             :         aosDatasetOptions.SetNameValue(
    6776             :             "CONFIG_FILE",
    6777           2 :             CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
    6778             :         aosDatasetOptions.SetNameValue(
    6779           2 :             "FORMAT", CSLFetchNameValue(papszCreationOptions, "FORMAT"));
    6780             :         aosDatasetOptions.SetNameValue(
    6781             :             "WRITE_GDAL_TAGS",
    6782           2 :             CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
    6783             :         const CPLString osLayerFilename(
    6784           2 :             CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
    6785           2 :         CPLAcquireMutex(hNCMutex, 1000.0);
    6786           2 :         poLayerDataset =
    6787           2 :             CreateLL(osLayerFilename, 0, 0, 0, aosDatasetOptions.List());
    6788           2 :         CPLReleaseMutex(hNCMutex);
    6789           2 :         if (poLayerDataset == nullptr)
    6790           0 :             return nullptr;
    6791             : 
    6792           2 :         nLayerCDFId = poLayerDataset->cdfid;
    6793           2 :         NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
    6794           2 :                            bWriteGDALHistory, "", "Create",
    6795             :                            NCDF_CONVENTIONS_CF_V1_6);
    6796             :     }
    6797          57 :     else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
    6798             :     {
    6799           2 :         SetDefineMode(true);
    6800             : 
    6801           2 :         nLayerCDFId = -1;
    6802           2 :         int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
    6803           2 :         NCDF_ERR(status);
    6804           2 :         if (status != NC_NOERR)
    6805           0 :             return nullptr;
    6806             : 
    6807           2 :         NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
    6808           2 :                            bWriteGDALHistory, "", "Create",
    6809             :                            NCDF_CONVENTIONS_CF_V1_6);
    6810             :     }
    6811             : 
    6812             :     // Make a clone to workaround a bug in released MapServer versions
    6813             :     // that destroys the passed SRS instead of releasing it .
    6814          59 :     OGRSpatialReference *poSRS = nullptr;
    6815          59 :     if (poSpatialRef)
    6816             :     {
    6817          43 :         poSRS = poSpatialRef->Clone();
    6818          43 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6819             :     }
    6820             :     std::shared_ptr<netCDFLayer> poLayer(
    6821          59 :         new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
    6822         118 :                         osNetCDFLayerName, eGType, poSRS));
    6823          59 :     if (poSRS != nullptr)
    6824          43 :         poSRS->Release();
    6825             : 
    6826             :     // Fetch layer creation options coming from config file
    6827         118 :     CPLStringList aosNewOptions(CSLDuplicate(papszOptions));
    6828          59 :     if (oWriterConfig.m_bIsValid)
    6829             :     {
    6830           2 :         for (const auto &[osName, osValue] :
    6831           4 :              oWriterConfig.m_oLayerCreationOptions)
    6832             :         {
    6833           1 :             aosNewOptions.SetNameValue(osName, osValue);
    6834             :         }
    6835           2 :         if (poLayerConfig != nullptr)
    6836             :         {
    6837           4 :             for (const auto &[osName, osValue] :
    6838           5 :                  poLayerConfig->m_oLayerCreationOptions)
    6839             :             {
    6840           2 :                 aosNewOptions.SetNameValue(osName, osValue);
    6841             :             }
    6842             :         }
    6843             :     }
    6844             : 
    6845          59 :     const bool bRet = poLayer->Create(aosNewOptions.List(), poLayerConfig);
    6846             : 
    6847          59 :     if (!bRet)
    6848             :     {
    6849           0 :         return nullptr;
    6850             :     }
    6851             : 
    6852          59 :     if (poLayerDataset != nullptr)
    6853           2 :         apoVectorDatasets.push_back(poLayerDataset);
    6854             : 
    6855          59 :     papoLayers.push_back(poLayer);
    6856          59 :     return poLayer.get();
    6857             : }
    6858             : 
    6859             : /************************************************************************/
    6860             : /*                          CloneAttributes()                           */
    6861             : /************************************************************************/
    6862             : 
    6863         137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
    6864             :                                     int nDstVarId)
    6865             : {
    6866         137 :     int nAttCount = -1;
    6867         137 :     int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
    6868         137 :     NCDF_ERR(status);
    6869             : 
    6870         693 :     for (int i = 0; i < nAttCount; i++)
    6871             :     {
    6872             :         char szName[NC_MAX_NAME + 1];
    6873         556 :         szName[0] = 0;
    6874         556 :         status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
    6875         556 :         NCDF_ERR(status);
    6876             : 
    6877             :         status =
    6878         556 :             nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
    6879         556 :         NCDF_ERR(status);
    6880         556 :         if (status != NC_NOERR)
    6881           0 :             return false;
    6882             :     }
    6883             : 
    6884         137 :     return true;
    6885             : }
    6886             : 
    6887             : /************************************************************************/
    6888             : /*                        CloneVariableContent()                        */
    6889             : /************************************************************************/
    6890             : 
    6891         121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
    6892             :                                          int nSrcVarId, int nDstVarId)
    6893             : {
    6894         121 :     int nVarDimCount = -1;
    6895         121 :     int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
    6896         121 :     NCDF_ERR(status);
    6897         121 :     int anDimIds[] = {-1, 1};
    6898         121 :     status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
    6899         121 :     NCDF_ERR(status);
    6900         121 :     nc_type nc_datatype = NC_NAT;
    6901         121 :     status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
    6902         121 :     NCDF_ERR(status);
    6903         121 :     size_t nTypeSize = 0;
    6904         121 :     switch (nc_datatype)
    6905             :     {
    6906          35 :         case NC_BYTE:
    6907             :         case NC_CHAR:
    6908          35 :             nTypeSize = 1;
    6909          35 :             break;
    6910           4 :         case NC_SHORT:
    6911           4 :             nTypeSize = 2;
    6912           4 :             break;
    6913          24 :         case NC_INT:
    6914          24 :             nTypeSize = 4;
    6915          24 :             break;
    6916           4 :         case NC_FLOAT:
    6917           4 :             nTypeSize = 4;
    6918           4 :             break;
    6919          43 :         case NC_DOUBLE:
    6920          43 :             nTypeSize = 8;
    6921          43 :             break;
    6922           2 :         case NC_UBYTE:
    6923           2 :             nTypeSize = 1;
    6924           2 :             break;
    6925           2 :         case NC_USHORT:
    6926           2 :             nTypeSize = 2;
    6927           2 :             break;
    6928           2 :         case NC_UINT:
    6929           2 :             nTypeSize = 4;
    6930           2 :             break;
    6931           4 :         case NC_INT64:
    6932             :         case NC_UINT64:
    6933           4 :             nTypeSize = 8;
    6934           4 :             break;
    6935           1 :         case NC_STRING:
    6936           1 :             nTypeSize = sizeof(char *);
    6937           1 :             break;
    6938           0 :         default:
    6939             :         {
    6940           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
    6941             :                      nc_datatype);
    6942           0 :             return false;
    6943             :         }
    6944             :     }
    6945             : 
    6946         121 :     size_t nElems = 1;
    6947             :     size_t anStart[NC_MAX_DIMS];
    6948             :     size_t anCount[NC_MAX_DIMS];
    6949         121 :     size_t nRecords = 1;
    6950         261 :     for (int i = 0; i < nVarDimCount; i++)
    6951             :     {
    6952         140 :         anStart[i] = 0;
    6953         140 :         if (i == 0)
    6954             :         {
    6955         116 :             anCount[i] = 1;
    6956         116 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
    6957         116 :             NCDF_ERR(status);
    6958             :         }
    6959             :         else
    6960             :         {
    6961          24 :             anCount[i] = 0;
    6962          24 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
    6963          24 :             NCDF_ERR(status);
    6964          24 :             nElems *= anCount[i];
    6965             :         }
    6966             :     }
    6967             : 
    6968             :     /* Workaround in some cases a netCDF bug:
    6969             :      * https://github.com/Unidata/netcdf-c/pull/1442 */
    6970         121 :     if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
    6971             :     {
    6972         119 :         nElems *= nRecords;
    6973         119 :         anCount[0] = nRecords;
    6974         119 :         nRecords = 1;
    6975             :     }
    6976             : 
    6977         121 :     void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
    6978         121 :     if (pBuffer == nullptr)
    6979           0 :         return false;
    6980             : 
    6981         240 :     for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
    6982             :     {
    6983         119 :         anStart[0] = iRecord;
    6984             : 
    6985         119 :         switch (nc_datatype)
    6986             :         {
    6987           5 :             case NC_BYTE:
    6988             :                 status =
    6989           5 :                     nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
    6990             :                                       static_cast<signed char *>(pBuffer));
    6991           5 :                 if (!status)
    6992           5 :                     status = nc_put_vara_schar(
    6993             :                         new_cdfid, nDstVarId, anStart, anCount,
    6994             :                         static_cast<signed char *>(pBuffer));
    6995           5 :                 break;
    6996          28 :             case NC_CHAR:
    6997             :                 status =
    6998          28 :                     nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
    6999             :                                      static_cast<char *>(pBuffer));
    7000          28 :                 if (!status)
    7001             :                     status =
    7002          28 :                         nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
    7003             :                                          static_cast<char *>(pBuffer));
    7004          28 :                 break;
    7005           4 :             case NC_SHORT:
    7006             :                 status =
    7007           4 :                     nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
    7008             :                                       static_cast<short *>(pBuffer));
    7009           4 :                 if (!status)
    7010           4 :                     status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
    7011             :                                                anCount,
    7012             :                                                static_cast<short *>(pBuffer));
    7013           4 :                 break;
    7014          24 :             case NC_INT:
    7015          24 :                 status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
    7016             :                                          static_cast<int *>(pBuffer));
    7017          24 :                 if (!status)
    7018             :                     status =
    7019          24 :                         nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
    7020             :                                         static_cast<int *>(pBuffer));
    7021          24 :                 break;
    7022           4 :             case NC_FLOAT:
    7023             :                 status =
    7024           4 :                     nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
    7025             :                                       static_cast<float *>(pBuffer));
    7026           4 :                 if (!status)
    7027           4 :                     status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
    7028             :                                                anCount,
    7029             :                                                static_cast<float *>(pBuffer));
    7030           4 :                 break;
    7031          43 :             case NC_DOUBLE:
    7032             :                 status =
    7033          43 :                     nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
    7034             :                                        static_cast<double *>(pBuffer));
    7035          43 :                 if (!status)
    7036          43 :                     status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
    7037             :                                                 anCount,
    7038             :                                                 static_cast<double *>(pBuffer));
    7039          43 :                 break;
    7040           1 :             case NC_STRING:
    7041             :                 status =
    7042           1 :                     nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
    7043             :                                        static_cast<char **>(pBuffer));
    7044           1 :                 if (!status)
    7045             :                 {
    7046           1 :                     status = nc_put_vara_string(
    7047             :                         new_cdfid, nDstVarId, anStart, anCount,
    7048             :                         static_cast<const char **>(pBuffer));
    7049           1 :                     nc_free_string(nElems, static_cast<char **>(pBuffer));
    7050             :                 }
    7051           1 :                 break;
    7052             : 
    7053           2 :             case NC_UBYTE:
    7054             :                 status =
    7055           2 :                     nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
    7056             :                                       static_cast<unsigned char *>(pBuffer));
    7057           2 :                 if (!status)
    7058           2 :                     status = nc_put_vara_uchar(
    7059             :                         new_cdfid, nDstVarId, anStart, anCount,
    7060             :                         static_cast<unsigned char *>(pBuffer));
    7061           2 :                 break;
    7062           2 :             case NC_USHORT:
    7063             :                 status =
    7064           2 :                     nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
    7065             :                                        static_cast<unsigned short *>(pBuffer));
    7066           2 :                 if (!status)
    7067           2 :                     status = nc_put_vara_ushort(
    7068             :                         new_cdfid, nDstVarId, anStart, anCount,
    7069             :                         static_cast<unsigned short *>(pBuffer));
    7070           2 :                 break;
    7071           2 :             case NC_UINT:
    7072             :                 status =
    7073           2 :                     nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
    7074             :                                      static_cast<unsigned int *>(pBuffer));
    7075           2 :                 if (!status)
    7076             :                     status =
    7077           2 :                         nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
    7078             :                                          static_cast<unsigned int *>(pBuffer));
    7079           2 :                 break;
    7080           2 :             case NC_INT64:
    7081             :                 status =
    7082           2 :                     nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
    7083             :                                          static_cast<long long *>(pBuffer));
    7084           2 :                 if (!status)
    7085           2 :                     status = nc_put_vara_longlong(
    7086             :                         new_cdfid, nDstVarId, anStart, anCount,
    7087             :                         static_cast<long long *>(pBuffer));
    7088           2 :                 break;
    7089           2 :             case NC_UINT64:
    7090           2 :                 status = nc_get_vara_ulonglong(
    7091             :                     old_cdfid, nSrcVarId, anStart, anCount,
    7092             :                     static_cast<unsigned long long *>(pBuffer));
    7093           2 :                 if (!status)
    7094           2 :                     status = nc_put_vara_ulonglong(
    7095             :                         new_cdfid, nDstVarId, anStart, anCount,
    7096             :                         static_cast<unsigned long long *>(pBuffer));
    7097           2 :                 break;
    7098           0 :             default:
    7099           0 :                 status = NC_EBADTYPE;
    7100             :         }
    7101             : 
    7102         119 :         NCDF_ERR(status);
    7103         119 :         if (status != NC_NOERR)
    7104             :         {
    7105           0 :             VSIFree(pBuffer);
    7106           0 :             return false;
    7107             :         }
    7108             :     }
    7109             : 
    7110         121 :     VSIFree(pBuffer);
    7111         121 :     return true;
    7112             : }
    7113             : 
    7114             : /************************************************************************/
    7115             : /*                         NCDFIsUnlimitedDim()                         */
    7116             : /************************************************************************/
    7117             : 
    7118          58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
    7119             : {
    7120          58 :     if (bIsNC4)
    7121             :     {
    7122          16 :         int nUnlimitedDims = 0;
    7123          16 :         nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
    7124          16 :         bool bFound = false;
    7125          16 :         if (nUnlimitedDims)
    7126             :         {
    7127             :             int *panUnlimitedDimIds =
    7128          16 :                 static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
    7129          16 :             nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
    7130          30 :             for (int i = 0; i < nUnlimitedDims; i++)
    7131             :             {
    7132          22 :                 if (panUnlimitedDimIds[i] == nDimId)
    7133             :                 {
    7134           8 :                     bFound = true;
    7135           8 :                     break;
    7136             :                 }
    7137             :             }
    7138          16 :             CPLFree(panUnlimitedDimIds);
    7139             :         }
    7140          16 :         return bFound;
    7141             :     }
    7142             :     else
    7143             :     {
    7144          42 :         int nUnlimitedDimId = -1;
    7145          42 :         nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
    7146          42 :         return nDimId == nUnlimitedDimId;
    7147             :     }
    7148             : }
    7149             : 
    7150             : /************************************************************************/
    7151             : /*                              CloneGrp()                              */
    7152             : /************************************************************************/
    7153             : 
    7154          16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
    7155             :                              int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7156             : {
    7157             :     // Clone dimensions
    7158          16 :     int nDimCount = -1;
    7159          16 :     int status = nc_inq_ndims(nOldGrpId, &nDimCount);
    7160          16 :     NCDF_ERR(status);
    7161          16 :     if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
    7162           0 :         return false;
    7163             :     int anDimIds[NC_MAX_DIMS];
    7164          16 :     int nUnlimiDimID = -1;
    7165          16 :     status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
    7166          16 :     NCDF_ERR(status);
    7167          16 :     if (bIsNC4)
    7168             :     {
    7169             :         // In NC4, the dimension ids of a group are not necessarily in
    7170             :         // [0,nDimCount-1] range
    7171           8 :         int nDimCount2 = -1;
    7172           8 :         status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
    7173           8 :         NCDF_ERR(status);
    7174           8 :         CPLAssert(nDimCount == nDimCount2);
    7175             :     }
    7176             :     else
    7177             :     {
    7178          36 :         for (int i = 0; i < nDimCount; i++)
    7179          28 :             anDimIds[i] = i;
    7180             :     }
    7181          60 :     for (int i = 0; i < nDimCount; i++)
    7182             :     {
    7183             :         char szDimName[NC_MAX_NAME + 1];
    7184          44 :         szDimName[0] = 0;
    7185          44 :         size_t nLen = 0;
    7186          44 :         const int nDimId = anDimIds[i];
    7187          44 :         status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
    7188          44 :         NCDF_ERR(status);
    7189          44 :         if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
    7190          16 :             nLen = NC_UNLIMITED;
    7191          28 :         else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
    7192          13 :             nLen = nNewSize;
    7193          44 :         int nNewDimId = -1;
    7194          44 :         status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
    7195          44 :         NCDF_ERR(status);
    7196          44 :         CPLAssert(nDimId == nNewDimId);
    7197          44 :         if (status != NC_NOERR)
    7198             :         {
    7199           0 :             return false;
    7200             :         }
    7201             :     }
    7202             : 
    7203             :     // Clone main attributes
    7204          16 :     if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
    7205             :     {
    7206           0 :         return false;
    7207             :     }
    7208             : 
    7209             :     // Clone variable definitions
    7210          16 :     int nVarCount = -1;
    7211          16 :     status = nc_inq_nvars(nOldGrpId, &nVarCount);
    7212          16 :     NCDF_ERR(status);
    7213             : 
    7214         137 :     for (int i = 0; i < nVarCount; i++)
    7215             :     {
    7216             :         char szVarName[NC_MAX_NAME + 1];
    7217         121 :         szVarName[0] = 0;
    7218         121 :         status = nc_inq_varname(nOldGrpId, i, szVarName);
    7219         121 :         NCDF_ERR(status);
    7220         121 :         nc_type nc_datatype = NC_NAT;
    7221         121 :         status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
    7222         121 :         NCDF_ERR(status);
    7223         121 :         int nVarDimCount = -1;
    7224         121 :         status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
    7225         121 :         NCDF_ERR(status);
    7226         121 :         status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
    7227         121 :         NCDF_ERR(status);
    7228         121 :         int nNewVarId = -1;
    7229         121 :         status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
    7230             :                             anDimIds, &nNewVarId);
    7231         121 :         NCDF_ERR(status);
    7232         121 :         CPLAssert(i == nNewVarId);
    7233         121 :         if (status != NC_NOERR)
    7234             :         {
    7235           0 :             return false;
    7236             :         }
    7237             : 
    7238         121 :         if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
    7239             :         {
    7240           0 :             return false;
    7241             :         }
    7242             :     }
    7243             : 
    7244          16 :     status = nc_enddef(nNewGrpId);
    7245          16 :     NCDF_ERR(status);
    7246          16 :     if (status != NC_NOERR)
    7247             :     {
    7248           0 :         return false;
    7249             :     }
    7250             : 
    7251             :     // Clone variable content
    7252         137 :     for (int i = 0; i < nVarCount; i++)
    7253             :     {
    7254         121 :         if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
    7255             :         {
    7256           0 :             return false;
    7257             :         }
    7258             :     }
    7259             : 
    7260          16 :     return true;
    7261             : }
    7262             : 
    7263             : /************************************************************************/
    7264             : /*                              GrowDim()                               */
    7265             : /************************************************************************/
    7266             : 
    7267          13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7268             : {
    7269             :     int nCreationMode;
    7270             :     // Set nCreationMode based on eFormat.
    7271          13 :     switch (eFormat)
    7272             :     {
    7273             : #ifdef NETCDF_HAS_NC2
    7274           0 :         case NCDF_FORMAT_NC2:
    7275           0 :             nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
    7276           0 :             break;
    7277             : #endif
    7278           5 :         case NCDF_FORMAT_NC4:
    7279           5 :             nCreationMode = NC_CLOBBER | NC_NETCDF4;
    7280           5 :             break;
    7281           0 :         case NCDF_FORMAT_NC4C:
    7282           0 :             nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
    7283           0 :             break;
    7284           8 :         case NCDF_FORMAT_NC:
    7285             :         default:
    7286           8 :             nCreationMode = NC_CLOBBER;
    7287           8 :             break;
    7288             :     }
    7289             : 
    7290          13 :     int new_cdfid = -1;
    7291          26 :     CPLString osTmpFilename(osFilename + ".tmp");
    7292          26 :     CPLString osFilenameForNCCreate(osTmpFilename);
    7293             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7294             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7295             :     {
    7296             :         char *pszTemp =
    7297             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    7298             :         osFilenameForNCCreate = pszTemp;
    7299             :         CPLFree(pszTemp);
    7300             :     }
    7301             : #endif
    7302          13 :     int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
    7303          13 :     NCDF_ERR(status);
    7304          13 :     if (status != NC_NOERR)
    7305           0 :         return false;
    7306             : 
    7307          13 :     if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
    7308             :                   nDimIdToGrow, nNewSize))
    7309             :     {
    7310           0 :         GDAL_nc_close(new_cdfid);
    7311           0 :         return false;
    7312             :     }
    7313             : 
    7314          13 :     int nGroupCount = 0;
    7315          26 :     std::vector<CPLString> oListGrpName;
    7316          31 :     if (eFormat == NCDF_FORMAT_NC4 &&
    7317          18 :         nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
    7318           5 :         nGroupCount > 0)
    7319             :     {
    7320             :         int *panGroupIds =
    7321           2 :             static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
    7322           2 :         status = nc_inq_grps(cdfid, nullptr, panGroupIds);
    7323           2 :         NCDF_ERR(status);
    7324           5 :         for (int i = 0; i < nGroupCount; i++)
    7325             :         {
    7326             :             char szGroupName[NC_MAX_NAME + 1];
    7327           3 :             szGroupName[0] = 0;
    7328           3 :             NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
    7329           3 :             int nNewGrpId = -1;
    7330           3 :             status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
    7331           3 :             NCDF_ERR(status);
    7332           3 :             if (status != NC_NOERR)
    7333             :             {
    7334           0 :                 CPLFree(panGroupIds);
    7335           0 :                 GDAL_nc_close(new_cdfid);
    7336           0 :                 return false;
    7337             :             }
    7338           3 :             if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
    7339             :                           nDimIdToGrow, nNewSize))
    7340             :             {
    7341           0 :                 CPLFree(panGroupIds);
    7342           0 :                 GDAL_nc_close(new_cdfid);
    7343           0 :                 return false;
    7344             :             }
    7345             :         }
    7346           2 :         CPLFree(panGroupIds);
    7347             : 
    7348           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7349             :         {
    7350           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7351           3 :             if (poLayer)
    7352             :             {
    7353             :                 char szGroupName[NC_MAX_NAME + 1];
    7354           3 :                 szGroupName[0] = 0;
    7355           3 :                 status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
    7356           3 :                 NCDF_ERR(status);
    7357           3 :                 oListGrpName.push_back(szGroupName);
    7358             :             }
    7359             :         }
    7360             :     }
    7361             : 
    7362          13 :     GDAL_nc_close(cdfid);
    7363          13 :     cdfid = -1;
    7364          13 :     GDAL_nc_close(new_cdfid);
    7365             : 
    7366          26 :     CPLString osOriFilename(osFilename + ".ori");
    7367          26 :     if (VSIRename(osFilename, osOriFilename) != 0 ||
    7368          13 :         VSIRename(osTmpFilename, osFilename) != 0)
    7369             :     {
    7370           0 :         CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
    7371           0 :         return false;
    7372             :     }
    7373          13 :     VSIUnlink(osOriFilename);
    7374             : 
    7375          26 :     CPLString osFilenameForNCOpen(osFilename);
    7376             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7377             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7378             :     {
    7379             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    7380             :         osFilenameForNCOpen = pszTemp;
    7381             :         CPLFree(pszTemp);
    7382             :     }
    7383             : #endif
    7384          13 :     status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
    7385          13 :     NCDF_ERR(status);
    7386          13 :     if (status != NC_NOERR)
    7387           0 :         return false;
    7388          13 :     bDefineMode = false;
    7389             : 
    7390          13 :     if (!oListGrpName.empty())
    7391             :     {
    7392           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7393             :         {
    7394           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7395           3 :             if (poLayer)
    7396             :             {
    7397           3 :                 int nNewLayerCDFID = -1;
    7398           3 :                 status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
    7399             :                                      &nNewLayerCDFID);
    7400           3 :                 NCDF_ERR(status);
    7401           3 :                 poLayer->SetCDFID(nNewLayerCDFID);
    7402             :             }
    7403             :         }
    7404             :     }
    7405             :     else
    7406             :     {
    7407          22 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7408             :         {
    7409          11 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7410          11 :             if (poLayer)
    7411          11 :                 poLayer->SetCDFID(cdfid);
    7412             :         }
    7413             :     }
    7414             : 
    7415          13 :     return true;
    7416             : }
    7417             : 
    7418             : #ifdef ENABLE_NCDUMP
    7419             : 
    7420             : /************************************************************************/
    7421             : /*                    netCDFDatasetCreateTempFile()                     */
    7422             : /************************************************************************/
    7423             : 
    7424             : /* Create a netCDF file from a text dump (format of ncdump) */
    7425             : /* Mostly to easy fuzzing of the driver, while still generating valid */
    7426             : /* netCDF files. */
    7427             : /* Note: not all data types are supported ! */
    7428           4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
    7429             :                                  const char *pszTmpFilename, VSILFILE *fpSrc)
    7430             : {
    7431           4 :     CPL_IGNORE_RET_VAL(eFormat);
    7432           4 :     int nCreateMode = NC_CLOBBER;
    7433           4 :     if (eFormat == NCDF_FORMAT_NC4)
    7434           1 :         nCreateMode |= NC_NETCDF4;
    7435           3 :     else if (eFormat == NCDF_FORMAT_NC4C)
    7436           0 :         nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
    7437           4 :     int nCdfId = -1;
    7438           4 :     int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
    7439           4 :     if (status != NC_NOERR)
    7440             :     {
    7441           0 :         return false;
    7442             :     }
    7443           4 :     VSIFSeekL(fpSrc, 0, SEEK_SET);
    7444             :     const char *pszLine;
    7445           4 :     constexpr int SECTION_NONE = 0;
    7446           4 :     constexpr int SECTION_DIMENSIONS = 1;
    7447           4 :     constexpr int SECTION_VARIABLES = 2;
    7448           4 :     constexpr int SECTION_DATA = 3;
    7449           4 :     int nActiveSection = SECTION_NONE;
    7450           8 :     std::map<CPLString, int> oMapDimToId;
    7451           8 :     std::map<int, int> oMapDimIdToDimLen;
    7452           8 :     std::map<CPLString, int> oMapVarToId;
    7453           8 :     std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
    7454           8 :     std::map<int, int> oMapVarIdToType;
    7455           4 :     std::set<CPLString> oSetAttrDefined;
    7456           4 :     oMapVarToId[""] = -1;
    7457           4 :     size_t nTotalVarSize = 0;
    7458         208 :     while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
    7459             :     {
    7460         204 :         if (STARTS_WITH(pszLine, "dimensions:") &&
    7461             :             nActiveSection == SECTION_NONE)
    7462             :         {
    7463           4 :             nActiveSection = SECTION_DIMENSIONS;
    7464             :         }
    7465         200 :         else if (STARTS_WITH(pszLine, "variables:") &&
    7466             :                  nActiveSection == SECTION_DIMENSIONS)
    7467             :         {
    7468           4 :             nActiveSection = SECTION_VARIABLES;
    7469             :         }
    7470         196 :         else if (STARTS_WITH(pszLine, "data:") &&
    7471             :                  nActiveSection == SECTION_VARIABLES)
    7472             :         {
    7473           4 :             nActiveSection = SECTION_DATA;
    7474           4 :             status = nc_enddef(nCdfId);
    7475           4 :             if (status != NC_NOERR)
    7476             :             {
    7477           0 :                 CPLDebug("netCDF", "nc_enddef() failed: %s",
    7478             :                          nc_strerror(status));
    7479             :             }
    7480             :         }
    7481         192 :         else if (nActiveSection == SECTION_DIMENSIONS)
    7482             :         {
    7483             :             const CPLStringList aosTokens(
    7484           9 :                 CSLTokenizeString2(pszLine, " \t=;", 0));
    7485           9 :             if (aosTokens.size() == 2)
    7486             :             {
    7487           9 :                 const char *pszDimName = aosTokens[0];
    7488           9 :                 bool bValidName = true;
    7489           9 :                 if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
    7490             :                 {
    7491             :                     // This is an internal netcdf prefix. Using it may
    7492             :                     // cause memory leaks.
    7493           0 :                     bValidName = false;
    7494             :                 }
    7495           9 :                 if (!bValidName)
    7496             :                 {
    7497           0 :                     CPLDebug("netCDF",
    7498             :                              "nc_def_dim(%s) failed: invalid name found",
    7499             :                              pszDimName);
    7500           0 :                     continue;
    7501             :                 }
    7502             : 
    7503             :                 const bool bIsASCII =
    7504           9 :                     CPLIsASCII(pszDimName, static_cast<size_t>(-1));
    7505           9 :                 if (!bIsASCII)
    7506             :                 {
    7507             :                     // Workaround https://github.com/Unidata/netcdf-c/pull/450
    7508           0 :                     CPLDebug("netCDF",
    7509             :                              "nc_def_dim(%s) failed: rejected because "
    7510             :                              "of non-ASCII characters",
    7511             :                              pszDimName);
    7512           0 :                     continue;
    7513             :                 }
    7514           9 :                 int nDimSize = EQUAL(aosTokens[1], "UNLIMITED")
    7515             :                                    ? NC_UNLIMITED
    7516           9 :                                    : atoi(aosTokens[1]);
    7517           9 :                 if (nDimSize >= 1000)
    7518           1 :                     nDimSize = 1000;  // to avoid very long processing
    7519           9 :                 if (nDimSize >= 0)
    7520             :                 {
    7521           9 :                     int nDimId = -1;
    7522           9 :                     status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
    7523           9 :                     if (status != NC_NOERR)
    7524             :                     {
    7525           0 :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
    7526             :                                  pszDimName, nDimSize, nc_strerror(status));
    7527             :                     }
    7528             :                     else
    7529             :                     {
    7530             : #ifdef DEBUG_VERBOSE
    7531             :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
    7532             :                                  pszDimName, nDimSize, pszLine);
    7533             : #endif
    7534           9 :                         oMapDimToId[pszDimName] = nDimId;
    7535           9 :                         oMapDimIdToDimLen[nDimId] = nDimSize;
    7536             :                     }
    7537             :                 }
    7538             :             }
    7539             :         }
    7540         183 :         else if (nActiveSection == SECTION_VARIABLES)
    7541             :         {
    7542         390 :             while (*pszLine == ' ' || *pszLine == '\t')
    7543         249 :                 pszLine++;
    7544         141 :             const char *pszColumn = strchr(pszLine, ':');
    7545         141 :             const char *pszEqual = strchr(pszLine, '=');
    7546         141 :             if (pszColumn == nullptr)
    7547             :             {
    7548             :                 const CPLStringList aosTokens(
    7549          21 :                     CSLTokenizeString2(pszLine, " \t=(),;", 0));
    7550          21 :                 if (aosTokens.size() >= 2)
    7551             :                 {
    7552          17 :                     const char *pszVarName = aosTokens[1];
    7553          17 :                     bool bValidName = true;
    7554          17 :                     if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
    7555             :                     {
    7556             :                         // This is an internal netcdf prefix. Using it may
    7557             :                         // cause memory leaks.
    7558           0 :                         bValidName = false;
    7559             :                     }
    7560         138 :                     for (int i = 0; pszVarName[i]; i++)
    7561             :                     {
    7562         121 :                         if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
    7563          28 :                               (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
    7564           9 :                               (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
    7565           6 :                               pszVarName[i] == '_'))
    7566             :                         {
    7567           0 :                             bValidName = false;
    7568             :                         }
    7569             :                     }
    7570          17 :                     if (!bValidName)
    7571             :                     {
    7572           0 :                         CPLDebug(
    7573             :                             "netCDF",
    7574             :                             "nc_def_var(%s) failed: illegal character found",
    7575             :                             pszVarName);
    7576           0 :                         continue;
    7577             :                     }
    7578          17 :                     if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
    7579             :                     {
    7580           0 :                         CPLDebug("netCDF",
    7581             :                                  "nc_def_var(%s) failed: already defined",
    7582             :                                  pszVarName);
    7583           0 :                         continue;
    7584             :                     }
    7585          17 :                     const char *pszVarType = aosTokens[0];
    7586          17 :                     int nc_datatype = NC_BYTE;
    7587          17 :                     size_t nDataTypeSize = 1;
    7588          17 :                     if (EQUAL(pszVarType, "char"))
    7589             :                     {
    7590           6 :                         nc_datatype = NC_CHAR;
    7591           6 :                         nDataTypeSize = 1;
    7592             :                     }
    7593          11 :                     else if (EQUAL(pszVarType, "byte"))
    7594             :                     {
    7595           3 :                         nc_datatype = NC_BYTE;
    7596           3 :                         nDataTypeSize = 1;
    7597             :                     }
    7598           8 :                     else if (EQUAL(pszVarType, "short"))
    7599             :                     {
    7600           0 :                         nc_datatype = NC_SHORT;
    7601           0 :                         nDataTypeSize = 2;
    7602             :                     }
    7603           8 :                     else if (EQUAL(pszVarType, "int"))
    7604             :                     {
    7605           0 :                         nc_datatype = NC_INT;
    7606           0 :                         nDataTypeSize = 4;
    7607             :                     }
    7608           8 :                     else if (EQUAL(pszVarType, "float"))
    7609             :                     {
    7610           0 :                         nc_datatype = NC_FLOAT;
    7611           0 :                         nDataTypeSize = 4;
    7612             :                     }
    7613           8 :                     else if (EQUAL(pszVarType, "double"))
    7614             :                     {
    7615           8 :                         nc_datatype = NC_DOUBLE;
    7616           8 :                         nDataTypeSize = 8;
    7617             :                     }
    7618           0 :                     else if (EQUAL(pszVarType, "ubyte"))
    7619             :                     {
    7620           0 :                         nc_datatype = NC_UBYTE;
    7621           0 :                         nDataTypeSize = 1;
    7622             :                     }
    7623           0 :                     else if (EQUAL(pszVarType, "ushort"))
    7624             :                     {
    7625           0 :                         nc_datatype = NC_USHORT;
    7626           0 :                         nDataTypeSize = 2;
    7627             :                     }
    7628           0 :                     else if (EQUAL(pszVarType, "uint"))
    7629             :                     {
    7630           0 :                         nc_datatype = NC_UINT;
    7631           0 :                         nDataTypeSize = 4;
    7632             :                     }
    7633           0 :                     else if (EQUAL(pszVarType, "int64"))
    7634             :                     {
    7635           0 :                         nc_datatype = NC_INT64;
    7636           0 :                         nDataTypeSize = 8;
    7637             :                     }
    7638           0 :                     else if (EQUAL(pszVarType, "uint64"))
    7639             :                     {
    7640           0 :                         nc_datatype = NC_UINT64;
    7641           0 :                         nDataTypeSize = 8;
    7642             :                     }
    7643             : 
    7644          17 :                     int nDims = aosTokens.size() - 2;
    7645          17 :                     if (nDims >= 32)
    7646             :                     {
    7647             :                         // The number of dimensions in a netCDFv4 file is
    7648             :                         // limited by #define H5S_MAX_RANK    32
    7649             :                         // but libnetcdf doesn't check that...
    7650           0 :                         CPLDebug("netCDF",
    7651             :                                  "nc_def_var(%s) failed: too many dimensions",
    7652             :                                  pszVarName);
    7653           0 :                         continue;
    7654             :                     }
    7655          17 :                     std::vector<int> aoDimIds;
    7656          17 :                     bool bFailed = false;
    7657          17 :                     size_t nSize = 1;
    7658          35 :                     for (int i = 0; i < nDims; i++)
    7659             :                     {
    7660          18 :                         const char *pszDimName = aosTokens[2 + i];
    7661          18 :                         if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
    7662             :                         {
    7663           0 :                             bFailed = true;
    7664           0 :                             break;
    7665             :                         }
    7666          18 :                         const int nDimId = oMapDimToId[pszDimName];
    7667          18 :                         aoDimIds.push_back(nDimId);
    7668             : 
    7669          18 :                         const size_t nDimSize = oMapDimIdToDimLen[nDimId];
    7670          18 :                         if (nDimSize != 0)
    7671             :                         {
    7672          18 :                             if (nSize >
    7673          18 :                                 std::numeric_limits<size_t>::max() / nDimSize)
    7674             :                             {
    7675           0 :                                 bFailed = true;
    7676           0 :                                 break;
    7677             :                             }
    7678             :                             else
    7679             :                             {
    7680          18 :                                 nSize *= nDimSize;
    7681             :                             }
    7682             :                         }
    7683             :                     }
    7684          17 :                     if (bFailed)
    7685             :                     {
    7686           0 :                         CPLDebug("netCDF",
    7687             :                                  "nc_def_var(%s) failed: unknown dimension(s)",
    7688             :                                  pszVarName);
    7689           0 :                         continue;
    7690             :                     }
    7691          17 :                     if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
    7692             :                     {
    7693           0 :                         CPLDebug("netCDF",
    7694             :                                  "nc_def_var(%s) failed: too large data",
    7695             :                                  pszVarName);
    7696           0 :                         continue;
    7697             :                     }
    7698          17 :                     if (nTotalVarSize >
    7699          34 :                             std::numeric_limits<size_t>::max() - nSize ||
    7700          17 :                         nTotalVarSize + nSize > 100 * 1024 * 1024)
    7701             :                     {
    7702           0 :                         CPLDebug("netCDF",
    7703             :                                  "nc_def_var(%s) failed: too large data",
    7704             :                                  pszVarName);
    7705           0 :                         continue;
    7706             :                     }
    7707          17 :                     nTotalVarSize += nSize;
    7708             : 
    7709          17 :                     int nVarId = -1;
    7710             :                     status =
    7711          30 :                         nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
    7712          13 :                                    (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
    7713          17 :                     if (status != NC_NOERR)
    7714             :                     {
    7715           0 :                         CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
    7716             :                                  pszVarName, nc_strerror(status));
    7717             :                     }
    7718             :                     else
    7719             :                     {
    7720             : #ifdef DEBUG_VERBOSE
    7721             :                         CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
    7722             :                                  pszVarName, pszLine);
    7723             : #endif
    7724          17 :                         oMapVarToId[pszVarName] = nVarId;
    7725          17 :                         oMapVarIdToType[nVarId] = nc_datatype;
    7726          17 :                         oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
    7727             :                     }
    7728             :                 }
    7729             :             }
    7730         120 :             else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
    7731             :             {
    7732         116 :                 CPLString osVarName(pszLine, pszColumn - pszLine);
    7733         116 :                 CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
    7734         116 :                 osAttrName.Trim();
    7735         116 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7736             :                 {
    7737           0 :                     CPLDebug("netCDF",
    7738             :                              "nc_put_att(%s:%s) failed: "
    7739             :                              "no corresponding variable",
    7740             :                              osVarName.c_str(), osAttrName.c_str());
    7741           0 :                     continue;
    7742             :                 }
    7743         116 :                 bool bValidName = true;
    7744        1743 :                 for (size_t i = 0; i < osAttrName.size(); i++)
    7745             :                 {
    7746        1865 :                     if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
    7747         238 :                           (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
    7748         158 :                           (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
    7749         158 :                           osAttrName[i] == '_'))
    7750             :                     {
    7751           0 :                         bValidName = false;
    7752             :                     }
    7753             :                 }
    7754         116 :                 if (!bValidName)
    7755             :                 {
    7756           0 :                     CPLDebug(
    7757             :                         "netCDF",
    7758             :                         "nc_put_att(%s:%s) failed: illegal character found",
    7759             :                         osVarName.c_str(), osAttrName.c_str());
    7760           0 :                     continue;
    7761             :                 }
    7762         116 :                 if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
    7763         232 :                     oSetAttrDefined.end())
    7764             :                 {
    7765           0 :                     CPLDebug("netCDF",
    7766             :                              "nc_put_att(%s:%s) failed: already defined",
    7767             :                              osVarName.c_str(), osAttrName.c_str());
    7768           0 :                     continue;
    7769             :                 }
    7770             : 
    7771         116 :                 const int nVarId = oMapVarToId[osVarName];
    7772         116 :                 const char *pszValue = pszEqual + 1;
    7773         232 :                 while (*pszValue == ' ')
    7774         116 :                     pszValue++;
    7775             : 
    7776         116 :                 status = NC_EBADTYPE;
    7777         116 :                 if (*pszValue == '"')
    7778             :                 {
    7779             :                     // For _FillValue, the attribute type should match
    7780             :                     // the variable type. Leaks memory with NC4 otherwise
    7781          74 :                     if (osAttrName == "_FillValue")
    7782             :                     {
    7783           0 :                         CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7784             :                                  osVarName.c_str(), osAttrName.c_str(),
    7785             :                                  nc_strerror(status));
    7786           0 :                         continue;
    7787             :                     }
    7788             : 
    7789             :                     // Unquote and unescape string value
    7790          74 :                     CPLString osVal(pszValue + 1);
    7791         222 :                     while (!osVal.empty())
    7792             :                     {
    7793         222 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7794             :                         {
    7795         148 :                             osVal.pop_back();
    7796             :                         }
    7797          74 :                         else if (osVal.back() == '"')
    7798             :                         {
    7799          74 :                             osVal.pop_back();
    7800          74 :                             break;
    7801             :                         }
    7802             :                         else
    7803             :                         {
    7804           0 :                             break;
    7805             :                         }
    7806             :                     }
    7807          74 :                     osVal.replaceAll("\\\"", '"');
    7808          74 :                     status = nc_put_att_text(nCdfId, nVarId, osAttrName,
    7809             :                                              osVal.size(), osVal.c_str());
    7810             :                 }
    7811             :                 else
    7812             :                 {
    7813          84 :                     CPLString osVal(pszValue);
    7814         126 :                     while (!osVal.empty())
    7815             :                     {
    7816         126 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7817             :                         {
    7818          84 :                             osVal.pop_back();
    7819             :                         }
    7820             :                         else
    7821             :                         {
    7822          42 :                             break;
    7823             :                         }
    7824             :                     }
    7825          42 :                     int nc_datatype = -1;
    7826          42 :                     if (!osVal.empty() && osVal.back() == 'b')
    7827             :                     {
    7828           3 :                         nc_datatype = NC_BYTE;
    7829           3 :                         osVal.pop_back();
    7830             :                     }
    7831          39 :                     else if (!osVal.empty() && osVal.back() == 's')
    7832             :                     {
    7833           3 :                         nc_datatype = NC_SHORT;
    7834           3 :                         osVal.pop_back();
    7835             :                     }
    7836          42 :                     if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
    7837             :                     {
    7838           7 :                         if (nc_datatype < 0)
    7839           4 :                             nc_datatype = NC_INT;
    7840             :                     }
    7841          35 :                     else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
    7842             :                     {
    7843          32 :                         nc_datatype = NC_DOUBLE;
    7844             :                     }
    7845             :                     else
    7846             :                     {
    7847           3 :                         nc_datatype = -1;
    7848             :                     }
    7849             : 
    7850             :                     // For _FillValue, check that the attribute type matches
    7851             :                     // the variable type. Leaks memory with NC4 otherwise
    7852          42 :                     if (osAttrName == "_FillValue")
    7853             :                     {
    7854           6 :                         if (nVarId < 0 ||
    7855           3 :                             nc_datatype != oMapVarIdToType[nVarId])
    7856             :                         {
    7857           0 :                             nc_datatype = -1;
    7858             :                         }
    7859             :                     }
    7860             : 
    7861          42 :                     if (nc_datatype == NC_BYTE)
    7862             :                     {
    7863             :                         signed char chVal =
    7864           3 :                             static_cast<signed char>(atoi(osVal));
    7865           3 :                         status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
    7866             :                                                   NC_BYTE, 1, &chVal);
    7867             :                     }
    7868          39 :                     else if (nc_datatype == NC_SHORT)
    7869             :                     {
    7870           0 :                         short nVal = static_cast<short>(atoi(osVal));
    7871           0 :                         status = nc_put_att_short(nCdfId, nVarId, osAttrName,
    7872             :                                                   NC_SHORT, 1, &nVal);
    7873             :                     }
    7874          39 :                     else if (nc_datatype == NC_INT)
    7875             :                     {
    7876           4 :                         int nVal = static_cast<int>(atoi(osVal));
    7877           4 :                         status = nc_put_att_int(nCdfId, nVarId, osAttrName,
    7878             :                                                 NC_INT, 1, &nVal);
    7879             :                     }
    7880          35 :                     else if (nc_datatype == NC_DOUBLE)
    7881             :                     {
    7882          32 :                         double dfVal = CPLAtof(osVal);
    7883          32 :                         status = nc_put_att_double(nCdfId, nVarId, osAttrName,
    7884             :                                                    NC_DOUBLE, 1, &dfVal);
    7885             :                     }
    7886             :                 }
    7887         116 :                 if (status != NC_NOERR)
    7888             :                 {
    7889           3 :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7890             :                              osVarName.c_str(), osAttrName.c_str(),
    7891             :                              nc_strerror(status));
    7892             :                 }
    7893             :                 else
    7894             :                 {
    7895         113 :                     oSetAttrDefined.insert(osVarName + ":" + osAttrName);
    7896             : #ifdef DEBUG_VERBOSE
    7897             :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
    7898             :                              osVarName.c_str(), osAttrName.c_str(), pszLine);
    7899             : #endif
    7900             :                 }
    7901             :             }
    7902             :         }
    7903          42 :         else if (nActiveSection == SECTION_DATA)
    7904             :         {
    7905          55 :             while (*pszLine == ' ' || *pszLine == '\t')
    7906          17 :                 pszLine++;
    7907          38 :             const char *pszEqual = strchr(pszLine, '=');
    7908          38 :             if (pszEqual)
    7909             :             {
    7910          17 :                 CPLString osVarName(pszLine, pszEqual - pszLine);
    7911          17 :                 osVarName.Trim();
    7912          17 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7913           0 :                     continue;
    7914          17 :                 const int nVarId = oMapVarToId[osVarName];
    7915          17 :                 CPLString osAccVal(pszEqual + 1);
    7916          17 :                 osAccVal.Trim();
    7917         153 :                 while (osAccVal.empty() || osAccVal.back() != ';')
    7918             :                 {
    7919         136 :                     pszLine = CPLReadLineL(fpSrc);
    7920         136 :                     if (pszLine == nullptr)
    7921           0 :                         break;
    7922         272 :                     CPLString osVal(pszLine);
    7923         136 :                     osVal.Trim();
    7924         136 :                     osAccVal += osVal;
    7925             :                 }
    7926          17 :                 if (pszLine == nullptr)
    7927           0 :                     break;
    7928          17 :                 osAccVal.pop_back();
    7929             : 
    7930             :                 const std::vector<int> aoDimIds =
    7931          34 :                     oMapVarIdToVectorOfDimId[nVarId];
    7932          17 :                 size_t nSize = 1;
    7933          34 :                 std::vector<size_t> aoStart, aoEdge;
    7934          17 :                 aoStart.resize(aoDimIds.size());
    7935          17 :                 aoEdge.resize(aoDimIds.size());
    7936          35 :                 for (size_t i = 0; i < aoDimIds.size(); ++i)
    7937             :                 {
    7938          18 :                     const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
    7939          36 :                     if (nDimSize != 0 &&
    7940          18 :                         nSize > std::numeric_limits<size_t>::max() / nDimSize)
    7941             :                     {
    7942           0 :                         nSize = 0;
    7943             :                     }
    7944             :                     else
    7945             :                     {
    7946          18 :                         nSize *= nDimSize;
    7947             :                     }
    7948          18 :                     aoStart[i] = 0;
    7949          18 :                     aoEdge[i] = nDimSize;
    7950             :                 }
    7951             : 
    7952          17 :                 status = NC_EBADTYPE;
    7953          17 :                 if (nSize == 0)
    7954             :                 {
    7955             :                     // Might happen with an unlimited dimension
    7956             :                 }
    7957          17 :                 else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
    7958             :                 {
    7959           8 :                     if (!aoStart.empty())
    7960             :                     {
    7961             :                         const CPLStringList aosTokens(
    7962          16 :                             CSLTokenizeString2(osAccVal, " ,;", 0));
    7963           8 :                         size_t nTokens = aosTokens.size();
    7964           8 :                         if (nTokens >= nSize)
    7965             :                         {
    7966             :                             double *padfVals = static_cast<double *>(
    7967           8 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
    7968           8 :                             if (padfVals)
    7969             :                             {
    7970         132 :                                 for (size_t i = 0; i < nSize; i++)
    7971             :                                 {
    7972         124 :                                     padfVals[i] = CPLAtof(aosTokens[i]);
    7973             :                                 }
    7974           8 :                                 status = nc_put_vara_double(
    7975           8 :                                     nCdfId, nVarId, &aoStart[0], &aoEdge[0],
    7976             :                                     padfVals);
    7977           8 :                                 VSIFree(padfVals);
    7978             :                             }
    7979             :                         }
    7980             :                     }
    7981             :                 }
    7982           9 :                 else if (oMapVarIdToType[nVarId] == NC_BYTE)
    7983             :                 {
    7984           3 :                     if (!aoStart.empty())
    7985             :                     {
    7986             :                         const CPLStringList aosTokens(
    7987           6 :                             CSLTokenizeString2(osAccVal, " ,;", 0));
    7988           3 :                         size_t nTokens = aosTokens.size();
    7989           3 :                         if (nTokens >= nSize)
    7990             :                         {
    7991             :                             signed char *panVals = static_cast<signed char *>(
    7992           3 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
    7993           3 :                             if (panVals)
    7994             :                             {
    7995        1203 :                                 for (size_t i = 0; i < nSize; i++)
    7996             :                                 {
    7997        1200 :                                     panVals[i] = static_cast<signed char>(
    7998        1200 :                                         atoi(aosTokens[i]));
    7999             :                                 }
    8000           3 :                                 status = nc_put_vara_schar(nCdfId, nVarId,
    8001           3 :                                                            &aoStart[0],
    8002           3 :                                                            &aoEdge[0], panVals);
    8003           3 :                                 VSIFree(panVals);
    8004             :                             }
    8005             :                         }
    8006             :                     }
    8007             :                 }
    8008           6 :                 else if (oMapVarIdToType[nVarId] == NC_CHAR)
    8009             :                 {
    8010           6 :                     if (aoStart.size() == 2)
    8011             :                     {
    8012           4 :                         std::vector<CPLString> aoStrings;
    8013           2 :                         bool bInString = false;
    8014           4 :                         CPLString osCurString;
    8015         935 :                         for (size_t i = 0; i < osAccVal.size();)
    8016             :                         {
    8017         933 :                             if (!bInString)
    8018             :                             {
    8019           8 :                                 if (osAccVal[i] == '"')
    8020             :                                 {
    8021           4 :                                     bInString = true;
    8022           4 :                                     osCurString.clear();
    8023             :                                 }
    8024           8 :                                 i++;
    8025             :                             }
    8026         926 :                             else if (osAccVal[i] == '\\' &&
    8027         926 :                                      i + 1 < osAccVal.size() &&
    8028           1 :                                      osAccVal[i + 1] == '"')
    8029             :                             {
    8030           1 :                                 osCurString += '"';
    8031           1 :                                 i += 2;
    8032             :                             }
    8033         924 :                             else if (osAccVal[i] == '"')
    8034             :                             {
    8035           4 :                                 aoStrings.push_back(osCurString);
    8036           4 :                                 osCurString.clear();
    8037           4 :                                 bInString = false;
    8038           4 :                                 i++;
    8039             :                             }
    8040             :                             else
    8041             :                             {
    8042         920 :                                 osCurString += osAccVal[i];
    8043         920 :                                 i++;
    8044             :                             }
    8045             :                         }
    8046           2 :                         const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
    8047           2 :                         const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
    8048           2 :                         size_t nIters = aoStrings.size();
    8049           2 :                         if (nIters > nRecords)
    8050           0 :                             nIters = nRecords;
    8051           6 :                         for (size_t i = 0; i < nIters; i++)
    8052             :                         {
    8053             :                             size_t anIndex[2];
    8054           4 :                             anIndex[0] = i;
    8055           4 :                             anIndex[1] = 0;
    8056             :                             size_t anCount[2];
    8057           4 :                             anCount[0] = 1;
    8058           4 :                             anCount[1] = aoStrings[i].size();
    8059           4 :                             if (anCount[1] > nWidth)
    8060           0 :                                 anCount[1] = nWidth;
    8061             :                             status =
    8062           4 :                                 nc_put_vara_text(nCdfId, nVarId, anIndex,
    8063           4 :                                                  anCount, aoStrings[i].c_str());
    8064           4 :                             if (status != NC_NOERR)
    8065           0 :                                 break;
    8066             :                         }
    8067             :                     }
    8068             :                 }
    8069          17 :                 if (status != NC_NOERR)
    8070             :                 {
    8071           4 :                     CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
    8072             :                              osVarName.c_str(), nc_strerror(status));
    8073             :                 }
    8074             :             }
    8075             :         }
    8076             :     }
    8077             : 
    8078           4 :     GDAL_nc_close(nCdfId);
    8079           4 :     return true;
    8080             : }
    8081             : 
    8082             : #endif  // ENABLE_NCDUMP
    8083             : 
    8084             : /************************************************************************/
    8085             : /*                                Open()                                */
    8086             : /************************************************************************/
    8087             : 
    8088         795 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
    8089             : 
    8090             : {
    8091             : #ifdef NCDF_DEBUG
    8092             :     CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
    8093             :              poOpenInfo->pszFilename);
    8094             : #endif
    8095             : 
    8096             :     // Does this appear to be a netcdf file?
    8097         795 :     NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
    8098         795 :     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8099             :     {
    8100         731 :         eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
    8101             : #ifdef NCDF_DEBUG
    8102             :         CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
    8103             : #endif
    8104             :         // Note: not calling Identify() directly, because we want the file type.
    8105             :         // Only support NCDF_FORMAT* formats.
    8106         731 :         if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
    8107           2 :             NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
    8108             :         {
    8109             :             // ok
    8110             :         }
    8111           2 :         else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
    8112           0 :                  poOpenInfo->IsSingleAllowedDriver("netCDF"))
    8113             :         {
    8114             :             // ok
    8115             :         }
    8116             :         else
    8117             :         {
    8118           2 :             return nullptr;
    8119             :         }
    8120             :     }
    8121             :     else
    8122             :     {
    8123             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    8124             :         // We don't necessarily want to catch bugs in libnetcdf ...
    8125             :         if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
    8126             :         {
    8127             :             return nullptr;
    8128             :         }
    8129             : #endif
    8130             :     }
    8131             : 
    8132         793 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
    8133             :     {
    8134         279 :         return OpenMultiDim(poOpenInfo);
    8135             :     }
    8136             : 
    8137        1028 :     CPLMutexHolderD(&hNCMutex);
    8138             : 
    8139         514 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    8140             :     // GDALDataset own mutex.
    8141         514 :     netCDFDataset *poDS = new netCDFDataset();
    8142         514 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    8143         514 :     CPLAcquireMutex(hNCMutex, 1000.0);
    8144             : 
    8145         514 :     poDS->SetDescription(poOpenInfo->pszFilename);
    8146             : 
    8147             :     // Check if filename start with NETCDF: tag.
    8148         514 :     bool bTreatAsSubdataset = false;
    8149        1028 :     CPLString osSubdatasetName;
    8150             : 
    8151             : #ifdef ENABLE_NCDUMP
    8152         514 :     const char *pszHeader =
    8153             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    8154         514 :     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
    8155           3 :         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
    8156             :     {
    8157             :         // By default create a temporary file that will be destroyed,
    8158             :         // unless NETCDF_TMP_FILE is defined. Can be useful to see which
    8159             :         // netCDF file has been generated from a potential fuzzed input.
    8160           3 :         poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
    8161           3 :         if (poDS->osFilename.empty())
    8162             :         {
    8163           3 :             poDS->bFileToDestroyAtClosing = true;
    8164           3 :             poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
    8165             :         }
    8166           3 :         if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
    8167             :                                          poOpenInfo->fpL))
    8168             :         {
    8169           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8170             :             // deadlock with GDALDataset own mutex.
    8171           0 :             delete poDS;
    8172           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8173           0 :             return nullptr;
    8174             :         }
    8175           3 :         bTreatAsSubdataset = false;
    8176           3 :         poDS->eFormat = eTmpFormat;
    8177             :     }
    8178             :     else
    8179             : #endif
    8180             : 
    8181         511 :         if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8182             :     {
    8183             :         GDALSubdatasetInfoH hInfo =
    8184          64 :             GDALGetSubdatasetInfo(poOpenInfo->pszFilename);
    8185          64 :         if (hInfo)
    8186             :         {
    8187          64 :             char *pszPath = GDALSubdatasetInfoGetPathComponent(hInfo);
    8188          64 :             poDS->osFilename = pszPath;
    8189          64 :             CPLFree(pszPath);
    8190             : 
    8191             :             char *pszSubdataset =
    8192          64 :                 GDALSubdatasetInfoGetSubdatasetComponent(hInfo);
    8193          64 :             if (pszSubdataset && pszSubdataset[0] != '\0')
    8194             :             {
    8195          64 :                 osSubdatasetName = pszSubdataset;
    8196          64 :                 bTreatAsSubdataset = true;
    8197             :             }
    8198             :             else
    8199             :             {
    8200           0 :                 osSubdatasetName = "";
    8201           0 :                 bTreatAsSubdataset = false;
    8202             :             }
    8203          64 :             CPLFree(pszSubdataset);
    8204             : 
    8205          64 :             GDALDestroySubdatasetInfo(hInfo);
    8206             :         }
    8207             : 
    8208         128 :         if (!STARTS_WITH(poDS->osFilename, "http://") &&
    8209          64 :             !STARTS_WITH(poDS->osFilename, "https://"))
    8210             :         {
    8211             :             // Identify Format from real file, with bCheckExt=FALSE.
    8212             :             auto poOpenInfo2 = std::make_unique<GDALOpenInfo>(
    8213          64 :                 poDS->osFilename.c_str(), GA_ReadOnly);
    8214          64 :             poDS->eFormat = netCDFIdentifyFormat(poOpenInfo2.get(),
    8215             :                                                  /* bCheckExt = */ false);
    8216          64 :             if (NCDF_FORMAT_NONE == poDS->eFormat ||
    8217          64 :                 NCDF_FORMAT_UNKNOWN == poDS->eFormat)
    8218             :             {
    8219           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8220             :                 // deadlock with GDALDataset own mutex.
    8221           0 :                 delete poDS;
    8222           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    8223           0 :                 return nullptr;
    8224             :             }
    8225             :         }
    8226             :     }
    8227             :     else
    8228             :     {
    8229         447 :         poDS->osFilename = poOpenInfo->pszFilename;
    8230         447 :         bTreatAsSubdataset = false;
    8231         447 :         poDS->eFormat = eTmpFormat;
    8232             :     }
    8233             : 
    8234             : // Try opening the dataset.
    8235             : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
    8236             :     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
    8237             :              poDS->osFilename.c_str());
    8238             : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
    8239             :     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
    8240             : #endif
    8241         514 :     int cdfid = -1;
    8242         514 :     const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
    8243             :                           ? NC_WRITE
    8244             :                           : NC_NOWRITE;
    8245        1028 :     CPLString osFilenameForNCOpen(poDS->osFilename);
    8246             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    8247             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    8248             :     {
    8249             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    8250             :         osFilenameForNCOpen = pszTemp;
    8251             :         CPLFree(pszTemp);
    8252             :     }
    8253             : #endif
    8254         514 :     int status2 = -1;
    8255             : 
    8256             : #ifdef ENABLE_UFFD
    8257         514 :     cpl_uffd_context *pCtx = nullptr;
    8258             : #endif
    8259             : 
    8260         529 :     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
    8261          15 :         poOpenInfo->eAccess == GA_ReadOnly)
    8262             :     {
    8263          15 :         vsi_l_offset nLength = 0;
    8264          15 :         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
    8265          15 :         if (poDS->fpVSIMEM)
    8266             :         {
    8267             :             // We assume that the file will not be modified. If it is, then
    8268             :             // pabyBuffer might become invalid.
    8269             :             GByte *pabyBuffer =
    8270          15 :                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
    8271          15 :             if (pabyBuffer)
    8272             :             {
    8273          15 :                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
    8274             :                                       nMode, static_cast<size_t>(nLength),
    8275             :                                       pabyBuffer, &cdfid);
    8276             :             }
    8277             :         }
    8278             :     }
    8279             :     else
    8280             :     {
    8281             :         const bool bVsiFile =
    8282         499 :             !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
    8283             : #ifdef ENABLE_UFFD
    8284         499 :         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
    8285         499 :         void *pVma = nullptr;
    8286         499 :         uint64_t nVmaSize = 0;
    8287             : 
    8288         499 :         if (bVsiFile)
    8289             :         {
    8290           2 :             if (bReadOnly)
    8291             :             {
    8292           2 :                 if (CPLIsUserFaultMappingSupported())
    8293             :                 {
    8294           2 :                     pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
    8295             :                                                      &nVmaSize);
    8296             :                 }
    8297             :                 else
    8298             :                 {
    8299           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    8300             :                              "Opening a /vsi file with the netCDF driver "
    8301             :                              "requires Linux userfaultfd to be available. "
    8302             :                              "If running from Docker, "
    8303             :                              "--security-opt seccomp=unconfined might be "
    8304             :                              "needed.%s",
    8305           0 :                              ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8306           0 :                                poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8307           0 :                               GDALGetDriverByName("HDF5"))
    8308             :                                  ? " Or you may set the GDAL_SKIP=netCDF "
    8309             :                                    "configuration option to force the use of "
    8310             :                                    "the HDF5 driver."
    8311             :                                  : "");
    8312             :                 }
    8313             :             }
    8314             :             else
    8315             :             {
    8316           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    8317             :                          "Opening a /vsi file with the netCDF driver is only "
    8318             :                          "supported in read-only mode");
    8319             :             }
    8320             :         }
    8321         499 :         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
    8322             :         {
    8323             :             // netCDF code, at least for netCDF 4.7.0, is confused by filenames
    8324             :             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
    8325             :             // final part
    8326           2 :             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
    8327             :                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);
    8328             :         }
    8329             :         else
    8330         497 :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8331             : #else
    8332             :         if (bVsiFile)
    8333             :         {
    8334             :             CPLError(
    8335             :                 CE_Failure, CPLE_AppDefined,
    8336             :                 "Opening a /vsi file with the netCDF driver requires Linux "
    8337             :                 "userfaultfd to be available.%s",
    8338             :                 ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8339             :                   poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8340             :                  GDALGetDriverByName("HDF5"))
    8341             :                     ? " Or you may set the GDAL_SKIP=netCDF "
    8342             :                       "configuration option to force the use of the HDF5 "
    8343             :                       "driver."
    8344             :                     : "");
    8345             :             status2 = NC_EIO;
    8346             :         }
    8347             :         else
    8348             :         {
    8349             :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8350             :         }
    8351             : #endif
    8352             :     }
    8353         514 :     if (status2 != NC_NOERR)
    8354             :     {
    8355             : #ifdef NCDF_DEBUG
    8356             :         CPLDebug("GDAL_netCDF", "error opening");
    8357             : #endif
    8358           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8359             :         // with GDALDataset own mutex.
    8360           0 :         delete poDS;
    8361           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8362           0 :         return nullptr;
    8363             :     }
    8364             : #ifdef NCDF_DEBUG
    8365             :     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
    8366             : #endif
    8367             : 
    8368             : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
    8369             :     // Try to destroy the temporary file right now on Unix
    8370         514 :     if (poDS->bFileToDestroyAtClosing)
    8371             :     {
    8372           3 :         if (VSIUnlink(poDS->osFilename) == 0)
    8373             :         {
    8374           3 :             poDS->bFileToDestroyAtClosing = false;
    8375             :         }
    8376             :     }
    8377             : #endif
    8378             : 
    8379             :     // Is this a real netCDF file?
    8380             :     int ndims;
    8381             :     int ngatts;
    8382             :     int nvars;
    8383             :     int unlimdimid;
    8384         514 :     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
    8385         514 :     if (status != NC_NOERR)
    8386             :     {
    8387           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8388             :         // with GDALDataset own mutex.
    8389           0 :         delete poDS;
    8390           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8391           0 :         return nullptr;
    8392             :     }
    8393             : 
    8394             :     // Get file type from netcdf.
    8395         514 :     int nTmpFormat = NCDF_FORMAT_NONE;
    8396         514 :     status = nc_inq_format(cdfid, &nTmpFormat);
    8397         514 :     if (status != NC_NOERR)
    8398             :     {
    8399           0 :         NCDF_ERR(status);
    8400             :     }
    8401             :     else
    8402             :     {
    8403         514 :         CPLDebug("GDAL_netCDF",
    8404             :                  "driver detected file type=%d, libnetcdf detected type=%d",
    8405         514 :                  poDS->eFormat, nTmpFormat);
    8406         514 :         if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
    8407             :         {
    8408             :             // Warn if file detection conflicts with that from libnetcdf
    8409             :             // except for NC4C, which we have no way of detecting initially.
    8410          26 :             if (nTmpFormat != NCDF_FORMAT_NC4C &&
    8411          13 :                 !STARTS_WITH(poDS->osFilename, "http://") &&
    8412           0 :                 !STARTS_WITH(poDS->osFilename, "https://"))
    8413             :             {
    8414           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    8415             :                          "NetCDF driver detected file type=%d, but libnetcdf "
    8416             :                          "detected type=%d",
    8417           0 :                          poDS->eFormat, nTmpFormat);
    8418             :             }
    8419          13 :             CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
    8420          13 :                      nTmpFormat, poDS->eFormat);
    8421          13 :             poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
    8422             :         }
    8423             :     }
    8424             : 
    8425             :     // Does the request variable exist?
    8426         514 :     if (bTreatAsSubdataset)
    8427             :     {
    8428             :         int dummy;
    8429          64 :         if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
    8430          64 :                                &dummy) != CE_None)
    8431             :         {
    8432           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    8433             :                      "%s is a netCDF file, but %s is not a variable.",
    8434             :                      poOpenInfo->pszFilename, osSubdatasetName.c_str());
    8435             : 
    8436           0 :             GDAL_nc_close(cdfid);
    8437             : #ifdef ENABLE_UFFD
    8438           0 :             NETCDF_UFFD_UNMAP(pCtx);
    8439             : #endif
    8440           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8441             :             // deadlock with GDALDataset own mutex.
    8442           0 :             delete poDS;
    8443           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8444           0 :             return nullptr;
    8445             :         }
    8446             :     }
    8447             : 
    8448             :     // Figure out whether or not the listed dataset has support for simple
    8449             :     // geometries (CF-1.8)
    8450         514 :     poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
    8451         514 :     bool bHasSimpleGeometries = false;  // but not necessarily valid
    8452         514 :     if (poDS->nCFVersion >= 1.8)
    8453             :     {
    8454          75 :         bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
    8455          75 :         if (bHasSimpleGeometries)
    8456             :         {
    8457          67 :             poDS->bSGSupport = true;
    8458          67 :             poDS->vcdf.enableFullVirtualMode();
    8459             :         }
    8460             :     }
    8461             : 
    8462             :     char szConventions[NC_MAX_NAME + 1];
    8463         514 :     szConventions[0] = '\0';
    8464         514 :     nc_type nAttype = NC_NAT;
    8465         514 :     size_t nAttlen = 0;
    8466         514 :     nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
    8467        1028 :     if (nAttlen >= sizeof(szConventions) ||
    8468         514 :         nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
    8469             :             NC_NOERR)
    8470             :     {
    8471          59 :         CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
    8472             :         // Note that 'Conventions' is always capital 'C' in CF spec.
    8473             :     }
    8474             :     else
    8475             :     {
    8476         455 :         szConventions[nAttlen] = '\0';
    8477             :     }
    8478             : 
    8479             :     // Create band information objects.
    8480         514 :     CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
    8481             : 
    8482             :     // Create a corresponding GDALDataset.
    8483             :     // Create Netcdf Subdataset if filename as NETCDF tag.
    8484         514 :     poDS->cdfid = cdfid;
    8485             : #ifdef ENABLE_UFFD
    8486         514 :     poDS->pCtx = pCtx;
    8487             : #endif
    8488         514 :     poDS->eAccess = poOpenInfo->eAccess;
    8489         514 :     poDS->bDefineMode = false;
    8490             : 
    8491         514 :     poDS->ReadAttributes(cdfid, NC_GLOBAL);
    8492             : 
    8493             :     // Identify coordinate and boundary variables that we should
    8494             :     // ignore as Raster Bands.
    8495         514 :     char **papszIgnoreVars = nullptr;
    8496         514 :     NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
    8497             :     // Filter variables to keep only valid 2+D raster bands and vector fields.
    8498         514 :     int nRasterVars = 0;
    8499         514 :     int nIgnoredVars = 0;
    8500         514 :     int nGroupID = -1;
    8501         514 :     int nVarID = -1;
    8502             : 
    8503             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
    8504        1028 :         oMap2DDimsToGroupAndVar;
    8505        1181 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8506         153 :         STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
    8507             :                                          "NC_GLOBAL#mission_name", ""),
    8508           1 :                     "Sentinel 3") &&
    8509           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8510             :                                    "NC_GLOBAL#altimeter_sensor_name", ""),
    8511         667 :               "SRAL") &&
    8512           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8513             :                                    "NC_GLOBAL#radiometer_sensor_name", ""),
    8514             :               "MWR"))
    8515             :     {
    8516           1 :         if (poDS->eAccess == GA_Update)
    8517             :         {
    8518           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8519             :             // deadlock with GDALDataset own mutex.
    8520           0 :             delete poDS;
    8521           0 :             return nullptr;
    8522             :         }
    8523           1 :         poDS->ProcessSentinel3_SRAL_MWR();
    8524             :     }
    8525             :     else
    8526             :     {
    8527         513 :         poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
    8528         665 :                          (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8529         152 :                              !bHasSimpleGeometries,
    8530             :                          papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
    8531             :                          &nIgnoredVars, oMap2DDimsToGroupAndVar);
    8532             :     }
    8533         514 :     CSLDestroy(papszIgnoreVars);
    8534             : 
    8535         514 :     const bool bListAllArrays = CPLTestBool(
    8536         514 :         CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    8537             : 
    8538             :     // Case where there is no raster variable
    8539         514 :     if (!bListAllArrays && nRasterVars == 0 && !bTreatAsSubdataset)
    8540             :     {
    8541         119 :         poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8542         119 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8543             :         // with GDALDataset own mutex.
    8544         119 :         poDS->TryLoadXML();
    8545             :         // If the dataset has been opened in raster mode only, exit
    8546         119 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    8547           9 :             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    8548             :         {
    8549           4 :             delete poDS;
    8550           4 :             poDS = nullptr;
    8551             :         }
    8552             :         // Otherwise if the dataset is opened in vector mode, that there is
    8553             :         // no vector layer and we are in read-only, exit too.
    8554         115 :         else if (poDS->GetLayerCount() == 0 &&
    8555         123 :                  (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8556           8 :                  poOpenInfo->eAccess == GA_ReadOnly)
    8557             :         {
    8558           8 :             delete poDS;
    8559           8 :             poDS = nullptr;
    8560             :         }
    8561         119 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8562         119 :         return poDS;
    8563             :     }
    8564             : 
    8565             :     // We have more than one variable with 2 dimensions in the
    8566             :     // file, then treat this as a subdataset container dataset.
    8567         395 :     bool bSeveralVariablesAsBands = false;
    8568         395 :     if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
    8569             :     {
    8570          29 :         if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
    8571          35 :                          false) &&
    8572           6 :             oMap2DDimsToGroupAndVar.size() == 1)
    8573             :         {
    8574           6 :             std::tie(nGroupID, nVarID) =
    8575          12 :                 oMap2DDimsToGroupAndVar.begin()->second.front();
    8576           6 :             bSeveralVariablesAsBands = true;
    8577             :         }
    8578             :         else
    8579             :         {
    8580          23 :             poDS->CreateSubDatasetList(cdfid);
    8581          23 :             poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8582          23 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8583             :             // deadlock with GDALDataset own mutex.
    8584          23 :             poDS->TryLoadXML();
    8585          23 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8586          23 :             return poDS;
    8587             :         }
    8588             :     }
    8589             : 
    8590             :     // If we are not treating things as a subdataset, then capture
    8591             :     // the name of the single available variable as the subdataset.
    8592         372 :     if (!bTreatAsSubdataset)
    8593             :     {
    8594         308 :         char *pszVarName = nullptr;
    8595         308 :         NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
    8596         308 :         osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
    8597         308 :         CPLFree(pszVarName);
    8598             :     }
    8599             : 
    8600             :     // We have ignored at least one variable, so we should report them
    8601             :     // as subdatasets for reference.
    8602         372 :     if (nIgnoredVars > 0 && !bTreatAsSubdataset)
    8603             :     {
    8604          29 :         CPLDebug("GDAL_netCDF",
    8605             :                  "As %d variables were ignored, creating subdataset list "
    8606             :                  "for reference. Variable #%d [%s] is the main variable",
    8607             :                  nIgnoredVars, nVarID, osSubdatasetName.c_str());
    8608          29 :         poDS->CreateSubDatasetList(cdfid);
    8609             :     }
    8610             : 
    8611             :     // Open the NETCDF subdataset NETCDF:"filename":subdataset.
    8612         372 :     int var = -1;
    8613         372 :     NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
    8614             :     // Now we can forget the root cdfid and only use the selected group.
    8615         372 :     cdfid = nGroupID;
    8616         372 :     int nd = 0;
    8617         372 :     nc_inq_varndims(cdfid, var, &nd);
    8618             : 
    8619         372 :     poDS->m_anDimIds.resize(nd);
    8620             : 
    8621             :     // X, Y, Z position in array
    8622         744 :     std::vector<int> anBandDimPos(nd);
    8623             : 
    8624         372 :     nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
    8625             : 
    8626             :     // Check if somebody tried to pass a variable with less than 1D.
    8627         372 :     if (nd < 1)
    8628             :     {
    8629           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8630             :                  "Variable has %d dimension(s) - not supported.", nd);
    8631           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8632             :         // with GDALDataset own mutex.
    8633           0 :         delete poDS;
    8634           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8635           0 :         return nullptr;
    8636             :     }
    8637             : 
    8638             :     // CF-1 Convention
    8639             :     //
    8640             :     // Dimensions to appear in the relative order T, then Z, then Y,
    8641             :     // then X  to the file. All other dimensions should, whenever
    8642             :     // possible, be placed to the left of the spatiotemporal
    8643             :     // dimensions.
    8644             : 
    8645             :     // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
    8646             :     // Ideally we should detect for other ordering and act accordingly
    8647             :     // Only done if file has Conventions=CF-* and only prints warning
    8648             :     // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
    8649             :     // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
    8650             :     const bool bCheckDims =
    8651         744 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
    8652         372 :         STARTS_WITH_CI(szConventions, "CF");
    8653             : 
    8654         372 :     bool bYXBandOrder = false;
    8655         372 :     if (nd == 3)
    8656             :     {
    8657             :         // If there's a coordinates attributes, and the variable it points to
    8658             :         // are 2D variables indexed by the same first and second dimension than
    8659             :         // our variable of interest, then it is Y,X,Band order.
    8660          46 :         char *pszCoordinates = nullptr;
    8661          46 :         if (NCDFGetAttr(cdfid, var, "coordinates", &pszCoordinates) ==
    8662          63 :                 CE_None &&
    8663          17 :             pszCoordinates)
    8664             :         {
    8665             :             const CPLStringList aosCoordinates(
    8666          34 :                 NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    8667          17 :             if (aosCoordinates.size() == 2)
    8668             :             {
    8669             :                 // Test that each variable is longitude/latitude.
    8670          13 :                 for (int i = 0; i < aosCoordinates.size(); i++)
    8671             :                 {
    8672          13 :                     if (NCDFIsVarLongitude(cdfid, -1, aosCoordinates[i]) ||
    8673           4 :                         NCDFIsVarLatitude(cdfid, -1, aosCoordinates[i]))
    8674             :                     {
    8675           9 :                         int nOtherGroupId = -1;
    8676           9 :                         int nOtherVarId = -1;
    8677           9 :                         if (NCDFResolveVar(cdfid, aosCoordinates[i],
    8678             :                                            &nOtherGroupId,
    8679           9 :                                            &nOtherVarId) == CE_None)
    8680             :                         {
    8681           9 :                             int coordDimCount = 0;
    8682           9 :                             nc_inq_varndims(nOtherGroupId, nOtherVarId,
    8683             :                                             &coordDimCount);
    8684           9 :                             if (coordDimCount == 2)
    8685             :                             {
    8686           3 :                                 int coordDimIds[2] = {0, 0};
    8687           3 :                                 nc_inq_vardimid(nOtherGroupId, nOtherVarId,
    8688             :                                                 coordDimIds);
    8689           4 :                                 if (coordDimIds[0] == poDS->m_anDimIds[0] &&
    8690           1 :                                     coordDimIds[1] == poDS->m_anDimIds[1])
    8691             :                                 {
    8692           1 :                                     bYXBandOrder = true;
    8693           1 :                                     break;
    8694             :                                 }
    8695             :                             }
    8696             :                         }
    8697             :                     }
    8698             :                 }
    8699             :             }
    8700             :         }
    8701          46 :         CPLFree(pszCoordinates);
    8702             : 
    8703          46 :         if (!bYXBandOrder)
    8704             :         {
    8705          45 :             char szDim0Name[NC_MAX_NAME + 1] = {};
    8706          45 :             char szDim1Name[NC_MAX_NAME + 1] = {};
    8707          45 :             status = nc_inq_dimname(cdfid, poDS->m_anDimIds[0], szDim0Name);
    8708          45 :             NCDF_ERR(status);
    8709          45 :             status = nc_inq_dimname(cdfid, poDS->m_anDimIds[1], szDim1Name);
    8710          45 :             NCDF_ERR(status);
    8711             : 
    8712          45 :             if (strcmp(szDim0Name, "number_of_lines") == 0 &&
    8713           1 :                 strcmp(szDim1Name, "pixels_per_line") == 0)
    8714             :             {
    8715             :                 // Like in PACE OCI products
    8716           1 :                 bYXBandOrder = true;
    8717             :             }
    8718             :             else
    8719             :             {
    8720             :                 // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
    8721             :                 // dimension order is downtrack, crosstrack, bands
    8722          44 :                 char szDim2Name[NC_MAX_NAME + 1] = {};
    8723          44 :                 status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDim2Name);
    8724          44 :                 NCDF_ERR(status);
    8725          86 :                 bYXBandOrder = strcmp(szDim2Name, "bands") == 0 ||
    8726          42 :                                strcmp(szDim2Name, "band") == 0;
    8727             :             }
    8728             :         }
    8729             :     }
    8730             : 
    8731         372 :     if (nd >= 2 && bCheckDims && !bYXBandOrder)
    8732             :     {
    8733         285 :         char szDimName1[NC_MAX_NAME + 1] = {};
    8734         285 :         char szDimName2[NC_MAX_NAME + 1] = {};
    8735         285 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
    8736         285 :         NCDF_ERR(status);
    8737         285 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
    8738         285 :         NCDF_ERR(status);
    8739         461 :         if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
    8740         176 :             NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
    8741             :         {
    8742           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8743             :                      "dimension #%d (%s) is not a Longitude/X dimension.",
    8744             :                      nd - 1, szDimName1);
    8745             :         }
    8746         461 :         if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
    8747         176 :             NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
    8748             :         {
    8749           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8750             :                      "dimension #%d (%s) is not a Latitude/Y dimension.",
    8751             :                      nd - 2, szDimName2);
    8752             :         }
    8753         285 :         if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
    8754         287 :              NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
    8755           2 :             (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
    8756           0 :              NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
    8757             :         {
    8758           2 :             poDS->bSwitchedXY = true;
    8759             :         }
    8760         285 :         if (nd >= 3)
    8761             :         {
    8762          52 :             char szDimName3[NC_MAX_NAME + 1] = {};
    8763             :             status =
    8764          52 :                 nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
    8765          52 :             NCDF_ERR(status);
    8766          52 :             if (nd >= 4)
    8767             :             {
    8768          13 :                 char szDimName4[NC_MAX_NAME + 1] = {};
    8769             :                 status =
    8770          13 :                     nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
    8771          13 :                 NCDF_ERR(status);
    8772          13 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
    8773             :                 {
    8774           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8775             :                              "dimension #%d (%s) is not a Vertical dimension.",
    8776             :                              nd - 3, szDimName3);
    8777             :                 }
    8778          13 :                 if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
    8779             :                 {
    8780           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8781             :                              "dimension #%d (%s) is not a Time dimension.",
    8782             :                              nd - 4, szDimName4);
    8783             :                 }
    8784             :             }
    8785             :             else
    8786             :             {
    8787          75 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
    8788          36 :                     NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
    8789             :                 {
    8790           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8791             :                              "dimension #%d (%s) is not a "
    8792             :                              "Time or Vertical dimension.",
    8793             :                              nd - 3, szDimName3);
    8794             :                 }
    8795             :             }
    8796             :         }
    8797             :     }
    8798             : 
    8799             :     // Get X dimensions information.
    8800             :     size_t xdim;
    8801         372 :     poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
    8802         372 :     nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
    8803             : 
    8804             :     // Get Y dimension information.
    8805             :     size_t ydim;
    8806         372 :     if (nd >= 2)
    8807             :     {
    8808         368 :         poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
    8809         368 :         nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
    8810             :     }
    8811             :     else
    8812             :     {
    8813           4 :         poDS->nYDimID = -1;
    8814           4 :         ydim = 1;
    8815             :     }
    8816             : 
    8817         372 :     if (xdim > INT_MAX || ydim > INT_MAX)
    8818             :     {
    8819           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8820             :                  "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
    8821             :                  static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
    8822           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8823             :         // with GDALDataset own mutex.
    8824           0 :         delete poDS;
    8825           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8826           0 :         return nullptr;
    8827             :     }
    8828             : 
    8829         372 :     poDS->nRasterXSize = static_cast<int>(xdim);
    8830         372 :     poDS->nRasterYSize = static_cast<int>(ydim);
    8831             : 
    8832         372 :     unsigned int k = 0;
    8833        1193 :     for (int j = 0; j < nd; j++)
    8834             :     {
    8835         821 :         if (poDS->m_anDimIds[j] == poDS->nXDimID)
    8836             :         {
    8837         372 :             anBandDimPos[0] = j;  // Save Position of XDim
    8838         372 :             k++;
    8839             :         }
    8840         821 :         if (poDS->m_anDimIds[j] == poDS->nYDimID)
    8841             :         {
    8842         368 :             anBandDimPos[1] = j;  // Save Position of YDim
    8843         368 :             k++;
    8844             :         }
    8845             :     }
    8846             :     // X and Y Dimension Ids were not found!
    8847         372 :     if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
    8848             :     {
    8849           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8850             :         // with GDALDataset own mutex.
    8851           0 :         delete poDS;
    8852           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8853           0 :         return nullptr;
    8854             :     }
    8855             : 
    8856             :     // Read Metadata for this variable.
    8857             : 
    8858             :     // Should disable as is also done at band level, except driver needs the
    8859             :     // variables as metadata (e.g. projection).
    8860         372 :     poDS->ReadAttributes(cdfid, var);
    8861             : 
    8862             :     // Read Metadata for each dimension.
    8863         372 :     int *panDimIds = nullptr;
    8864         372 :     NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
    8865             :     // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
    8866             :     // in NetCDF-3 because we see only the dimensions of the selected group
    8867             :     // and its parents.
    8868             :     // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
    8869             :     // [0..max(panDimIds)], but they are not all useful so we fill names
    8870             :     // of useless dims with empty string.
    8871         372 :     if (panDimIds)
    8872             :     {
    8873         372 :         const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
    8874         372 :         std::set<int> oSetExistingDimIds;
    8875        1233 :         for (int i = 0; i < ndims; i++)
    8876             :         {
    8877         861 :             oSetExistingDimIds.insert(panDimIds[i]);
    8878             :         }
    8879         372 :         std::set<int> oSetDimIdsUsedByVar;
    8880        1193 :         for (int i = 0; i < nd; i++)
    8881             :         {
    8882         821 :             oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
    8883             :         }
    8884        1235 :         for (int j = 0; j <= nMaxDimId; j++)
    8885             :         {
    8886             :             // Is j dim used?
    8887         863 :             if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
    8888             :             {
    8889             :                 // Useful dim.
    8890         861 :                 char szTemp[NC_MAX_NAME + 1] = {};
    8891         861 :                 status = nc_inq_dimname(cdfid, j, szTemp);
    8892         861 :                 if (status != NC_NOERR)
    8893             :                 {
    8894           0 :                     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8895             :                     // deadlock with GDALDataset own
    8896             :                     // mutex.
    8897           0 :                     delete poDS;
    8898           0 :                     CPLAcquireMutex(hNCMutex, 1000.0);
    8899           0 :                     return nullptr;
    8900             :                 }
    8901         861 :                 poDS->papszDimName.AddString(szTemp);
    8902             : 
    8903         861 :                 if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
    8904             :                 {
    8905         821 :                     int nDimGroupId = -1;
    8906         821 :                     int nDimVarId = -1;
    8907         821 :                     if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
    8908         821 :                                        &nDimGroupId, &nDimVarId) == CE_None)
    8909             :                     {
    8910         593 :                         poDS->ReadAttributes(nDimGroupId, nDimVarId);
    8911             :                     }
    8912             :                 }
    8913             :             }
    8914             :             else
    8915             :             {
    8916             :                 // Useless dim.
    8917           2 :                 poDS->papszDimName.AddString("");
    8918             :             }
    8919             :         }
    8920         372 :         CPLFree(panDimIds);
    8921             :     }
    8922             : 
    8923             :     // Set projection info.
    8924         744 :     std::vector<std::string> aosRemovedMDItems;
    8925         372 :     if (nd > 1)
    8926             :     {
    8927         368 :         poDS->SetProjectionFromVar(cdfid, var,
    8928             :                                    /*bReadSRSOnly=*/false,
    8929             :                                    /* pszGivenGM = */ nullptr,
    8930             :                                    /* returnProjStr = */ nullptr,
    8931             :                                    /* sg = */ nullptr, &aosRemovedMDItems);
    8932             :     }
    8933             : 
    8934             :     // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
    8935         372 :     const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
    8936         372 :     if (pszValue)
    8937             :     {
    8938          24 :         poDS->bBottomUp = CPLTestBool(pszValue);
    8939          24 :         CPLDebug("GDAL_netCDF",
    8940             :                  "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
    8941          24 :                  static_cast<int>(poDS->bBottomUp), pszValue);
    8942             :     }
    8943             : 
    8944             :     // Save non-spatial dimension info.
    8945             : 
    8946         372 :     int *panBandZLev = nullptr;
    8947         372 :     int nDim = (nd >= 2) ? 2 : 1;
    8948             :     size_t lev_count;
    8949         372 :     size_t nTotLevCount = 1;
    8950         372 :     nc_type nType = NC_NAT;
    8951             : 
    8952         372 :     if (nd > 2)
    8953             :     {
    8954          62 :         nDim = 2;
    8955          62 :         panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
    8956             : 
    8957          62 :         CPLString osExtraDimNames = "{";
    8958             : 
    8959          62 :         char szDimName[NC_MAX_NAME + 1] = {};
    8960             : 
    8961          62 :         bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
    8962         267 :         for (int j = 0; j < nd; j++)
    8963             :         {
    8964         348 :             if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
    8965         143 :                 (poDS->m_anDimIds[j] != poDS->nYDimID))
    8966             :             {
    8967          81 :                 nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
    8968          81 :                 nTotLevCount *= lev_count;
    8969          81 :                 panBandZLev[nDim - 2] = static_cast<int>(lev_count);
    8970          81 :                 anBandDimPos[nDim] = j;  // Save Position of ZDim
    8971             :                 // Save non-spatial dimension names.
    8972          81 :                 if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
    8973             :                     NC_NOERR)
    8974             :                 {
    8975          81 :                     osExtraDimNames += szDimName;
    8976          81 :                     if (j < nd - 3)
    8977             :                     {
    8978          19 :                         osExtraDimNames += ",";
    8979             :                     }
    8980             : 
    8981          81 :                     int nIdxGroupID = -1;
    8982          81 :                     int nIdxVarID = Get1DVariableIndexedByDimension(
    8983          81 :                         cdfid, poDS->m_anDimIds[j], szDimName, true,
    8984          81 :                         &nIdxGroupID);
    8985          81 :                     poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
    8986          81 :                     poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
    8987             : 
    8988          81 :                     if (nIdxVarID >= 0)
    8989             :                     {
    8990          72 :                         nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
    8991             :                         char szExtraDimDef[NC_MAX_NAME + 1];
    8992          72 :                         snprintf(szExtraDimDef, sizeof(szExtraDimDef),
    8993             :                                  "{%ld,%d}", (long)lev_count, nType);
    8994             :                         char szTemp[NC_MAX_NAME + 32 + 1];
    8995          72 :                         snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    8996             :                                  szDimName);
    8997          72 :                         poDS->papszMetadata = CSLSetNameValue(
    8998             :                             poDS->papszMetadata, szTemp, szExtraDimDef);
    8999             : 
    9000             :                         // Retrieving data for unlimited dimensions might be
    9001             :                         // costly on network storage, so don't do it.
    9002             :                         // Each band will capture the value along the extra
    9003             :                         // dimension in its NETCDF_DIM_xxxx band metadata item
    9004             :                         // Addresses use case of
    9005             :                         // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
    9006             :                         const bool bIsLocal =
    9007          72 :                             VSIIsLocal(osFilenameForNCOpen.c_str());
    9008             :                         bool bListDimValues =
    9009          73 :                             bIsLocal || lev_count == 1 ||
    9010           1 :                             !NCDFIsUnlimitedDim(poDS->eFormat ==
    9011             :                                                     NCDF_FORMAT_NC4,
    9012           1 :                                                 cdfid, poDS->m_anDimIds[j]);
    9013             :                         const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
    9014          72 :                             CPLGetConfigOption(
    9015             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
    9016          72 :                         if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
    9017             :                         {
    9018           2 :                             bListDimValues = CPLTestBool(
    9019             :                                 pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
    9020             :                         }
    9021          70 :                         else if (!bListDimValues && !bIsLocal &&
    9022           1 :                                  !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
    9023             :                         {
    9024           1 :                             bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
    9025           1 :                             CPLDebug(
    9026             :                                 "GDAL_netCDF",
    9027             :                                 "Listing extra dimension values is skipped "
    9028             :                                 "because this dataset is hosted on a network "
    9029             :                                 "file system, and such an operation could be "
    9030             :                                 "slow. If you still want to proceed, set the "
    9031             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
    9032             :                                 "configuration option to YES");
    9033             :                         }
    9034          72 :                         if (bListDimValues)
    9035             :                         {
    9036          70 :                             char *pszTemp = nullptr;
    9037          70 :                             if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
    9038          70 :                                              &pszTemp) == CE_None)
    9039             :                             {
    9040          70 :                                 snprintf(szTemp, sizeof(szTemp),
    9041             :                                          "NETCDF_DIM_%s_VALUES", szDimName);
    9042          70 :                                 poDS->papszMetadata = CSLSetNameValue(
    9043             :                                     poDS->papszMetadata, szTemp, pszTemp);
    9044          70 :                                 CPLFree(pszTemp);
    9045             :                             }
    9046             :                         }
    9047             :                     }
    9048             :                 }
    9049             :                 else
    9050             :                 {
    9051           0 :                     poDS->m_anExtraDimGroupIds.push_back(-1);
    9052           0 :                     poDS->m_anExtraDimVarIds.push_back(-1);
    9053             :                 }
    9054             : 
    9055          81 :                 nDim++;
    9056             :             }
    9057             :         }
    9058          62 :         osExtraDimNames += "}";
    9059          62 :         poDS->papszMetadata = CSLSetNameValue(
    9060             :             poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
    9061             :     }
    9062             : 
    9063             :     // Store Metadata.
    9064         382 :     for (const auto &osStr : aosRemovedMDItems)
    9065          10 :         poDS->papszMetadata =
    9066          10 :             CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
    9067             : 
    9068         372 :     poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    9069             : 
    9070             :     // Create bands.
    9071             : 
    9072             :     // Arbitrary threshold.
    9073             :     int nMaxBandCount =
    9074         372 :         atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
    9075         372 :     if (nMaxBandCount <= 0)
    9076           0 :         nMaxBandCount = 32768;
    9077         372 :     if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
    9078             :     {
    9079           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9080             :                  "Limiting number of bands to %d instead of %u", nMaxBandCount,
    9081             :                  static_cast<unsigned int>(nTotLevCount));
    9082           0 :         nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
    9083             :     }
    9084         372 :     if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
    9085             :     {
    9086           0 :         poDS->nRasterXSize = 0;
    9087           0 :         poDS->nRasterYSize = 0;
    9088           0 :         nTotLevCount = 0;
    9089           0 :         if (poDS->GetLayerCount() == 0)
    9090             :         {
    9091           0 :             CPLFree(panBandZLev);
    9092           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9093             :             // deadlock with GDALDataset own mutex.
    9094           0 :             delete poDS;
    9095           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9096           0 :             return nullptr;
    9097             :         }
    9098             :     }
    9099         372 :     if (bSeveralVariablesAsBands)
    9100             :     {
    9101           6 :         const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
    9102          24 :         for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
    9103             :              ++iBand)
    9104             :         {
    9105          18 :             int bandVarGroupId = listVariables[iBand].first;
    9106          18 :             int bandVarId = listVariables[iBand].second;
    9107             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    9108           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
    9109          18 :                 bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
    9110          18 :             poDS->SetBand(iBand + 1, poBand);
    9111             :         }
    9112             :     }
    9113             :     else
    9114             :     {
    9115         846 :         for (unsigned int lev = 0; lev < nTotLevCount; lev++)
    9116             :         {
    9117             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    9118           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
    9119         480 :                 lev, panBandZLev, anBandDimPos.data(), lev + 1);
    9120         480 :             poDS->SetBand(lev + 1, poBand);
    9121             :         }
    9122             :     }
    9123             : 
    9124         372 :     if (panBandZLev)
    9125          62 :         CPLFree(panBandZLev);
    9126             :     // Handle angular geographic coordinates here
    9127             : 
    9128             :     // Initialize any PAM information.
    9129         372 :     if (bTreatAsSubdataset)
    9130             :     {
    9131          64 :         poDS->SetPhysicalFilename(poDS->osFilename);
    9132          64 :         poDS->SetSubdatasetName(osSubdatasetName);
    9133             :     }
    9134             : 
    9135         372 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9136             :     // GDALDataset own mutex.
    9137         372 :     poDS->TryLoadXML();
    9138             : 
    9139         372 :     if (bTreatAsSubdataset)
    9140          64 :         poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
    9141             :     else
    9142         308 :         poDS->oOvManager.Initialize(poDS, poDS->osFilename);
    9143             : 
    9144         372 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9145             : 
    9146         372 :     return poDS;
    9147             : }
    9148             : 
    9149             : /************************************************************************/
    9150             : /*                            CopyMetadata()                            */
    9151             : /*                                                                      */
    9152             : /*      Create a copy of metadata for NC_GLOBAL or a variable           */
    9153             : /************************************************************************/
    9154             : 
    9155         169 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
    9156             :                          GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
    9157             :                          const char *pszPrefix)
    9158             : {
    9159             :     // Remove the following band meta but set them later from band data.
    9160         169 :     const char *const papszIgnoreBand[] = {
    9161             :         CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    9162             :         NCDF_FillValue, "coordinates",   nullptr};
    9163         169 :     const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
    9164             : 
    9165         169 :     CSLConstList papszMetadata = nullptr;
    9166         169 :     if (poSrcDS)
    9167             :     {
    9168          72 :         papszMetadata = poSrcDS->GetMetadata();
    9169             :     }
    9170          97 :     else if (poSrcBand)
    9171             :     {
    9172          97 :         papszMetadata = poSrcBand->GetMetadata();
    9173             :     }
    9174             : 
    9175         655 :     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
    9176             :     {
    9177             : #ifdef NCDF_DEBUG
    9178             :         CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
    9179             : #endif
    9180             : 
    9181         486 :         CPLString osMetaName(pszKey);
    9182             : 
    9183             :         // Check for items that match pszPrefix if applicable.
    9184         486 :         if (pszPrefix && !EQUAL(pszPrefix, ""))
    9185             :         {
    9186             :             // Remove prefix.
    9187         115 :             if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
    9188             :             {
    9189          17 :                 osMetaName = osMetaName.substr(strlen(pszPrefix));
    9190             :             }
    9191             :             // Only copy items that match prefix.
    9192             :             else
    9193             :             {
    9194          98 :                 continue;
    9195             :             }
    9196             :         }
    9197             : 
    9198             :         // Fix various issues with metadata translation.
    9199         388 :         if (CDFVarID == NC_GLOBAL)
    9200             :         {
    9201             :             // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
    9202         493 :             if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
    9203         244 :                 (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
    9204          21 :                 continue;
    9205             :             // Remove NC_GLOBAL prefix for netcdf global Metadata.
    9206         228 :             else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
    9207             :             {
    9208          33 :                 osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
    9209             :             }
    9210             :             // GDAL Metadata renamed as GDAL-[meta].
    9211         195 :             else if (strstr(osMetaName, "#") == nullptr)
    9212             :             {
    9213          22 :                 osMetaName = "GDAL_" + osMetaName;
    9214             :             }
    9215             :             // Keep time, lev and depth information for safe-keeping.
    9216             :             // Time and vertical coordinate handling need improvements.
    9217             :             /*
    9218             :             else if( STARTS_WITH(szMetaName, "time#") )
    9219             :             {
    9220             :                 szMetaName[4] = '-';
    9221             :             }
    9222             :             else if( STARTS_WITH(szMetaName, "lev#") )
    9223             :             {
    9224             :                 szMetaName[3] = '-';
    9225             :             }
    9226             :             else if( STARTS_WITH(szMetaName, "depth#") )
    9227             :             {
    9228             :                 szMetaName[5] = '-';
    9229             :             }
    9230             :             */
    9231             :             // Only copy data without # (previously all data was copied).
    9232         228 :             if (strstr(osMetaName, "#") != nullptr)
    9233         173 :                 continue;
    9234             :             // netCDF attributes do not like the '#' character.
    9235             :             // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
    9236             :             //     if( szMetaName[h] == '#') szMetaName[h] = '-';
    9237             :             // }
    9238             :         }
    9239             :         else
    9240             :         {
    9241             :             // Do not copy varname, stats, NETCDF_DIM_*, nodata
    9242             :             // and items in papszIgnoreBand.
    9243         139 :             if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
    9244         107 :                 STARTS_WITH(osMetaName, "STATISTICS_") ||
    9245         107 :                 STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
    9246          74 :                 STARTS_WITH(osMetaName, "missing_value") ||
    9247         293 :                 STARTS_WITH(osMetaName, "_FillValue") ||
    9248          47 :                 CSLFindString(papszIgnoreBand, osMetaName) != -1)
    9249          97 :                 continue;
    9250             :         }
    9251             : 
    9252             : #ifdef NCDF_DEBUG
    9253             :         CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
    9254             :                  pszValue);
    9255             : #endif
    9256          97 :         if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
    9257             :         {
    9258           0 :             CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
    9259             :                      nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
    9260             :         }
    9261             :     }
    9262             : 
    9263             :     // Set add_offset and scale_factor here if present.
    9264         169 :     if (poSrcBand && poDstBand)
    9265             :     {
    9266             : 
    9267          97 :         int bGotAddOffset = FALSE;
    9268          97 :         const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
    9269          97 :         int bGotScale = FALSE;
    9270          97 :         const double dfScale = poSrcBand->GetScale(&bGotScale);
    9271             : 
    9272          97 :         if (bGotAddOffset && dfAddOffset != 0.0)
    9273           1 :             poDstBand->SetOffset(dfAddOffset);
    9274          97 :         if (bGotScale && dfScale != 1.0)
    9275           1 :             poDstBand->SetScale(dfScale);
    9276             :     }
    9277         169 : }
    9278             : 
    9279             : /************************************************************************/
    9280             : /*                            CreateLL()                                */
    9281             : /*                                                                      */
    9282             : /*      Shared functionality between netCDFDataset::Create() and        */
    9283             : /*      netCDF::CreateCopy() for creating netcdf file based on a set of */
    9284             : /*      options and a configuration.                                    */
    9285             : /************************************************************************/
    9286             : 
    9287         205 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
    9288             :                                        int nYSize, int nBandsIn,
    9289             :                                        CSLConstList papszOptions)
    9290             : {
    9291         205 :     if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
    9292         132 :           (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
    9293             :     {
    9294           1 :         return nullptr;
    9295             :     }
    9296             : 
    9297         204 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9298             :     // GDALDataset own mutex.
    9299         204 :     netCDFDataset *poDS = new netCDFDataset();
    9300         204 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9301             : 
    9302         204 :     poDS->nRasterXSize = nXSize;
    9303         204 :     poDS->nRasterYSize = nYSize;
    9304         204 :     poDS->eAccess = GA_Update;
    9305         204 :     poDS->osFilename = pszFilename;
    9306             : 
    9307             :     // From gtiff driver, is this ok?
    9308             :     /*
    9309             :     poDS->nBlockXSize = nXSize;
    9310             :     poDS->nBlockYSize = 1;
    9311             :     poDS->nBlocksPerBand =
    9312             :         DIV_ROUND_UP((nYSize, poDS->nBlockYSize))
    9313             :         * DIV_ROUND_UP((nXSize, poDS->nBlockXSize));
    9314             :         */
    9315             : 
    9316             :     // process options.
    9317         204 :     poDS->papszCreationOptions = CSLDuplicate(papszOptions);
    9318         204 :     poDS->ProcessCreationOptions();
    9319             : 
    9320         204 :     if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
    9321             :     {
    9322             :         VSIStatBuf sStat;
    9323           3 :         if (VSIStat(pszFilename, &sStat) == 0)
    9324             :         {
    9325           0 :             if (!VSI_ISDIR(sStat.st_mode))
    9326             :             {
    9327           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    9328             :                          "%s is an existing file, but not a directory",
    9329             :                          pszFilename);
    9330           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9331             :                 // deadlock with GDALDataset own
    9332             :                 // mutex.
    9333           0 :                 delete poDS;
    9334           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    9335           0 :                 return nullptr;
    9336             :             }
    9337             :         }
    9338           3 :         else if (VSIMkdir(pszFilename, 0755) != 0)
    9339             :         {
    9340           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
    9341             :                      pszFilename);
    9342           1 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9343             :             // deadlock with GDALDataset own mutex.
    9344           1 :             delete poDS;
    9345           1 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9346           1 :             return nullptr;
    9347             :         }
    9348             : 
    9349           2 :         return poDS;
    9350             :     }
    9351             :     // Create the dataset.
    9352         402 :     CPLString osFilenameForNCCreate(pszFilename);
    9353             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    9354             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    9355             :     {
    9356             :         char *pszTemp =
    9357             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    9358             :         osFilenameForNCCreate = pszTemp;
    9359             :         CPLFree(pszTemp);
    9360             :     }
    9361             : #endif
    9362             : 
    9363             : #if defined(_WIN32)
    9364             :     {
    9365             :         // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
    9366             :         // crashes
    9367             :         VSIStatBuf sStat;
    9368             :         const std::string osDirname =
    9369             :             CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
    9370             :         if (VSIStat(osDirname.c_str(), &sStat) != 0)
    9371             :         {
    9372             :             CPLError(CE_Failure, CPLE_OpenFailed,
    9373             :                      "Unable to create netCDF file %s: non existing output "
    9374             :                      "directory",
    9375             :                      pszFilename);
    9376             :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9377             :             // deadlock with GDALDataset own mutex.
    9378             :             delete poDS;
    9379             :             CPLAcquireMutex(hNCMutex, 1000.0);
    9380             :             return nullptr;
    9381             :         }
    9382             :     }
    9383             : #endif
    9384             : 
    9385             :     int status =
    9386         201 :         nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
    9387             : 
    9388             :     // Put into define mode.
    9389         201 :     poDS->SetDefineMode(true);
    9390             : 
    9391         201 :     if (status != NC_NOERR)
    9392             :     {
    9393          30 :         CPLError(CE_Failure, CPLE_OpenFailed,
    9394             :                  "Unable to create netCDF file %s (Error code %d): %s .",
    9395             :                  pszFilename, status, nc_strerror(status));
    9396          30 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    9397             :         // with GDALDataset own mutex.
    9398          30 :         delete poDS;
    9399          30 :         CPLAcquireMutex(hNCMutex, 1000.0);
    9400          30 :         return nullptr;
    9401             :     }
    9402             : 
    9403             :     // Define dimensions.
    9404         171 :     if (nXSize > 0 && nYSize > 0)
    9405             :     {
    9406         118 :         poDS->papszDimName.AddString(NCDF_DIMNAME_X);
    9407             :         status =
    9408         118 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
    9409         118 :         NCDF_ERR(status);
    9410         118 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9411             :                  poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
    9412             : 
    9413         118 :         poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
    9414             :         status =
    9415         118 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
    9416         118 :         NCDF_ERR(status);
    9417         118 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9418             :                  poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
    9419             :     }
    9420             : 
    9421         171 :     return poDS;
    9422             : }
    9423             : 
    9424             : /************************************************************************/
    9425             : /*                               Create()                               */
    9426             : /************************************************************************/
    9427             : 
    9428         127 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
    9429             :                                    int nYSize, int nBandsIn, GDALDataType eType,
    9430             :                                    CSLConstList papszOptions)
    9431             : {
    9432         127 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
    9433             :              pszFilename);
    9434             : 
    9435             :     const char *legacyCreationOp =
    9436         127 :         CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9437         254 :     std::string legacyCreationOp_s = std::string(legacyCreationOp);
    9438             : 
    9439             :     // Check legacy creation op FIRST
    9440             : 
    9441         127 :     bool legacyCreateMode = false;
    9442             : 
    9443         127 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
    9444             :     {
    9445          56 :         legacyCreateMode = true;
    9446             :     }
    9447          71 :     else if (legacyCreationOp_s == "CF_1.8")
    9448             :     {
    9449          54 :         legacyCreateMode = false;
    9450             :     }
    9451             : 
    9452          17 :     else if (legacyCreationOp_s == "WKT")
    9453             :     {
    9454          17 :         legacyCreateMode = true;
    9455             :     }
    9456             : 
    9457             :     else
    9458             :     {
    9459           0 :         CPLError(
    9460             :             CE_Failure, CPLE_NotSupported,
    9461             :             "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
    9462             :             legacyCreationOp_s.c_str());
    9463           0 :         return nullptr;
    9464             :     }
    9465             : 
    9466         254 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9467         240 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9468         113 :         (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
    9469             :          eType == GDT_Int64))
    9470             :     {
    9471          10 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9472          10 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9473             :     }
    9474             : 
    9475         254 :     CPLStringList aosBandNames;
    9476         127 :     if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
    9477             :     {
    9478             :         aosBandNames =
    9479           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9480             : 
    9481           2 :         if (aosBandNames.Count() != nBandsIn)
    9482             :         {
    9483           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9484             :                      "Attempted to create netCDF with %d bands but %d names "
    9485             :                      "provided in BAND_NAMES.",
    9486             :                      nBandsIn, aosBandNames.Count());
    9487             : 
    9488           1 :             return nullptr;
    9489             :         }
    9490             :     }
    9491             : 
    9492         252 :     CPLMutexHolderD(&hNCMutex);
    9493             : 
    9494         126 :     auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
    9495         126 :                                         aosOptions.List());
    9496             : 
    9497         126 :     if (!poDS)
    9498          19 :         return nullptr;
    9499             : 
    9500         107 :     if (!legacyCreateMode)
    9501             :     {
    9502          37 :         poDS->bSGSupport = true;
    9503          37 :         poDS->vcdf.enableFullVirtualMode();
    9504             :     }
    9505             : 
    9506             :     else
    9507             :     {
    9508          70 :         poDS->bSGSupport = false;
    9509             :     }
    9510             : 
    9511             :     // Should we write signed or unsigned byte?
    9512             :     // TODO should this only be done in Create()
    9513         107 :     poDS->bSignedData = true;
    9514         107 :     const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
    9515         107 :     if (eType == GDT_UInt8 && !EQUAL(pszValue, "SIGNEDBYTE"))
    9516          15 :         poDS->bSignedData = false;
    9517             : 
    9518             :     // Add Conventions, GDAL info and history.
    9519         107 :     if (poDS->cdfid >= 0)
    9520             :     {
    9521             :         const char *CF_Vector_Conv =
    9522         173 :             poDS->bSGSupport ||
    9523             :                     // Use of variable length strings require CF-1.8
    9524          68 :                     EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
    9525             :                 ? NCDF_CONVENTIONS_CF_V1_8
    9526         173 :                 : NCDF_CONVENTIONS_CF_V1_6;
    9527         105 :         poDS->bWriteGDALVersion = CPLTestBool(
    9528             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9529         105 :         poDS->bWriteGDALHistory = CPLTestBool(
    9530             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9531         105 :         NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
    9532         105 :                            poDS->bWriteGDALHistory, "", "Create",
    9533             :                            (nBandsIn == 0) ? CF_Vector_Conv
    9534             :                                            : GDAL_DEFAULT_NCDF_CONVENTIONS);
    9535             :     }
    9536             : 
    9537             :     // Define bands.
    9538         198 :     for (int iBand = 1; iBand <= nBandsIn; iBand++)
    9539             :     {
    9540             :         const char *pszBandName =
    9541          91 :             aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
    9542             : 
    9543          91 :         poDS->SetBand(iBand, new netCDFRasterBand(
    9544          91 :                                  netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
    9545          91 :                                  eType, iBand, poDS->bSignedData, pszBandName));
    9546             :     }
    9547             : 
    9548         107 :     CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
    9549             :     // Return same dataset.
    9550         107 :     return poDS;
    9551             : }
    9552             : 
    9553             : template <class T>
    9554          97 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
    9555             :                            int nXSize, int nYSize, GDALProgressFunc pfnProgress,
    9556             :                            void *pProgressData)
    9557             : {
    9558          97 :     const GDALDataType eDT = poSrcBand->GetRasterDataType();
    9559          97 :     T *patScanline = static_cast<T *>(VSI_MALLOC2_VERBOSE(nXSize, sizeof(T)));
    9560          97 :     CPLErr eErr = patScanline ? CE_None : CE_Failure;
    9561             : 
    9562        6414 :     for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
    9563             :     {
    9564        6317 :         eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
    9565             :                                    nXSize, 1, eDT, 0, 0, nullptr);
    9566        6317 :         if (eErr != CE_None)
    9567             :         {
    9568           0 :             CPLDebug(
    9569             :                 "GDAL_netCDF",
    9570             :                 "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
    9571             :                 eErr);
    9572             :         }
    9573             :         else
    9574             :         {
    9575        6317 :             eErr =
    9576             :                 poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
    9577             :                                     nXSize, 1, eDT, 0, 0, nullptr);
    9578        6317 :             if (eErr != CE_None)
    9579           0 :                 CPLDebug("GDAL_netCDF",
    9580             :                          "NCDFCopyBand(), poDstBand->RasterIO() returned error "
    9581             :                          "code %d",
    9582             :                          eErr);
    9583             :         }
    9584             : 
    9585        6317 :         if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
    9586             :         {
    9587         317 :             if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
    9588             :             {
    9589           0 :                 eErr = CE_Failure;
    9590           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt,
    9591             :                          "User terminated CreateCopy()");
    9592             :             }
    9593             :         }
    9594             :     }
    9595             : 
    9596          97 :     CPLFree(patScanline);
    9597             : 
    9598          97 :     pfnProgress(1.0, nullptr, pProgressData);
    9599             : 
    9600          97 :     return eErr;
    9601             : }
    9602             : 
    9603             : /************************************************************************/
    9604             : /*                             CreateCopy()                             */
    9605             : /************************************************************************/
    9606             : 
    9607             : GDALDataset *
    9608          93 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
    9609             :                           CPL_UNUSED int bStrict, CSLConstList papszOptions,
    9610             :                           GDALProgressFunc pfnProgress, void *pProgressData)
    9611             : {
    9612         186 :     CPLMutexHolderD(&hNCMutex);
    9613             : 
    9614          93 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
    9615             :              pszFilename);
    9616             : 
    9617          93 :     if (poSrcDS->GetRootGroup())
    9618             :     {
    9619          10 :         auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
    9620          10 :         if (poDrv)
    9621             :         {
    9622          10 :             return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    9623             :                                             papszOptions, pfnProgress,
    9624          10 :                                             pProgressData);
    9625             :         }
    9626             :     }
    9627             : 
    9628          83 :     const int nBands = poSrcDS->GetRasterCount();
    9629          83 :     const int nXSize = poSrcDS->GetRasterXSize();
    9630          83 :     const int nYSize = poSrcDS->GetRasterYSize();
    9631          83 :     const char *pszWKT = poSrcDS->GetProjectionRef();
    9632             : 
    9633             :     // Check input bands for errors.
    9634          83 :     if (nBands == 0)
    9635             :     {
    9636           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    9637             :                  "NetCDF driver does not support "
    9638             :                  "source dataset with zero band.");
    9639           1 :         return nullptr;
    9640             :     }
    9641             : 
    9642          82 :     GDALDataType eDT = GDT_Unknown;
    9643          82 :     GDALRasterBand *poSrcBand = nullptr;
    9644         193 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9645             :     {
    9646         115 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9647         115 :         eDT = poSrcBand->GetRasterDataType();
    9648         115 :         if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
    9649             :         {
    9650           4 :             CPLError(CE_Failure, CPLE_NotSupported,
    9651             :                      "NetCDF driver does not support source dataset with band "
    9652             :                      "of complex type.");
    9653           4 :             return nullptr;
    9654             :         }
    9655             :     }
    9656             : 
    9657         156 :     CPLStringList aosBandNames;
    9658          78 :     if (const char *pszBandNames =
    9659          78 :             CSLFetchNameValue(papszOptions, "BAND_NAMES"))
    9660             :     {
    9661             :         aosBandNames =
    9662           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9663             : 
    9664           2 :         if (aosBandNames.Count() != nBands)
    9665             :         {
    9666           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9667             :                      "Attempted to create netCDF with %d bands but %d names "
    9668             :                      "provided in BAND_NAMES.",
    9669             :                      nBands, aosBandNames.Count());
    9670             : 
    9671           1 :             return nullptr;
    9672             :         }
    9673             :     }
    9674             : 
    9675          77 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    9676           0 :         return nullptr;
    9677             : 
    9678             :     // Same as in Create().
    9679         154 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9680         145 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9681          68 :         (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
    9682             :          eDT == GDT_Int64))
    9683             :     {
    9684           6 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9685           6 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9686             :     }
    9687          77 :     netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
    9688          77 :                                                   nBands, aosOptions.List());
    9689          77 :     if (!poDS)
    9690          13 :         return nullptr;
    9691             : 
    9692             :     // Copy global metadata.
    9693             :     // Add Conventions, GDAL info and history.
    9694          64 :     CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
    9695          64 :     const bool bWriteGDALVersion = CPLTestBool(
    9696             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9697          64 :     const bool bWriteGDALHistory = CPLTestBool(
    9698             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9699          64 :     NCDFAddGDALHistory(
    9700             :         poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
    9701          64 :         poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
    9702          64 :         poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
    9703             : 
    9704          64 :     pfnProgress(0.1, nullptr, pProgressData);
    9705             : 
    9706             :     // Check for extra dimensions.
    9707          64 :     int nDim = 2;
    9708             :     CPLStringList aosExtraDimNames =
    9709         128 :         NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9710             : 
    9711          64 :     if (!aosExtraDimNames.empty())
    9712             :     {
    9713           5 :         size_t nDimSizeTot = 1;
    9714             :         // first make sure dimensions lengths compatible with band count
    9715             :         // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
    9716          13 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9717             :         {
    9718             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9719           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9720             :                      aosExtraDimNames[i]);
    9721             :             const CPLStringList aosExtraDimValues =
    9722           8 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9723           8 :             const size_t nDimSize = atol(aosExtraDimValues[0]);
    9724           8 :             nDimSizeTot *= nDimSize;
    9725             :         }
    9726           5 :         if (nDimSizeTot == (size_t)nBands)
    9727             :         {
    9728           5 :             nDim = 2 + aosExtraDimNames.size();
    9729             :         }
    9730             :         else
    9731             :         {
    9732             :             // if nBands != #bands computed raise a warning
    9733             :             // just issue a debug message, because it was probably intentional
    9734           0 :             CPLDebug("GDAL_netCDF",
    9735             :                      "Warning: Number of bands (%d) is not compatible with "
    9736             :                      "dimensions "
    9737             :                      "(total=%ld names=%s)",
    9738             :                      nBands, (long)nDimSizeTot,
    9739           0 :                      poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9740           0 :             aosExtraDimNames.clear();
    9741             :         }
    9742             :     }
    9743             : 
    9744          64 :     int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9745          64 :     int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9746             : 
    9747             :     nc_type nVarType;
    9748          64 :     int *panBandZLev = nullptr;
    9749          64 :     int *panDimVarIds = nullptr;
    9750             : 
    9751          64 :     if (nDim > 2)
    9752             :     {
    9753           5 :         panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9754           5 :         panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9755             : 
    9756             :         // Define all dims.
    9757          13 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9758             :         {
    9759           8 :             poDS->papszDimName.AddString(aosExtraDimNames[i]);
    9760             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9761           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9762             :                      aosExtraDimNames[i]);
    9763             :             const CPLStringList aosExtraDimValues =
    9764          16 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9765             :             const int nDimSize =
    9766           8 :                 aosExtraDimValues.empty() ? 0 : atoi(aosExtraDimValues[0]);
    9767             :             // nc_type is an enum in netcdf-3, needs casting.
    9768           0 :             nVarType = static_cast<nc_type>(
    9769           8 :                 aosExtraDimValues.size() >= 2 ? atol(aosExtraDimValues[1]) : 0);
    9770           8 :             panBandZLev[i] = nDimSize;
    9771           8 :             panBandDimPos[i + 2] = i;  // Save Position of ZDim.
    9772             : 
    9773             :             // Define dim.
    9774           8 :             int status = nc_def_dim(poDS->cdfid, aosExtraDimNames[i], nDimSize,
    9775           8 :                                     &(panDimIds[i]));
    9776           8 :             NCDF_ERR(status);
    9777             : 
    9778             :             // Define dim var.
    9779           8 :             int anDim[1] = {panDimIds[i]};
    9780           8 :             status = nc_def_var(poDS->cdfid, aosExtraDimNames[i], nVarType, 1,
    9781           8 :                                 anDim, &(panDimVarIds[i]));
    9782           8 :             NCDF_ERR(status);
    9783             : 
    9784             :             // Add dim metadata, using global var# items.
    9785           8 :             snprintf(szTemp, sizeof(szTemp), "%s#", aosExtraDimNames[i]);
    9786           8 :             CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
    9787           8 :                          panDimVarIds[i], szTemp);
    9788             :         }
    9789             :     }
    9790             : 
    9791             :     // Copy GeoTransform and Projection.
    9792             : 
    9793             :     // Copy geolocation info.
    9794          64 :     CSLConstList papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
    9795          64 :     if (papszGeolocationInfo != nullptr)
    9796           5 :         poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
    9797             : 
    9798             :     // Copy geotransform.
    9799          64 :     bool bGotGeoTransform = false;
    9800          64 :     GDALGeoTransform gt;
    9801          64 :     CPLErr eErr = poSrcDS->GetGeoTransform(gt);
    9802          64 :     if (eErr == CE_None)
    9803             :     {
    9804          46 :         poDS->SetGeoTransform(gt);
    9805             :         // Disable AddProjectionVars() from being called.
    9806          46 :         bGotGeoTransform = true;
    9807          46 :         poDS->m_bHasGeoTransform = false;
    9808             :     }
    9809             : 
    9810             :     // Copy projection.
    9811          64 :     void *pScaledProgress = nullptr;
    9812          64 :     if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
    9813             :     {
    9814          47 :         poDS->SetProjection(pszWKT ? pszWKT : "");
    9815             : 
    9816             :         // Now we can call AddProjectionVars() directly.
    9817          47 :         poDS->m_bHasGeoTransform = bGotGeoTransform;
    9818          47 :         poDS->AddProjectionVars(true, nullptr, nullptr);
    9819             :         pScaledProgress =
    9820          47 :             GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
    9821          47 :         poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
    9822          47 :         GDALDestroyScaledProgress(pScaledProgress);
    9823             :     }
    9824             :     else
    9825             :     {
    9826          17 :         poDS->bBottomUp =
    9827          17 :             CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
    9828          17 :         if (papszGeolocationInfo)
    9829             :         {
    9830           4 :             poDS->AddProjectionVars(true, nullptr, nullptr);
    9831           4 :             poDS->AddProjectionVars(false, nullptr, nullptr);
    9832             :         }
    9833             :     }
    9834             : 
    9835             :     // Save X,Y dim positions.
    9836          64 :     panDimIds[nDim - 1] = poDS->nXDimID;
    9837          64 :     panBandDimPos[0] = nDim - 1;
    9838          64 :     panDimIds[nDim - 2] = poDS->nYDimID;
    9839          64 :     panBandDimPos[1] = nDim - 2;
    9840             : 
    9841             :     // Write extra dim values - after projection for optimization.
    9842          64 :     if (nDim > 2)
    9843             :     {
    9844             :         // Make sure we are in data mode.
    9845           5 :         poDS->SetDefineMode(false);
    9846          13 :         for (int i = aosExtraDimNames.size() - 1; i >= 0; i--)
    9847             :         {
    9848             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9849           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
    9850             :                      aosExtraDimNames[i]);
    9851           8 :             if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
    9852             :             {
    9853           8 :                 NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
    9854           8 :                              poSrcDS->GetMetadataItem(szTemp));
    9855             :             }
    9856             :         }
    9857             :     }
    9858             : 
    9859          64 :     pfnProgress(0.25, nullptr, pProgressData);
    9860             : 
    9861             :     // Define Bands.
    9862          64 :     netCDFRasterBand *poBand = nullptr;
    9863          64 :     int nBandID = -1;
    9864             : 
    9865         161 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9866             :     {
    9867          97 :         CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
    9868             :                  nBands, nDim);
    9869             : 
    9870          97 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9871          97 :         eDT = poSrcBand->GetRasterDataType();
    9872             : 
    9873             :         // Get var name from NETCDF_VARNAME.
    9874             :         const char *pszNETCDF_VARNAME =
    9875          97 :             poSrcBand->GetMetadataItem("NETCDF_VARNAME");
    9876             :         char szBandName[NC_MAX_NAME + 1];
    9877          97 :         if (!aosBandNames.empty())
    9878             :         {
    9879           2 :             snprintf(szBandName, sizeof(szBandName), "%s",
    9880             :                      aosBandNames[iBand - 1]);
    9881             :         }
    9882          95 :         else if (pszNETCDF_VARNAME)
    9883             :         {
    9884          32 :             if (nBands > 1 && aosExtraDimNames.empty())
    9885           0 :                 snprintf(szBandName, sizeof(szBandName), "%s%d",
    9886             :                          pszNETCDF_VARNAME, iBand);
    9887             :             else
    9888          32 :                 snprintf(szBandName, sizeof(szBandName), "%s",
    9889             :                          pszNETCDF_VARNAME);
    9890             :         }
    9891             :         else
    9892             :         {
    9893          63 :             szBandName[0] = '\0';
    9894             :         }
    9895             : 
    9896             :         // Get long_name from <var>#long_name.
    9897          97 :         const char *pszLongName = "";
    9898          97 :         if (pszNETCDF_VARNAME)
    9899             :         {
    9900             :             pszLongName =
    9901          64 :                 poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
    9902          32 :                                              .append("#")
    9903          32 :                                              .append(CF_LNG_NAME)
    9904          32 :                                              .c_str());
    9905          32 :             if (!pszLongName)
    9906          25 :                 pszLongName = "";
    9907             :         }
    9908             : 
    9909          97 :         constexpr bool bSignedData = false;
    9910             : 
    9911          97 :         if (nDim > 2)
    9912          27 :             poBand = new netCDFRasterBand(
    9913          27 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9914             :                 bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
    9915          27 :                 panBandZLev, panBandDimPos, panDimIds);
    9916             :         else
    9917          70 :             poBand = new netCDFRasterBand(
    9918          70 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9919          70 :                 bSignedData, szBandName, pszLongName);
    9920             : 
    9921          97 :         poDS->SetBand(iBand, poBand);
    9922             : 
    9923             :         // Set nodata value, if any.
    9924          97 :         GDALCopyNoDataValue(poBand, poSrcBand);
    9925             : 
    9926             :         // Copy Metadata for band.
    9927          97 :         CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
    9928             :                      poDS->cdfid, poBand->nZId);
    9929             : 
    9930             :         // If more than 2D pass the first band's netcdf var ID to subsequent
    9931             :         // bands.
    9932          97 :         if (nDim > 2)
    9933          27 :             nBandID = poBand->nZId;
    9934             :     }
    9935             : 
    9936             :     // Write projection variable to band variable.
    9937          64 :     poDS->AddGridMappingRef();
    9938             : 
    9939          64 :     pfnProgress(0.5, nullptr, pProgressData);
    9940             : 
    9941             :     // Write bands.
    9942             : 
    9943             :     // Make sure we are in data mode.
    9944          64 :     poDS->SetDefineMode(false);
    9945             : 
    9946          64 :     double dfTemp = 0.5;
    9947             : 
    9948          64 :     eErr = CE_None;
    9949             : 
    9950         161 :     for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
    9951             :     {
    9952          97 :         const double dfTemp2 = dfTemp + 0.4 / nBands;
    9953          97 :         pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
    9954             :                                                    pProgressData);
    9955          97 :         dfTemp = dfTemp2;
    9956             : 
    9957          97 :         CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
    9958             : 
    9959          97 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9960          97 :         eDT = poSrcBand->GetRasterDataType();
    9961             : 
    9962          97 :         GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
    9963             : 
    9964             :         // Copy band data.
    9965          97 :         if (eDT == GDT_UInt8)
    9966             :         {
    9967          57 :             CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
    9968          57 :             eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
    9969             :                                        GDALScaledProgress, pScaledProgress);
    9970             :         }
    9971          40 :         else if (eDT == GDT_Int8)
    9972             :         {
    9973           1 :             CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
    9974           1 :             eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
    9975             :                                        GDALScaledProgress, pScaledProgress);
    9976             :         }
    9977          39 :         else if (eDT == GDT_UInt16)
    9978             :         {
    9979           2 :             CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
    9980           2 :             eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9981             :                                         GDALScaledProgress, pScaledProgress);
    9982             :         }
    9983          37 :         else if (eDT == GDT_Int16)
    9984             :         {
    9985           5 :             CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
    9986           5 :             eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9987             :                                          GDALScaledProgress, pScaledProgress);
    9988             :         }
    9989          32 :         else if (eDT == GDT_UInt32)
    9990             :         {
    9991           2 :             CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
    9992           2 :             eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9993             :                                          GDALScaledProgress, pScaledProgress);
    9994             :         }
    9995          30 :         else if (eDT == GDT_Int32)
    9996             :         {
    9997          18 :             CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
    9998          18 :             eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9999             :                                         GDALScaledProgress, pScaledProgress);
   10000             :         }
   10001          12 :         else if (eDT == GDT_UInt64)
   10002             :         {
   10003           2 :             CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
   10004           2 :             eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
   10005             :                                                nYSize, GDALScaledProgress,
   10006             :                                                pScaledProgress);
   10007             :         }
   10008          10 :         else if (eDT == GDT_Int64)
   10009             :         {
   10010           2 :             CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
   10011             :             eErr =
   10012           2 :                 NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
   10013             :                                            GDALScaledProgress, pScaledProgress);
   10014             :         }
   10015           8 :         else if (eDT == GDT_Float32)
   10016             :         {
   10017           6 :             CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
   10018           6 :             eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
   10019             :                                        GDALScaledProgress, pScaledProgress);
   10020             :         }
   10021           2 :         else if (eDT == GDT_Float64)
   10022             :         {
   10023           2 :             CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
   10024           2 :             eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
   10025             :                                         GDALScaledProgress, pScaledProgress);
   10026             :         }
   10027             :         else
   10028             :         {
   10029           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10030             :                      "The NetCDF driver does not support GDAL data type %d",
   10031             :                      eDT);
   10032             :         }
   10033             : 
   10034          97 :         GDALDestroyScaledProgress(pScaledProgress);
   10035             :     }
   10036             : 
   10037          64 :     delete (poDS);
   10038             : 
   10039          64 :     CPLFree(panDimIds);
   10040          64 :     CPLFree(panBandDimPos);
   10041          64 :     CPLFree(panBandZLev);
   10042          64 :     CPLFree(panDimVarIds);
   10043             : 
   10044          64 :     if (eErr != CE_None)
   10045           0 :         return nullptr;
   10046             : 
   10047          64 :     pfnProgress(0.95, nullptr, pProgressData);
   10048             : 
   10049             :     // Re-open dataset so we can return it.
   10050         128 :     CPLStringList aosOpenOptions;
   10051          64 :     aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
   10052          64 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
   10053          64 :     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
   10054          64 :     oOpenInfo.papszOpenOptions = aosOpenOptions.List();
   10055          64 :     auto poRetDS = Open(&oOpenInfo);
   10056             : 
   10057             :     // PAM cloning is disabled. See bug #4244.
   10058             :     // if( poDS )
   10059             :     //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
   10060             : 
   10061          64 :     pfnProgress(1.0, nullptr, pProgressData);
   10062             : 
   10063          64 :     return poRetDS;
   10064             : }
   10065             : 
   10066             : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
   10067             : // May not be known when Create() is called, see AddProjectionVars().
   10068         314 : void netCDFDataset::ProcessCreationOptions()
   10069             : {
   10070             :     const char *pszConfig =
   10071         314 :         CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
   10072         314 :     if (pszConfig != nullptr)
   10073             :     {
   10074           4 :         if (oWriterConfig.Parse(pszConfig))
   10075             :         {
   10076             :             // Override dataset creation options from the config file
   10077           2 :             for (const auto &[osName, osValue] :
   10078           3 :                  oWriterConfig.m_oDatasetCreationOptions)
   10079             :             {
   10080           1 :                 papszCreationOptions =
   10081           1 :                     CSLSetNameValue(papszCreationOptions, osName, osValue);
   10082             :             }
   10083             :         }
   10084             :     }
   10085             : 
   10086             :     // File format.
   10087         314 :     eFormat = NCDF_FORMAT_NC;
   10088         314 :     const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
   10089         314 :     if (pszValue != nullptr)
   10090             :     {
   10091         146 :         if (EQUAL(pszValue, "NC"))
   10092             :         {
   10093           3 :             eFormat = NCDF_FORMAT_NC;
   10094             :         }
   10095             : #ifdef NETCDF_HAS_NC2
   10096         143 :         else if (EQUAL(pszValue, "NC2"))
   10097             :         {
   10098           1 :             eFormat = NCDF_FORMAT_NC2;
   10099             :         }
   10100             : #endif
   10101         142 :         else if (EQUAL(pszValue, "NC4"))
   10102             :         {
   10103         138 :             eFormat = NCDF_FORMAT_NC4;
   10104             :         }
   10105           4 :         else if (EQUAL(pszValue, "NC4C"))
   10106             :         {
   10107           4 :             eFormat = NCDF_FORMAT_NC4C;
   10108             :         }
   10109             :         else
   10110             :         {
   10111           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10112             :                      "FORMAT=%s in not supported, using the default NC format.",
   10113             :                      pszValue);
   10114             :         }
   10115             :     }
   10116             : 
   10117             :     // COMPRESS option.
   10118         314 :     pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
   10119         314 :     if (pszValue != nullptr)
   10120             :     {
   10121           3 :         if (EQUAL(pszValue, "NONE"))
   10122             :         {
   10123           1 :             eCompress = NCDF_COMPRESS_NONE;
   10124             :         }
   10125           2 :         else if (EQUAL(pszValue, "DEFLATE"))
   10126             :         {
   10127           2 :             eCompress = NCDF_COMPRESS_DEFLATE;
   10128           2 :             if (!((eFormat == NCDF_FORMAT_NC4) ||
   10129           2 :                   (eFormat == NCDF_FORMAT_NC4C)))
   10130             :             {
   10131           1 :                 CPLError(CE_Warning, CPLE_IllegalArg,
   10132             :                          "NOTICE: Format set to NC4C because compression is "
   10133             :                          "set to DEFLATE.");
   10134           1 :                 eFormat = NCDF_FORMAT_NC4C;
   10135             :             }
   10136             :         }
   10137             :         else
   10138             :         {
   10139           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10140             :                      "COMPRESS=%s is not supported.", pszValue);
   10141             :         }
   10142             :     }
   10143             : 
   10144             :     // ZLEVEL option.
   10145         314 :     pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
   10146         314 :     if (pszValue != nullptr)
   10147             :     {
   10148           1 :         nZLevel = atoi(pszValue);
   10149           1 :         if (!(nZLevel >= 1 && nZLevel <= 9))
   10150             :         {
   10151           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10152             :                      "ZLEVEL=%s value not recognised, ignoring.", pszValue);
   10153           0 :             nZLevel = NCDF_DEFLATE_LEVEL;
   10154             :         }
   10155             :     }
   10156             : 
   10157             :     // CHUNKING option.
   10158         314 :     bChunking =
   10159         314 :         CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
   10160             : 
   10161             :     // MULTIPLE_LAYERS option.
   10162             :     const char *pszMultipleLayerBehavior =
   10163         314 :         CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
   10164         628 :     const char *pszGeometryEnc = CSLFetchNameValueDef(
   10165         314 :         papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
   10166         314 :     if (EQUAL(pszMultipleLayerBehavior, "NO") ||
   10167           4 :         EQUAL(pszGeometryEnc, "CF_1.8"))
   10168             :     {
   10169         310 :         eMultipleLayerBehavior = SINGLE_LAYER;
   10170             :     }
   10171           4 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
   10172             :     {
   10173           3 :         eMultipleLayerBehavior = SEPARATE_FILES;
   10174             :     }
   10175           1 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
   10176             :     {
   10177           1 :         if (eFormat == NCDF_FORMAT_NC4)
   10178             :         {
   10179           1 :             eMultipleLayerBehavior = SEPARATE_GROUPS;
   10180             :         }
   10181             :         else
   10182             :         {
   10183           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10184             :                      "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
   10185             :                      pszMultipleLayerBehavior);
   10186             :         }
   10187             :     }
   10188             :     else
   10189             :     {
   10190           0 :         CPLError(CE_Warning, CPLE_IllegalArg,
   10191             :                  "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
   10192             :     }
   10193             : 
   10194             :     // Set nCreateMode based on eFormat.
   10195         314 :     switch (eFormat)
   10196             :     {
   10197             : #ifdef NETCDF_HAS_NC2
   10198           1 :         case NCDF_FORMAT_NC2:
   10199           1 :             nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
   10200           1 :             break;
   10201             : #endif
   10202         138 :         case NCDF_FORMAT_NC4:
   10203         138 :             nCreateMode = NC_CLOBBER | NC_NETCDF4;
   10204         138 :             break;
   10205           5 :         case NCDF_FORMAT_NC4C:
   10206           5 :             nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
   10207           5 :             break;
   10208         170 :         case NCDF_FORMAT_NC:
   10209             :         default:
   10210         170 :             nCreateMode = NC_CLOBBER;
   10211         170 :             break;
   10212             :     }
   10213             : 
   10214         314 :     CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
   10215         314 :              eFormat, eCompress, nZLevel);
   10216         314 : }
   10217             : 
   10218         288 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) const
   10219             : {
   10220         288 :     if (eCompress == NCDF_COMPRESS_DEFLATE)
   10221             :     {
   10222             :         // Must set chunk size to avoid huge performance hit (set
   10223             :         // bChunkingArg=TRUE)
   10224             :         // perhaps another solution it to change the chunk cache?
   10225             :         // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
   10226             :         // TODO: make sure this is okay.
   10227           2 :         CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
   10228           2 :                  static_cast<int>(bChunkingArg), nZLevel);
   10229             : 
   10230           2 :         int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
   10231           2 :         NCDF_ERR(status);
   10232             : 
   10233           2 :         if (status == NC_NOERR && bChunkingArg && bChunking)
   10234             :         {
   10235             :             // set chunking to be 1 for all dims, except X dim
   10236             :             // size_t chunksize[] = { 1, (size_t)nRasterXSize };
   10237             :             size_t chunksize[MAX_NC_DIMS];
   10238             :             int nd;
   10239           2 :             nc_inq_varndims(cdfid, nVarId, &nd);
   10240           2 :             chunksize[0] = (size_t)1;
   10241           2 :             chunksize[1] = (size_t)1;
   10242           2 :             for (int i = 2; i < nd; i++)
   10243           0 :                 chunksize[i] = (size_t)1;
   10244           2 :             chunksize[nd - 1] = (size_t)nRasterXSize;
   10245             : 
   10246             :             // Config options just for testing purposes
   10247             :             const char *pszBlockXSize =
   10248           2 :                 CPLGetConfigOption("BLOCKXSIZE", nullptr);
   10249           2 :             if (pszBlockXSize)
   10250           0 :                 chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
   10251             : 
   10252             :             const char *pszBlockYSize =
   10253           2 :                 CPLGetConfigOption("BLOCKYSIZE", nullptr);
   10254           2 :             if (nd >= 2 && pszBlockYSize)
   10255           0 :                 chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
   10256             : 
   10257           2 :             CPLDebug("GDAL_netCDF",
   10258             :                      "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
   10259           2 :                      (long)chunksize[0], (long)chunksize[1],
   10260           2 :                      (long)chunksize[nd - 1], nd);
   10261             : #ifdef NCDF_DEBUG
   10262             :             for (int i = 0; i < nd; i++)
   10263             :                 CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
   10264             :                          chunksize[i]);
   10265             : #endif
   10266             : 
   10267           2 :             status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
   10268           2 :             NCDF_ERR(status);
   10269             :         }
   10270             :         else
   10271             :         {
   10272           0 :             CPLDebug("GDAL_netCDF", "chunksize not set");
   10273             :         }
   10274           2 :         return status;
   10275             :     }
   10276         286 :     return NC_NOERR;
   10277             : }
   10278             : 
   10279             : /************************************************************************/
   10280             : /*                          NCDFUnloadDriver()                          */
   10281             : /************************************************************************/
   10282             : 
   10283           8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
   10284             : {
   10285           8 :     if (hNCMutex != nullptr)
   10286           4 :         CPLDestroyMutex(hNCMutex);
   10287           8 :     hNCMutex = nullptr;
   10288           8 : }
   10289             : 
   10290             : /************************************************************************/
   10291             : /*                        GDALRegister_netCDF()                         */
   10292             : /************************************************************************/
   10293             : 
   10294             : class GDALnetCDFDriver final : public GDALDriver
   10295             : {
   10296             :   public:
   10297          19 :     GDALnetCDFDriver() = default;
   10298             : 
   10299             :     const char *GetMetadataItem(const char *pszName,
   10300             :                                 const char *pszDomain) override;
   10301             : 
   10302          93 :     CSLConstList GetMetadata(const char *pszDomain) override
   10303             :     {
   10304         186 :         std::lock_guard oLock(m_oMutex);
   10305          93 :         InitializeDCAPVirtualIO();
   10306         186 :         return GDALDriver::GetMetadata(pszDomain);
   10307             :     }
   10308             : 
   10309             :   private:
   10310             :     std::recursive_mutex m_oMutex{};
   10311             :     bool m_bInitialized = false;
   10312             : 
   10313         106 :     void InitializeDCAPVirtualIO()
   10314             :     {
   10315         106 :         if (!m_bInitialized)
   10316             :         {
   10317          12 :             m_bInitialized = true;
   10318             : 
   10319             : #ifdef ENABLE_UFFD
   10320          12 :             if (CPLIsUserFaultMappingSupported())
   10321             :             {
   10322          12 :                 SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
   10323             :             }
   10324             : #endif
   10325             :         }
   10326         106 :     }
   10327             : };
   10328             : 
   10329        1412 : const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName,
   10330             :                                               const char *pszDomain)
   10331             : {
   10332        2824 :     std::lock_guard oLock(m_oMutex);
   10333        1412 :     if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
   10334             :     {
   10335          13 :         InitializeDCAPVirtualIO();
   10336             :     }
   10337        2824 :     return GDALDriver::GetMetadataItem(pszName, pszDomain);
   10338             : }
   10339             : 
   10340          19 : void GDALRegister_netCDF()
   10341             : 
   10342             : {
   10343          19 :     if (!GDAL_CHECK_VERSION("netCDF driver"))
   10344           0 :         return;
   10345             : 
   10346          19 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
   10347           0 :         return;
   10348             : 
   10349          19 :     GDALDriver *poDriver = new GDALnetCDFDriver();
   10350          19 :     netCDFDriverSetCommonMetadata(poDriver);
   10351             : 
   10352          19 :     poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
   10353          19 :                               GDAL_DEFAULT_NCDF_CONVENTIONS);
   10354          19 :     poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
   10355             : 
   10356             :     // Set pfns and register driver.
   10357          19 :     poDriver->pfnOpen = netCDFDataset::Open;
   10358          19 :     poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
   10359          19 :     poDriver->pfnCreate = netCDFDataset::Create;
   10360          19 :     poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
   10361          19 :     poDriver->pfnUnloadDriver = NCDFUnloadDriver;
   10362             : 
   10363          19 :     GetGDALDriverManager()->RegisterDriver(poDriver);
   10364             : }
   10365             : 
   10366             : /************************************************************************/
   10367             : /*                            New functions                             */
   10368             : /************************************************************************/
   10369             : 
   10370             : /* Test for GDAL version string >= target */
   10371         257 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
   10372             : {
   10373             : 
   10374             :     // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
   10375         257 :     if (pszVersion == nullptr || EQUAL(pszVersion, ""))
   10376           0 :         return false;
   10377         257 :     else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
   10378           0 :         return false;
   10379             :     // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
   10380         257 :     else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
   10381           0 :         return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
   10382         257 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
   10383           2 :         return nTarget <= 1900;
   10384         255 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
   10385           0 :         return nTarget <= 1800;
   10386             : 
   10387         255 :     const CPLStringList aosTokens(CSLTokenizeString2(pszVersion + 5, ".", 0));
   10388             : 
   10389         255 :     int nVersions[] = {0, 0, 0, 0};
   10390        1020 :     for (int iToken = 0; iToken < std::min(4, aosTokens.size()); iToken++)
   10391             :     {
   10392         765 :         nVersions[iToken] = atoi(aosTokens[iToken]);
   10393         765 :         if (nVersions[iToken] < 0)
   10394           0 :             nVersions[iToken] = 0;
   10395         765 :         else if (nVersions[iToken] > 99)
   10396           0 :             nVersions[iToken] = 99;
   10397             :     }
   10398             : 
   10399         255 :     int nVersion = 0;
   10400         255 :     if (nVersions[0] > 1 || nVersions[1] >= 10)
   10401         255 :         nVersion =
   10402         255 :             GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
   10403             :     else
   10404           0 :         nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
   10405           0 :                    nVersions[2] * 10 + nVersions[3];
   10406             : 
   10407         255 :     return nTarget <= nVersion;
   10408             : }
   10409             : 
   10410             : // Add Conventions, GDAL version and history.
   10411         173 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
   10412             :                                bool bWriteGDALVersion, bool bWriteGDALHistory,
   10413             :                                const char *pszOldHist,
   10414             :                                const char *pszFunctionName,
   10415             :                                const char *pszCFVersion)
   10416             : {
   10417         173 :     if (pszCFVersion == nullptr)
   10418             :     {
   10419          48 :         pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
   10420             :     }
   10421         173 :     int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
   10422             :                                  strlen(pszCFVersion), pszCFVersion);
   10423         173 :     NCDF_ERR(status);
   10424             : 
   10425         173 :     if (bWriteGDALVersion)
   10426             :     {
   10427         171 :         const char *pszNCDF_GDAL = GDALVersionInfo("--version");
   10428         171 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
   10429             :                                  strlen(pszNCDF_GDAL), pszNCDF_GDAL);
   10430         171 :         NCDF_ERR(status);
   10431             :     }
   10432             : 
   10433         173 :     if (bWriteGDALHistory)
   10434             :     {
   10435             :         // Add history.
   10436         342 :         CPLString osTmp;
   10437             : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
   10438             :         if (!EQUAL(GDALGetCmdLine(), ""))
   10439             :             osTmp = GDALGetCmdLine();
   10440             :         else
   10441             :             osTmp =
   10442             :                 CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10443             : #else
   10444         171 :         osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10445             : #endif
   10446             : 
   10447         171 :         NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
   10448             :     }
   10449           2 :     else if (pszOldHist != nullptr)
   10450             :     {
   10451           0 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10452             :                                  strlen(pszOldHist), pszOldHist);
   10453           0 :         NCDF_ERR(status);
   10454             :     }
   10455         173 : }
   10456             : 
   10457             : // Code taken from cdo and libcdi, used for writing the history attribute.
   10458             : 
   10459             : // void cdoDefHistory(int fileID, char *histstring)
   10460         171 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
   10461             :                            const char *pszOldHist)
   10462             : {
   10463             :     // Check pszOldHist - as if there was no previous history, it will be
   10464             :     // a null pointer - if so set as empty.
   10465         171 :     if (nullptr == pszOldHist)
   10466             :     {
   10467          59 :         pszOldHist = "";
   10468             :     }
   10469             : 
   10470             :     char strtime[32];
   10471         171 :     strtime[0] = '\0';
   10472             : 
   10473         171 :     time_t tp = time(nullptr);
   10474         171 :     if (tp != -1)
   10475             :     {
   10476             :         struct tm ltime;
   10477         171 :         VSILocalTime(&tp, &ltime);
   10478         171 :         (void)strftime(strtime, sizeof(strtime),
   10479             :                        "%a %b %d %H:%M:%S %Y: ", &ltime);
   10480             :     }
   10481             : 
   10482             :     // status = nc_get_att_text(fpImage, NC_GLOBAL,
   10483             :     //                           "history", pszOldHist);
   10484             :     // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
   10485             : 
   10486         171 :     size_t nNewHistSize =
   10487         171 :         strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
   10488             :     char *pszNewHist =
   10489         171 :         static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
   10490             : 
   10491         171 :     strcpy(pszNewHist, strtime);
   10492         171 :     strcat(pszNewHist, pszAddHist);
   10493             : 
   10494             :     // int disableHistory = FALSE;
   10495             :     // if( !disableHistory )
   10496             :     {
   10497         171 :         if (!EQUAL(pszOldHist, ""))
   10498           3 :             strcat(pszNewHist, "\n");
   10499         171 :         strcat(pszNewHist, pszOldHist);
   10500             :     }
   10501             : 
   10502         171 :     const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10503             :                                        strlen(pszNewHist), pszNewHist);
   10504         171 :     NCDF_ERR(status);
   10505             : 
   10506         171 :     CPLFree(pszNewHist);
   10507         171 : }
   10508             : 
   10509        6636 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
   10510             :                              size_t *nDestSize)
   10511             : {
   10512             :     /* Reallocate the data string until the content fits */
   10513        6636 :     while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
   10514             :     {
   10515         411 :         (*nDestSize) *= 2;
   10516         411 :         *ppszDest = static_cast<char *>(
   10517         411 :             CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
   10518             : #ifdef NCDF_DEBUG
   10519             :         CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
   10520             :                  (*nDestSize) / 2, *nDestSize);
   10521             : #endif
   10522             :     }
   10523        6225 :     strcat(*ppszDest, pszSrc);
   10524             : 
   10525        6225 :     return CE_None;
   10526             : }
   10527             : 
   10528             : /* helper function for NCDFGetAttr() */
   10529             : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
   10530             : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
   10531             : /* *ppszValue is the responsibility of the caller and must be freed */
   10532       68413 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
   10533             :                            double *pdfValue, char **ppszValue)
   10534             : {
   10535       68413 :     nc_type nAttrType = NC_NAT;
   10536       68413 :     size_t nAttrLen = 0;
   10537             : 
   10538       68413 :     if (ppszValue)
   10539       67251 :         *ppszValue = nullptr;
   10540             : 
   10541       68413 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10542       68413 :     if (status != NC_NOERR)
   10543       36601 :         return CE_Failure;
   10544             : 
   10545             : #ifdef NCDF_DEBUG
   10546             :     CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
   10547             :              nAttrLen, nAttrType);
   10548             : #endif
   10549       31812 :     if (nAttrLen == 0 && nAttrType != NC_CHAR)
   10550           1 :         return CE_Failure;
   10551             : 
   10552             :     /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
   10553       31811 :     size_t nAttrValueSize = nAttrLen + 1;
   10554       31811 :     if (nAttrType != NC_CHAR && nAttrValueSize < 10)
   10555        3572 :         nAttrValueSize = 10;
   10556       31811 :     if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
   10557        1722 :         nAttrValueSize = 20;
   10558       31811 :     if (nAttrType == NC_INT64 && nAttrValueSize < 20)
   10559          22 :         nAttrValueSize = 22;
   10560             :     char *pszAttrValue =
   10561       31811 :         static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
   10562       31811 :     *pszAttrValue = '\0';
   10563             : 
   10564       31811 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10565         638 :         NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
   10566             : 
   10567       31811 :     double dfValue = 0.0;
   10568       31811 :     size_t m = 0;
   10569             :     char szTemp[256];
   10570       31811 :     bool bSetDoubleFromStr = false;
   10571             : 
   10572       31811 :     switch (nAttrType)
   10573             :     {
   10574       28237 :         case NC_CHAR:
   10575       28237 :             CPL_IGNORE_RET_VAL(
   10576       28237 :                 nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
   10577       28237 :             pszAttrValue[nAttrLen] = '\0';
   10578       28237 :             bSetDoubleFromStr = true;
   10579       28237 :             dfValue = 0.0;
   10580       28237 :             break;
   10581          94 :         case NC_BYTE:
   10582             :         {
   10583             :             signed char *pscTemp = static_cast<signed char *>(
   10584          94 :                 CPLCalloc(nAttrLen, sizeof(signed char)));
   10585          94 :             nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
   10586          94 :             dfValue = static_cast<double>(pscTemp[0]);
   10587          94 :             if (nAttrLen > 1)
   10588             :             {
   10589          24 :                 for (m = 0; m < nAttrLen - 1; m++)
   10590             :                 {
   10591          13 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10592          13 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10593             :                 }
   10594             :             }
   10595          94 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10596          94 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10597          94 :             CPLFree(pscTemp);
   10598          94 :             break;
   10599             :         }
   10600         523 :         case NC_SHORT:
   10601             :         {
   10602             :             short *psTemp =
   10603         523 :                 static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
   10604         523 :             nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
   10605         523 :             dfValue = static_cast<double>(psTemp[0]);
   10606         523 :             if (nAttrLen > 1)
   10607             :             {
   10608         840 :                 for (m = 0; m < nAttrLen - 1; m++)
   10609             :                 {
   10610         420 :                     snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10611         420 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10612             :                 }
   10613             :             }
   10614         523 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10615         523 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10616         523 :             CPLFree(psTemp);
   10617         523 :             break;
   10618             :         }
   10619         528 :         case NC_INT:
   10620             :         {
   10621         528 :             int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10622         528 :             nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
   10623         528 :             dfValue = static_cast<double>(pnTemp[0]);
   10624         528 :             if (nAttrLen > 1)
   10625             :             {
   10626         218 :                 for (m = 0; m < nAttrLen - 1; m++)
   10627             :                 {
   10628         139 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10629         139 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10630             :                 }
   10631             :             }
   10632         528 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10633         528 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10634         528 :             CPLFree(pnTemp);
   10635         528 :             break;
   10636             :         }
   10637         395 :         case NC_FLOAT:
   10638             :         {
   10639             :             float *pfTemp =
   10640         395 :                 static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10641         395 :             nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
   10642         395 :             dfValue = static_cast<double>(pfTemp[0]);
   10643         395 :             if (nAttrLen > 1)
   10644             :             {
   10645          60 :                 for (m = 0; m < nAttrLen - 1; m++)
   10646             :                 {
   10647          30 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10648          30 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10649             :                 }
   10650             :             }
   10651         395 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10652         395 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10653         395 :             CPLFree(pfTemp);
   10654         395 :             break;
   10655             :         }
   10656        1722 :         case NC_DOUBLE:
   10657             :         {
   10658             :             double *pdfTemp =
   10659        1722 :                 static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10660        1722 :             nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
   10661        1722 :             dfValue = pdfTemp[0];
   10662        1722 :             if (nAttrLen > 1)
   10663             :             {
   10664         166 :                 for (m = 0; m < nAttrLen - 1; m++)
   10665             :                 {
   10666          90 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10667          90 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10668             :                 }
   10669             :             }
   10670        1722 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10671        1722 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10672        1722 :             CPLFree(pdfTemp);
   10673        1722 :             break;
   10674             :         }
   10675         167 :         case NC_STRING:
   10676             :         {
   10677             :             char **ppszTemp =
   10678         167 :                 static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
   10679         167 :             nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
   10680         167 :             bSetDoubleFromStr = true;
   10681         167 :             dfValue = 0.0;
   10682         167 :             if (nAttrLen > 1)
   10683             :             {
   10684          19 :                 for (m = 0; m < nAttrLen - 1; m++)
   10685             :                 {
   10686          12 :                     NCDFSafeStrcat(&pszAttrValue,
   10687          12 :                                    ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10688             :                                    &nAttrValueSize);
   10689          12 :                     NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
   10690             :                 }
   10691             :             }
   10692         167 :             NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10693             :                            &nAttrValueSize);
   10694         167 :             nc_free_string(nAttrLen, ppszTemp);
   10695         167 :             CPLFree(ppszTemp);
   10696         167 :             break;
   10697             :         }
   10698          28 :         case NC_UBYTE:
   10699             :         {
   10700             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   10701          28 :                 CPLCalloc(nAttrLen, sizeof(unsigned char)));
   10702          28 :             nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
   10703          28 :             dfValue = static_cast<double>(pucTemp[0]);
   10704          28 :             if (nAttrLen > 1)
   10705             :             {
   10706           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10707             :                 {
   10708           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10709           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10710             :                 }
   10711             :             }
   10712          28 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10713          28 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10714          28 :             CPLFree(pucTemp);
   10715          28 :             break;
   10716             :         }
   10717          26 :         case NC_USHORT:
   10718             :         {
   10719             :             unsigned short *pusTemp = static_cast<unsigned short *>(
   10720          26 :                 CPLCalloc(nAttrLen, sizeof(unsigned short)));
   10721          26 :             nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
   10722          26 :             dfValue = static_cast<double>(pusTemp[0]);
   10723          26 :             if (nAttrLen > 1)
   10724             :             {
   10725          10 :                 for (m = 0; m < nAttrLen - 1; m++)
   10726             :                 {
   10727           5 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10728           5 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10729             :                 }
   10730             :             }
   10731          26 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10732          26 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10733          26 :             CPLFree(pusTemp);
   10734          26 :             break;
   10735             :         }
   10736          21 :         case NC_UINT:
   10737             :         {
   10738             :             unsigned int *punTemp =
   10739          21 :                 static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10740          21 :             nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
   10741          21 :             dfValue = static_cast<double>(punTemp[0]);
   10742          21 :             if (nAttrLen > 1)
   10743             :             {
   10744           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10745             :                 {
   10746           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   10747           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10748             :                 }
   10749             :             }
   10750          21 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   10751          21 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10752          21 :             CPLFree(punTemp);
   10753          21 :             break;
   10754             :         }
   10755          22 :         case NC_INT64:
   10756             :         {
   10757             :             GIntBig *panTemp =
   10758          22 :                 static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
   10759          22 :             nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
   10760          22 :             dfValue = static_cast<double>(panTemp[0]);
   10761          22 :             if (nAttrLen > 1)
   10762             :             {
   10763           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10764             :                 {
   10765           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
   10766           0 :                                 panTemp[m]);
   10767           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10768             :                 }
   10769             :             }
   10770          22 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
   10771          22 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10772          22 :             CPLFree(panTemp);
   10773          22 :             break;
   10774             :         }
   10775          22 :         case NC_UINT64:
   10776             :         {
   10777             :             GUIntBig *panTemp =
   10778          22 :                 static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
   10779          22 :             nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
   10780          22 :             dfValue = static_cast<double>(panTemp[0]);
   10781          22 :             if (nAttrLen > 1)
   10782             :             {
   10783           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10784             :                 {
   10785           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
   10786           0 :                                 panTemp[m]);
   10787           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10788             :                 }
   10789             :             }
   10790          22 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
   10791          22 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10792          22 :             CPLFree(panTemp);
   10793          22 :             break;
   10794             :         }
   10795          26 :         default:
   10796          26 :             CPLDebug("GDAL_netCDF",
   10797             :                      "NCDFGetAttr unsupported type %d for attribute %s",
   10798             :                      nAttrType, pszAttrName);
   10799          26 :             break;
   10800             :     }
   10801             : 
   10802       31811 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10803         638 :         NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
   10804             : 
   10805       31811 :     if (bSetDoubleFromStr)
   10806             :     {
   10807       28404 :         if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
   10808             :         {
   10809       28222 :             if (ppszValue == nullptr && pdfValue != nullptr)
   10810             :             {
   10811           1 :                 CPLFree(pszAttrValue);
   10812           1 :                 return CE_Failure;
   10813             :             }
   10814             :         }
   10815       28403 :         dfValue = CPLAtof(pszAttrValue);
   10816             :     }
   10817             : 
   10818             :     /* set return values */
   10819       31810 :     if (ppszValue)
   10820       31497 :         *ppszValue = pszAttrValue;
   10821             :     else
   10822         313 :         CPLFree(pszAttrValue);
   10823             : 
   10824       31810 :     if (pdfValue)
   10825         313 :         *pdfValue = dfValue;
   10826             : 
   10827       31810 :     return CE_None;
   10828             : }
   10829             : 
   10830             : /* sets pdfValue to first value found */
   10831        1162 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10832             :                    double *pdfValue)
   10833             : {
   10834        1162 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
   10835             : }
   10836             : 
   10837             : /* pszValue is the responsibility of the caller and must be freed */
   10838       67251 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10839             :                    char **pszValue)
   10840             : {
   10841       67251 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
   10842             : }
   10843             : 
   10844             : /* By default write NC_CHAR, but detect for int/float/double and */
   10845             : /* NC4 string arrays */
   10846         112 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10847             :                           const char *pszValue)
   10848             : {
   10849         112 :     int status = 0;
   10850         112 :     char *pszTemp = nullptr;
   10851             : 
   10852             :     /* get the attribute values as tokens */
   10853         224 :     CPLStringList aosValues = NCDFTokenizeArray(pszValue);
   10854         112 :     if (aosValues.empty())
   10855           0 :         return CE_Failure;
   10856             : 
   10857         112 :     size_t nAttrLen = aosValues.size();
   10858             : 
   10859             :     /* first detect type */
   10860         112 :     nc_type nAttrType = NC_CHAR;
   10861         112 :     nc_type nTmpAttrType = NC_CHAR;
   10862         237 :     for (size_t i = 0; i < nAttrLen; i++)
   10863             :     {
   10864         125 :         nTmpAttrType = NC_CHAR;
   10865         125 :         bool bFoundType = false;
   10866         125 :         errno = 0;
   10867         125 :         int nValue = static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   10868             :         /* test for int */
   10869             :         /* TODO test for Byte and short - can this be done safely? */
   10870         125 :         if (errno == 0 && aosValues[i] != pszTemp && *pszTemp == 0)
   10871             :         {
   10872             :             char szTemp[256];
   10873          19 :             CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
   10874          19 :             if (EQUAL(szTemp, aosValues[i]))
   10875             :             {
   10876          19 :                 bFoundType = true;
   10877          19 :                 nTmpAttrType = NC_INT;
   10878             :             }
   10879             :             else
   10880             :             {
   10881             :                 unsigned int unValue = static_cast<unsigned int>(
   10882           0 :                     strtoul(aosValues[i], &pszTemp, 10));
   10883           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
   10884           0 :                 if (EQUAL(szTemp, aosValues[i]))
   10885             :                 {
   10886           0 :                     bFoundType = true;
   10887           0 :                     nTmpAttrType = NC_UINT;
   10888             :                 }
   10889             :             }
   10890             :         }
   10891         125 :         if (!bFoundType)
   10892             :         {
   10893             :             /* test for double */
   10894         106 :             errno = 0;
   10895         106 :             double dfValue = CPLStrtod(aosValues[i], &pszTemp);
   10896         106 :             if ((errno == 0) && (aosValues[i] != pszTemp) && (*pszTemp == 0))
   10897             :             {
   10898             :                 // Test for float instead of double.
   10899             :                 // strtof() is C89, which is not available in MSVC.
   10900             :                 // See if we lose precision if we cast to float and write to
   10901             :                 // char*.
   10902          14 :                 float fValue = float(dfValue);
   10903             :                 char szTemp[256];
   10904          14 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
   10905          14 :                 if (EQUAL(szTemp, aosValues[i]))
   10906           8 :                     nTmpAttrType = NC_FLOAT;
   10907             :                 else
   10908           6 :                     nTmpAttrType = NC_DOUBLE;
   10909             :             }
   10910             :         }
   10911         125 :         if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
   10912         105 :              nTmpAttrType > nAttrType) ||
   10913         105 :             (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
   10914           5 :             (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
   10915          20 :             nAttrType = nTmpAttrType;
   10916             :     }
   10917             : 
   10918             : #ifdef DEBUG
   10919         112 :     if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
   10920             :     {
   10921           0 :         nAttrType = NC_DOUBLE;
   10922           0 :         nAttrLen = 0;
   10923             :     }
   10924             : #endif
   10925             : 
   10926             :     /* now write the data */
   10927         112 :     if (nAttrType == NC_CHAR)
   10928             :     {
   10929          92 :         int nTmpFormat = 0;
   10930          92 :         if (nAttrLen > 1)
   10931             :         {
   10932           0 :             status = nc_inq_format(nCdfId, &nTmpFormat);
   10933           0 :             NCDF_ERR(status);
   10934             :         }
   10935          92 :         if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
   10936           0 :             status =
   10937           0 :                 nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
   10938           0 :                                   const_cast<const char **>(aosValues.List()));
   10939             :         else
   10940          92 :             status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
   10941             :                                      strlen(pszValue), pszValue);
   10942          92 :         NCDF_ERR(status);
   10943             :     }
   10944             :     else
   10945             :     {
   10946          20 :         switch (nAttrType)
   10947             :         {
   10948          11 :             case NC_INT:
   10949             :             {
   10950             :                 int *pnTemp =
   10951          11 :                     static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10952          30 :                 for (size_t i = 0; i < nAttrLen; i++)
   10953             :                 {
   10954          19 :                     pnTemp[i] =
   10955          19 :                         static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   10956             :                 }
   10957          11 :                 status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
   10958             :                                         nAttrLen, pnTemp);
   10959          11 :                 NCDF_ERR(status);
   10960          11 :                 CPLFree(pnTemp);
   10961          11 :                 break;
   10962             :             }
   10963           0 :             case NC_UINT:
   10964             :             {
   10965             :                 unsigned int *punTemp = static_cast<unsigned int *>(
   10966           0 :                     CPLCalloc(nAttrLen, sizeof(unsigned int)));
   10967           0 :                 for (size_t i = 0; i < nAttrLen; i++)
   10968             :                 {
   10969           0 :                     punTemp[i] = static_cast<unsigned int>(
   10970           0 :                         strtol(aosValues[i], &pszTemp, 10));
   10971             :                 }
   10972           0 :                 status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
   10973             :                                          nAttrLen, punTemp);
   10974           0 :                 NCDF_ERR(status);
   10975           0 :                 CPLFree(punTemp);
   10976           0 :                 break;
   10977             :             }
   10978           6 :             case NC_FLOAT:
   10979             :             {
   10980             :                 float *pfTemp =
   10981           6 :                     static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10982          14 :                 for (size_t i = 0; i < nAttrLen; i++)
   10983             :                 {
   10984           8 :                     pfTemp[i] =
   10985           8 :                         static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
   10986             :                 }
   10987           6 :                 status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
   10988             :                                           nAttrLen, pfTemp);
   10989           6 :                 NCDF_ERR(status);
   10990           6 :                 CPLFree(pfTemp);
   10991           6 :                 break;
   10992             :             }
   10993           3 :             case NC_DOUBLE:
   10994             :             {
   10995             :                 double *pdfTemp =
   10996           3 :                     static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10997           9 :                 for (size_t i = 0; i < nAttrLen; i++)
   10998             :                 {
   10999           6 :                     pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
   11000             :                 }
   11001           3 :                 status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
   11002             :                                            NC_DOUBLE, nAttrLen, pdfTemp);
   11003           3 :                 NCDF_ERR(status);
   11004           3 :                 CPLFree(pdfTemp);
   11005           3 :                 break;
   11006             :             }
   11007           0 :             default:
   11008           0 :                 return CE_Failure;
   11009             :         }
   11010             :     }
   11011             : 
   11012         112 :     return CE_None;
   11013             : }
   11014             : 
   11015          78 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
   11016             : {
   11017             :     /* get var information */
   11018          78 :     int nVarDimId = -1;
   11019          78 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   11020          78 :     if (status != NC_NOERR || nVarDimId != 1)
   11021           0 :         return CE_Failure;
   11022             : 
   11023          78 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11024          78 :     if (status != NC_NOERR)
   11025           0 :         return CE_Failure;
   11026             : 
   11027          78 :     nc_type nVarType = NC_NAT;
   11028          78 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11029          78 :     if (status != NC_NOERR)
   11030           0 :         return CE_Failure;
   11031             : 
   11032          78 :     size_t nVarLen = 0;
   11033          78 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11034          78 :     if (status != NC_NOERR)
   11035           0 :         return CE_Failure;
   11036             : 
   11037          78 :     size_t start[1] = {0};
   11038          78 :     size_t count[1] = {nVarLen};
   11039             : 
   11040             :     /* Allocate guaranteed minimum size */
   11041          78 :     size_t nVarValueSize = NCDF_MAX_STR_LEN;
   11042             :     char *pszVarValue =
   11043          78 :         static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
   11044          78 :     *pszVarValue = '\0';
   11045             : 
   11046          78 :     if (nVarLen == 0)
   11047             :     {
   11048             :         /* set return values */
   11049           1 :         *pszValue = pszVarValue;
   11050             : 
   11051           1 :         return CE_None;
   11052             :     }
   11053             : 
   11054          77 :     if (nVarLen > 1 && nVarType != NC_CHAR)
   11055          42 :         NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
   11056             : 
   11057          77 :     switch (nVarType)
   11058             :     {
   11059           0 :         case NC_CHAR:
   11060           0 :             nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
   11061           0 :             pszVarValue[nVarLen] = '\0';
   11062           0 :             break;
   11063           0 :         case NC_BYTE:
   11064             :         {
   11065             :             signed char *pscTemp = static_cast<signed char *>(
   11066           0 :                 CPLCalloc(nVarLen, sizeof(signed char)));
   11067           0 :             nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11068             :             char szTemp[256];
   11069           0 :             size_t m = 0;
   11070           0 :             for (; m < nVarLen - 1; m++)
   11071             :             {
   11072           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   11073           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11074             :             }
   11075           0 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   11076           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11077           0 :             CPLFree(pscTemp);
   11078           0 :             break;
   11079             :         }
   11080           0 :         case NC_SHORT:
   11081             :         {
   11082             :             short *psTemp =
   11083           0 :                 static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11084           0 :             nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
   11085             :             char szTemp[256];
   11086           0 :             size_t m = 0;
   11087           0 :             for (; m < nVarLen - 1; m++)
   11088             :             {
   11089           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   11090           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11091             :             }
   11092           0 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   11093           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11094           0 :             CPLFree(psTemp);
   11095           0 :             break;
   11096             :         }
   11097          21 :         case NC_INT:
   11098             :         {
   11099          21 :             int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11100          21 :             nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11101             :             char szTemp[256];
   11102          21 :             size_t m = 0;
   11103          44 :             for (; m < nVarLen - 1; m++)
   11104             :             {
   11105          23 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   11106          23 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11107             :             }
   11108          21 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   11109          21 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11110          21 :             CPLFree(pnTemp);
   11111          21 :             break;
   11112             :         }
   11113           8 :         case NC_FLOAT:
   11114             :         {
   11115             :             float *pfTemp =
   11116           8 :                 static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11117           8 :             nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11118             :             char szTemp[256];
   11119           8 :             size_t m = 0;
   11120         325 :             for (; m < nVarLen - 1; m++)
   11121             :             {
   11122         317 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   11123         317 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11124             :             }
   11125           8 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   11126           8 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11127           8 :             CPLFree(pfTemp);
   11128           8 :             break;
   11129             :         }
   11130          47 :         case NC_DOUBLE:
   11131             :         {
   11132             :             double *pdfTemp =
   11133          47 :                 static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11134          47 :             nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11135             :             char szTemp[256];
   11136          47 :             size_t m = 0;
   11137         225 :             for (; m < nVarLen - 1; m++)
   11138             :             {
   11139         178 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   11140         178 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11141             :             }
   11142          47 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   11143          47 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11144          47 :             CPLFree(pdfTemp);
   11145          47 :             break;
   11146             :         }
   11147           0 :         case NC_STRING:
   11148             :         {
   11149             :             char **ppszTemp =
   11150           0 :                 static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
   11151           0 :             nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
   11152           0 :             size_t m = 0;
   11153           0 :             for (; m < nVarLen - 1; m++)
   11154             :             {
   11155           0 :                 NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11156           0 :                 NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
   11157             :             }
   11158           0 :             NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11159           0 :             nc_free_string(nVarLen, ppszTemp);
   11160           0 :             CPLFree(ppszTemp);
   11161           0 :             break;
   11162             :         }
   11163           0 :         case NC_UBYTE:
   11164             :         {
   11165             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   11166           0 :                 CPLCalloc(nVarLen, sizeof(unsigned char)));
   11167           0 :             nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
   11168             :             char szTemp[256];
   11169           0 :             size_t m = 0;
   11170           0 :             for (; m < nVarLen - 1; m++)
   11171             :             {
   11172           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   11173           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11174             :             }
   11175           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   11176           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11177           0 :             CPLFree(pucTemp);
   11178           0 :             break;
   11179             :         }
   11180           0 :         case NC_USHORT:
   11181             :         {
   11182             :             unsigned short *pusTemp = static_cast<unsigned short *>(
   11183           0 :                 CPLCalloc(nVarLen, sizeof(unsigned short)));
   11184           0 :             nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
   11185             :             char szTemp[256];
   11186           0 :             size_t m = 0;
   11187           0 :             for (; m < nVarLen - 1; m++)
   11188             :             {
   11189           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   11190           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11191             :             }
   11192           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   11193           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11194           0 :             CPLFree(pusTemp);
   11195           0 :             break;
   11196             :         }
   11197           0 :         case NC_UINT:
   11198             :         {
   11199             :             unsigned int *punTemp = static_cast<unsigned int *>(
   11200           0 :                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11201           0 :             nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
   11202             :             char szTemp[256];
   11203           0 :             size_t m = 0;
   11204           0 :             for (; m < nVarLen - 1; m++)
   11205             :             {
   11206           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   11207           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11208             :             }
   11209           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   11210           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11211           0 :             CPLFree(punTemp);
   11212           0 :             break;
   11213             :         }
   11214           1 :         case NC_INT64:
   11215             :         {
   11216             :             long long *pnTemp =
   11217           1 :                 static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
   11218           1 :             nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
   11219             :             char szTemp[256];
   11220           1 :             size_t m = 0;
   11221           2 :             for (; m < nVarLen - 1; m++)
   11222             :             {
   11223           1 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
   11224           1 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11225             :             }
   11226           1 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
   11227           1 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11228           1 :             CPLFree(pnTemp);
   11229           1 :             break;
   11230             :         }
   11231           0 :         case NC_UINT64:
   11232             :         {
   11233             :             unsigned long long *pnTemp = static_cast<unsigned long long *>(
   11234           0 :                 CPLCalloc(nVarLen, sizeof(unsigned long long)));
   11235           0 :             nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
   11236             :             char szTemp[256];
   11237           0 :             size_t m = 0;
   11238           0 :             for (; m < nVarLen - 1; m++)
   11239             :             {
   11240           0 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
   11241           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11242             :             }
   11243           0 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
   11244           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11245           0 :             CPLFree(pnTemp);
   11246           0 :             break;
   11247             :         }
   11248           0 :         default:
   11249           0 :             CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
   11250             :                      nVarType);
   11251           0 :             CPLFree(pszVarValue);
   11252           0 :             pszVarValue = nullptr;
   11253           0 :             break;
   11254             :     }
   11255             : 
   11256          77 :     if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
   11257          42 :         NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
   11258             : 
   11259             :     /* set return values */
   11260          77 :     *pszValue = pszVarValue;
   11261             : 
   11262          77 :     return CE_None;
   11263             : }
   11264             : 
   11265           8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
   11266             : {
   11267           8 :     if (EQUAL(pszValue, ""))
   11268           0 :         return CE_Failure;
   11269             : 
   11270             :     /* get var information */
   11271           8 :     int nVarDimId = -1;
   11272           8 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   11273           8 :     if (status != NC_NOERR || nVarDimId != 1)
   11274           0 :         return CE_Failure;
   11275             : 
   11276           8 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11277           8 :     if (status != NC_NOERR)
   11278           0 :         return CE_Failure;
   11279             : 
   11280           8 :     nc_type nVarType = NC_CHAR;
   11281           8 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11282           8 :     if (status != NC_NOERR)
   11283           0 :         return CE_Failure;
   11284             : 
   11285           8 :     size_t nVarLen = 0;
   11286           8 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11287           8 :     if (status != NC_NOERR)
   11288           0 :         return CE_Failure;
   11289             : 
   11290           8 :     size_t start[1] = {0};
   11291           8 :     size_t count[1] = {nVarLen};
   11292             : 
   11293             :     /* get the values as tokens */
   11294          16 :     CPLStringList aosValues = NCDFTokenizeArray(pszValue);
   11295           8 :     if (aosValues.empty())
   11296           0 :         return CE_Failure;
   11297             : 
   11298           8 :     nVarLen = aosValues.size();
   11299             : 
   11300             :     /* now write the data */
   11301           8 :     if (nVarType == NC_CHAR)
   11302             :     {
   11303           0 :         status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
   11304           0 :         NCDF_ERR(status);
   11305             :     }
   11306             :     else
   11307             :     {
   11308           8 :         switch (nVarType)
   11309             :         {
   11310           0 :             case NC_BYTE:
   11311             :             {
   11312             :                 signed char *pscTemp = static_cast<signed char *>(
   11313           0 :                     CPLCalloc(nVarLen, sizeof(signed char)));
   11314           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11315             :                 {
   11316           0 :                     char *pszTemp = nullptr;
   11317           0 :                     pscTemp[i] = static_cast<signed char>(
   11318           0 :                         strtol(aosValues[i], &pszTemp, 10));
   11319             :                 }
   11320             :                 status =
   11321           0 :                     nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11322           0 :                 NCDF_ERR(status);
   11323           0 :                 CPLFree(pscTemp);
   11324           0 :                 break;
   11325             :             }
   11326           0 :             case NC_SHORT:
   11327             :             {
   11328             :                 short *psTemp =
   11329           0 :                     static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11330           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11331             :                 {
   11332           0 :                     char *pszTemp = nullptr;
   11333           0 :                     psTemp[i] =
   11334           0 :                         static_cast<short>(strtol(aosValues[i], &pszTemp, 10));
   11335             :                 }
   11336             :                 status =
   11337           0 :                     nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
   11338           0 :                 NCDF_ERR(status);
   11339           0 :                 CPLFree(psTemp);
   11340           0 :                 break;
   11341             :             }
   11342           3 :             case NC_INT:
   11343             :             {
   11344             :                 int *pnTemp =
   11345           3 :                     static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11346          11 :                 for (size_t i = 0; i < nVarLen; i++)
   11347             :                 {
   11348           8 :                     char *pszTemp = nullptr;
   11349           8 :                     pnTemp[i] =
   11350           8 :                         static_cast<int>(strtol(aosValues[i], &pszTemp, 10));
   11351             :                 }
   11352           3 :                 status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11353           3 :                 NCDF_ERR(status);
   11354           3 :                 CPLFree(pnTemp);
   11355           3 :                 break;
   11356             :             }
   11357           0 :             case NC_FLOAT:
   11358             :             {
   11359             :                 float *pfTemp =
   11360           0 :                     static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11361           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11362             :                 {
   11363           0 :                     char *pszTemp = nullptr;
   11364           0 :                     pfTemp[i] =
   11365           0 :                         static_cast<float>(CPLStrtod(aosValues[i], &pszTemp));
   11366             :                 }
   11367             :                 status =
   11368           0 :                     nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11369           0 :                 NCDF_ERR(status);
   11370           0 :                 CPLFree(pfTemp);
   11371           0 :                 break;
   11372             :             }
   11373           5 :             case NC_DOUBLE:
   11374             :             {
   11375             :                 double *pdfTemp =
   11376           5 :                     static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11377          19 :                 for (size_t i = 0; i < nVarLen; i++)
   11378             :                 {
   11379          14 :                     char *pszTemp = nullptr;
   11380          14 :                     pdfTemp[i] = CPLStrtod(aosValues[i], &pszTemp);
   11381             :                 }
   11382             :                 status =
   11383           5 :                     nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11384           5 :                 NCDF_ERR(status);
   11385           5 :                 CPLFree(pdfTemp);
   11386           5 :                 break;
   11387             :             }
   11388           0 :             default:
   11389             :             {
   11390           0 :                 int nTmpFormat = 0;
   11391           0 :                 status = nc_inq_format(nCdfId, &nTmpFormat);
   11392           0 :                 NCDF_ERR(status);
   11393           0 :                 if (nTmpFormat == NCDF_FORMAT_NC4)
   11394             :                 {
   11395           0 :                     switch (nVarType)
   11396             :                     {
   11397           0 :                         case NC_STRING:
   11398             :                         {
   11399           0 :                             status = nc_put_vara_string(
   11400             :                                 nCdfId, nVarId, start, count,
   11401           0 :                                 const_cast<const char **>(aosValues.List()));
   11402           0 :                             NCDF_ERR(status);
   11403           0 :                             break;
   11404             :                         }
   11405           0 :                         case NC_UBYTE:
   11406             :                         {
   11407             :                             unsigned char *pucTemp =
   11408             :                                 static_cast<unsigned char *>(
   11409           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned char)));
   11410           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11411             :                             {
   11412           0 :                                 char *pszTemp = nullptr;
   11413           0 :                                 pucTemp[i] = static_cast<unsigned char>(
   11414           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11415             :                             }
   11416           0 :                             status = nc_put_vara_uchar(nCdfId, nVarId, start,
   11417             :                                                        count, pucTemp);
   11418           0 :                             NCDF_ERR(status);
   11419           0 :                             CPLFree(pucTemp);
   11420           0 :                             break;
   11421             :                         }
   11422           0 :                         case NC_USHORT:
   11423             :                         {
   11424             :                             unsigned short *pusTemp =
   11425             :                                 static_cast<unsigned short *>(
   11426           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned short)));
   11427           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11428             :                             {
   11429           0 :                                 char *pszTemp = nullptr;
   11430           0 :                                 pusTemp[i] = static_cast<unsigned short>(
   11431           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11432             :                             }
   11433           0 :                             status = nc_put_vara_ushort(nCdfId, nVarId, start,
   11434             :                                                         count, pusTemp);
   11435           0 :                             NCDF_ERR(status);
   11436           0 :                             CPLFree(pusTemp);
   11437           0 :                             break;
   11438             :                         }
   11439           0 :                         case NC_UINT:
   11440             :                         {
   11441             :                             unsigned int *punTemp = static_cast<unsigned int *>(
   11442           0 :                                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11443           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11444             :                             {
   11445           0 :                                 char *pszTemp = nullptr;
   11446           0 :                                 punTemp[i] = static_cast<unsigned int>(
   11447           0 :                                     strtoul(aosValues[i], &pszTemp, 10));
   11448             :                             }
   11449           0 :                             status = nc_put_vara_uint(nCdfId, nVarId, start,
   11450             :                                                       count, punTemp);
   11451           0 :                             NCDF_ERR(status);
   11452           0 :                             CPLFree(punTemp);
   11453           0 :                             break;
   11454             :                         }
   11455           0 :                         default:
   11456           0 :                             return CE_Failure;
   11457             :                     }
   11458             :                 }
   11459           0 :                 break;
   11460             :             }
   11461             :         }
   11462             :     }
   11463             : 
   11464           8 :     return CE_None;
   11465             : }
   11466             : 
   11467             : /************************************************************************/
   11468             : /*                       GetDefaultNoDataValue()                        */
   11469             : /************************************************************************/
   11470             : 
   11471         200 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
   11472             :                                  bool &bGotNoData)
   11473             : 
   11474             : {
   11475         200 :     int nNoFill = 0;
   11476         200 :     double dfNoData = 0.0;
   11477             : 
   11478         200 :     switch (nVarType)
   11479             :     {
   11480           0 :         case NC_CHAR:
   11481             :         case NC_BYTE:
   11482             :         case NC_UBYTE:
   11483             :             // Don't do default fill-values for bytes, too risky.
   11484             :             // This function should not be called in those cases.
   11485           0 :             CPLAssert(false);
   11486             :             break;
   11487          24 :         case NC_SHORT:
   11488             :         {
   11489          24 :             short nFillVal = 0;
   11490          24 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11491             :                 NC_NOERR)
   11492             :             {
   11493          24 :                 if (!nNoFill)
   11494             :                 {
   11495          23 :                     bGotNoData = true;
   11496          23 :                     dfNoData = nFillVal;
   11497             :                 }
   11498             :             }
   11499             :             else
   11500           0 :                 dfNoData = NC_FILL_SHORT;
   11501          24 :             break;
   11502             :         }
   11503          26 :         case NC_INT:
   11504             :         {
   11505          26 :             int nFillVal = 0;
   11506          26 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11507             :                 NC_NOERR)
   11508             :             {
   11509          26 :                 if (!nNoFill)
   11510             :                 {
   11511          25 :                     bGotNoData = true;
   11512          25 :                     dfNoData = nFillVal;
   11513             :                 }
   11514             :             }
   11515             :             else
   11516           0 :                 dfNoData = NC_FILL_INT;
   11517          26 :             break;
   11518             :         }
   11519          83 :         case NC_FLOAT:
   11520             :         {
   11521          83 :             float fFillVal = 0;
   11522          83 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
   11523             :                 NC_NOERR)
   11524             :             {
   11525          83 :                 if (!nNoFill)
   11526             :                 {
   11527          79 :                     bGotNoData = true;
   11528          79 :                     dfNoData = fFillVal;
   11529             :                 }
   11530             :             }
   11531             :             else
   11532           0 :                 dfNoData = NC_FILL_FLOAT;
   11533          83 :             break;
   11534             :         }
   11535          34 :         case NC_DOUBLE:
   11536             :         {
   11537          34 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
   11538             :                 NC_NOERR)
   11539             :             {
   11540          34 :                 if (!nNoFill)
   11541             :                 {
   11542          34 :                     bGotNoData = true;
   11543             :                 }
   11544             :             }
   11545             :             else
   11546           0 :                 dfNoData = NC_FILL_DOUBLE;
   11547          34 :             break;
   11548             :         }
   11549           7 :         case NC_USHORT:
   11550             :         {
   11551           7 :             unsigned short nFillVal = 0;
   11552           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11553             :                 NC_NOERR)
   11554             :             {
   11555           7 :                 if (!nNoFill)
   11556             :                 {
   11557           7 :                     bGotNoData = true;
   11558           7 :                     dfNoData = nFillVal;
   11559             :                 }
   11560             :             }
   11561             :             else
   11562           0 :                 dfNoData = NC_FILL_USHORT;
   11563           7 :             break;
   11564             :         }
   11565           7 :         case NC_UINT:
   11566             :         {
   11567           7 :             unsigned int nFillVal = 0;
   11568           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11569             :                 NC_NOERR)
   11570             :             {
   11571           7 :                 if (!nNoFill)
   11572             :                 {
   11573           7 :                     bGotNoData = true;
   11574           7 :                     dfNoData = nFillVal;
   11575             :                 }
   11576             :             }
   11577             :             else
   11578           0 :                 dfNoData = NC_FILL_UINT;
   11579           7 :             break;
   11580             :         }
   11581          19 :         default:
   11582          19 :             dfNoData = 0.0;
   11583          19 :             break;
   11584             :     }
   11585             : 
   11586         200 :     return dfNoData;
   11587             : }
   11588             : 
   11589             : /************************************************************************/
   11590             : /*                  NCDFGetDefaultNoDataValueAsInt64()                  */
   11591             : /************************************************************************/
   11592             : 
   11593           2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
   11594             :                                          bool &bGotNoData)
   11595             : 
   11596             : {
   11597           2 :     int nNoFill = 0;
   11598           2 :     long long nFillVal = 0;
   11599           2 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11600             :     {
   11601           2 :         if (!nNoFill)
   11602             :         {
   11603           2 :             bGotNoData = true;
   11604           2 :             return static_cast<int64_t>(nFillVal);
   11605             :         }
   11606             :     }
   11607             :     else
   11608           0 :         return static_cast<int64_t>(NC_FILL_INT64);
   11609           0 :     return 0;
   11610             : }
   11611             : 
   11612             : /************************************************************************/
   11613             : /*                 NCDFGetDefaultNoDataValueAsUInt64()                  */
   11614             : /************************************************************************/
   11615             : 
   11616           1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
   11617             :                                            bool &bGotNoData)
   11618             : 
   11619             : {
   11620           1 :     int nNoFill = 0;
   11621           1 :     unsigned long long nFillVal = 0;
   11622           1 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11623             :     {
   11624           1 :         if (!nNoFill)
   11625             :         {
   11626           1 :             bGotNoData = true;
   11627           1 :             return static_cast<uint64_t>(nFillVal);
   11628             :         }
   11629             :     }
   11630             :     else
   11631           0 :         return static_cast<uint64_t>(NC_FILL_UINT64);
   11632           0 :     return 0;
   11633             : }
   11634             : 
   11635       12255 : static int NCDFDoesVarContainAttribVal(int nCdfId,
   11636             :                                        const char *const *papszAttribNames,
   11637             :                                        const char *const *papszAttribValues,
   11638             :                                        int nVarId, const char *pszVarName,
   11639             :                                        bool bStrict = true)
   11640             : {
   11641       12255 :     if (nVarId == -1 && pszVarName != nullptr)
   11642        8312 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11643             : 
   11644       12255 :     if (nVarId == -1)
   11645         950 :         return -1;
   11646             : 
   11647       11305 :     bool bFound = false;
   11648       52686 :     for (int i = 0; !bFound && papszAttribNames != nullptr &&
   11649       50197 :                     papszAttribNames[i] != nullptr;
   11650             :          i++)
   11651             :     {
   11652       41381 :         char *pszTemp = nullptr;
   11653       41381 :         if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
   11654       59459 :                 CE_None &&
   11655       18078 :             pszTemp != nullptr)
   11656             :         {
   11657       18078 :             if (bStrict)
   11658             :             {
   11659       18078 :                 if (EQUAL(pszTemp, papszAttribValues[i]))
   11660        2489 :                     bFound = true;
   11661             :             }
   11662             :             else
   11663             :             {
   11664           0 :                 if (EQUALN(pszTemp, papszAttribValues[i],
   11665             :                            strlen(papszAttribValues[i])))
   11666           0 :                     bFound = true;
   11667             :             }
   11668       18078 :             CPLFree(pszTemp);
   11669             :         }
   11670             :     }
   11671       11305 :     return bFound;
   11672             : }
   11673             : 
   11674        2133 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
   11675             :                                         const char *const *papszAttribValues,
   11676             :                                         int nVarId, const char *pszVarName,
   11677             :                                         int bStrict = true)
   11678             : {
   11679        2133 :     if (nVarId == -1 && pszVarName != nullptr)
   11680        1579 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11681             : 
   11682        2133 :     if (nVarId == -1)
   11683           0 :         return -1;
   11684             : 
   11685        2133 :     bool bFound = false;
   11686        2133 :     char *pszTemp = nullptr;
   11687        2512 :     if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
   11688         379 :         pszTemp == nullptr)
   11689        1754 :         return FALSE;
   11690             : 
   11691        7703 :     for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
   11692             :     {
   11693        7324 :         if (bStrict)
   11694             :         {
   11695        7296 :             if (EQUAL(pszTemp, papszAttribValues[i]))
   11696          31 :                 bFound = true;
   11697             :         }
   11698             :         else
   11699             :         {
   11700          28 :             if (EQUALN(pszTemp, papszAttribValues[i],
   11701             :                        strlen(papszAttribValues[i])))
   11702           0 :                 bFound = true;
   11703             :         }
   11704             :     }
   11705             : 
   11706         379 :     CPLFree(pszTemp);
   11707             : 
   11708         379 :     return bFound;
   11709             : }
   11710             : 
   11711         948 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
   11712             : {
   11713         948 :     if (papszName == nullptr || EQUAL(papszName, ""))
   11714           0 :         return false;
   11715             : 
   11716        2560 :     for (int i = 0; papszValues && papszValues[i]; ++i)
   11717             :     {
   11718        1756 :         if (EQUAL(papszName, papszValues[i]))
   11719         144 :             return true;
   11720             :     }
   11721             : 
   11722         804 :     return false;
   11723             : }
   11724             : 
   11725             : // Test that a variable is longitude/latitude coordinate,
   11726             : // following CF 4.1 and 4.2.
   11727        4125 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
   11728             : {
   11729             :     // Check for matching attributes.
   11730        4125 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
   11731             :                                            papszCFLongitudeAttribValues, nVarId,
   11732             :                                            pszVarName);
   11733             :     // If not found using attributes then check using var name
   11734             :     // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
   11735        4125 :     if (bVal == -1)
   11736             :     {
   11737         304 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11738             :                    "STRICT"))
   11739         304 :             bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
   11740             :         else
   11741           0 :             bVal = FALSE;
   11742             :     }
   11743        3821 :     else if (bVal)
   11744             :     {
   11745             :         // Check that the units is not 'm' or '1'. See #6759
   11746         805 :         char *pszTemp = nullptr;
   11747        1187 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11748         382 :             pszTemp != nullptr)
   11749             :         {
   11750         382 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11751          97 :                 bVal = false;
   11752         382 :             CPLFree(pszTemp);
   11753             :         }
   11754             :     }
   11755             : 
   11756        4125 :     return CPL_TO_BOOL(bVal);
   11757             : }
   11758             : 
   11759        2369 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
   11760             : {
   11761        2369 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
   11762             :                                            papszCFLatitudeAttribValues, nVarId,
   11763             :                                            pszVarName);
   11764        2369 :     if (bVal == -1)
   11765             :     {
   11766         175 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11767             :                    "STRICT"))
   11768         175 :             bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
   11769             :         else
   11770           0 :             bVal = FALSE;
   11771             :     }
   11772        2194 :     else if (bVal)
   11773             :     {
   11774             :         // Check that the units is not 'm' or '1'. See #6759
   11775         556 :         char *pszTemp = nullptr;
   11776         698 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11777         142 :             pszTemp != nullptr)
   11778             :         {
   11779         142 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11780          36 :                 bVal = false;
   11781         142 :             CPLFree(pszTemp);
   11782             :         }
   11783             :     }
   11784             : 
   11785        2369 :     return CPL_TO_BOOL(bVal);
   11786             : }
   11787             : 
   11788        2572 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
   11789             : {
   11790        2572 :     int bVal = NCDFDoesVarContainAttribVal(
   11791             :         nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
   11792             :         nVarId, pszVarName);
   11793        2572 :     if (bVal == -1)
   11794             :     {
   11795         298 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11796             :                    "STRICT"))
   11797         298 :             bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
   11798             :         else
   11799           0 :             bVal = FALSE;
   11800             :     }
   11801        2274 :     else if (bVal)
   11802             :     {
   11803             :         // Check that the units is not '1'
   11804         458 :         char *pszTemp = nullptr;
   11805         686 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11806         228 :             pszTemp != nullptr)
   11807             :         {
   11808         228 :             if (EQUAL(pszTemp, "1"))
   11809           5 :                 bVal = false;
   11810         228 :             CPLFree(pszTemp);
   11811             :         }
   11812             :     }
   11813             : 
   11814        2572 :     return CPL_TO_BOOL(bVal);
   11815             : }
   11816             : 
   11817        1812 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
   11818             : {
   11819        1812 :     int bVal = NCDFDoesVarContainAttribVal(
   11820             :         nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
   11821             :         nVarId, pszVarName);
   11822        1812 :     if (bVal == -1)
   11823             :     {
   11824         171 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11825             :                    "STRICT"))
   11826         171 :             bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
   11827             :         else
   11828           0 :             bVal = FALSE;
   11829             :     }
   11830        1641 :     else if (bVal)
   11831             :     {
   11832             :         // Check that the units is not '1'
   11833         452 :         char *pszTemp = nullptr;
   11834         675 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11835         223 :             pszTemp != nullptr)
   11836             :         {
   11837         223 :             if (EQUAL(pszTemp, "1"))
   11838           5 :                 bVal = false;
   11839         223 :             CPLFree(pszTemp);
   11840             :         }
   11841             :     }
   11842             : 
   11843        1812 :     return CPL_TO_BOOL(bVal);
   11844             : }
   11845             : 
   11846             : /* test that a variable is a vertical coordinate, following CF 4.3 */
   11847        1118 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
   11848             : {
   11849             :     /* check for matching attributes */
   11850        1118 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
   11851             :                                     papszCFVerticalAttribValues, nVarId,
   11852        1118 :                                     pszVarName))
   11853         111 :         return true;
   11854             :     /* check for matching units */
   11855        1007 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11856             :                                           papszCFVerticalUnitsValues, nVarId,
   11857        1007 :                                           pszVarName))
   11858          31 :         return true;
   11859             :     /* check for matching standard name */
   11860         976 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
   11861             :                                           papszCFVerticalStandardNameValues,
   11862         976 :                                           nVarId, pszVarName))
   11863           0 :         return true;
   11864             :     else
   11865         976 :         return false;
   11866             : }
   11867             : 
   11868             : /* test that a variable is a time coordinate, following CF 4.4 */
   11869         259 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
   11870             : {
   11871             :     /* check for matching attributes */
   11872         259 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
   11873             :                                     papszCFTimeAttribValues, nVarId,
   11874         259 :                                     pszVarName))
   11875         109 :         return true;
   11876             :     /* check for matching units */
   11877         150 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11878             :                                           papszCFTimeUnitsValues, nVarId,
   11879         150 :                                           pszVarName, false))
   11880           0 :         return true;
   11881             :     else
   11882         150 :         return false;
   11883             : }
   11884             : 
   11885             : // Parse a string, and return as a string list.
   11886             : // If it is an array of the form {a,b}, then tokenize it.
   11887             : // Otherwise, return a copy.
   11888         200 : static CPLStringList NCDFTokenizeArray(const char *pszValue)
   11889             : {
   11890         200 :     if (pszValue == nullptr || EQUAL(pszValue, ""))
   11891          59 :         return CPLStringList();
   11892             : 
   11893         282 :     CPLStringList aosValues;
   11894         141 :     const int nLen = static_cast<int>(strlen(pszValue));
   11895             : 
   11896         141 :     if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
   11897             :     {
   11898          41 :         char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
   11899          41 :         strncpy(pszTemp, pszValue + 1, nLen - 2);
   11900          41 :         pszTemp[nLen - 2] = '\0';
   11901             :         aosValues.Assign(
   11902          41 :             CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS));
   11903          41 :         CPLFree(pszTemp);
   11904             :     }
   11905             :     else
   11906             :     {
   11907         100 :         aosValues.AddString(pszValue);
   11908             :     }
   11909             : 
   11910         141 :     return aosValues;
   11911             : }
   11912             : 
   11913             : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
   11914             : // Leading slash is optional.
   11915         436 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
   11916             :                                  int *pnGroupId, int *pnVarId)
   11917             : {
   11918         436 :     *pnGroupId = -1;
   11919         436 :     *pnVarId = -1;
   11920             : 
   11921             :     // Open group.
   11922             :     char *pszGroupFullName =
   11923         436 :         CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
   11924             :     // Add a leading slash if needed.
   11925         436 :     if (pszGroupFullName[0] != '/')
   11926             :     {
   11927         419 :         char *old = pszGroupFullName;
   11928         419 :         pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
   11929         419 :         CPLFree(old);
   11930             :     }
   11931             :     // Detect root group.
   11932         436 :     if (EQUAL(pszGroupFullName, "/"))
   11933             :     {
   11934         419 :         *pnGroupId = nCdfId;
   11935         419 :         CPLFree(pszGroupFullName);
   11936             :     }
   11937             :     else
   11938             :     {
   11939          17 :         int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
   11940          17 :         CPLFree(pszGroupFullName);
   11941          17 :         NCDF_ERR_RET(status);
   11942             :     }
   11943             : 
   11944             :     // Open var.
   11945         436 :     const char *pszVarName = CPLGetFilename(pszSubdatasetName);
   11946         436 :     NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
   11947             : 
   11948         436 :     return CE_None;
   11949             : }
   11950             : 
   11951             : // Get all dimensions visible from a given NetCDF (or group) ID and any of
   11952             : // its parents.
   11953         372 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
   11954             : {
   11955         372 :     int nDims = 0;
   11956         372 :     int *panDimIds = nullptr;
   11957         372 :     NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
   11958             : 
   11959         372 :     panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
   11960             : 
   11961         372 :     int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
   11962         372 :     if (status != NC_NOERR)
   11963           0 :         CPLFree(panDimIds);
   11964         372 :     NCDF_ERR_RET(status);
   11965             : 
   11966         372 :     *pnDims = nDims;
   11967         372 :     *ppanDimIds = panDimIds;
   11968             : 
   11969         372 :     return CE_None;
   11970             : }
   11971             : 
   11972             : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
   11973             : // Consider only direct children, does not get children of children.
   11974        3286 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
   11975             :                                int **ppanSubGroupIds)
   11976             : {
   11977        3286 :     *pnSubGroups = 0;
   11978        3286 :     *ppanSubGroupIds = nullptr;
   11979             : 
   11980             :     int nSubGroups;
   11981        3286 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
   11982             :     int *panSubGroupIds =
   11983        3286 :         static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
   11984        3286 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
   11985        3286 :     *pnSubGroups = nSubGroups;
   11986        3286 :     *ppanSubGroupIds = panSubGroupIds;
   11987             : 
   11988        3286 :     return CE_None;
   11989             : }
   11990             : 
   11991             : // Get the full name of a given NetCDF (or group) ID
   11992             : // (e.g. /group1/group2/.../groupn).
   11993             : // bNC3Compat remove the leading slash for top-level variables for
   11994             : // backward compatibility (top-level variables are the ones in the root group).
   11995       18634 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
   11996             :                                    bool bNC3Compat)
   11997             : {
   11998       18634 :     *ppszFullName = nullptr;
   11999             : 
   12000             :     size_t nFullNameLen;
   12001       18634 :     NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
   12002       18634 :     *ppszFullName =
   12003       18634 :         static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
   12004       18634 :     int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
   12005       18634 :     if (status != NC_NOERR)
   12006             :     {
   12007           0 :         CPLFree(*ppszFullName);
   12008           0 :         *ppszFullName = nullptr;
   12009           0 :         NCDF_ERR_RET(status);
   12010             :     }
   12011             : 
   12012       18634 :     if (bNC3Compat && EQUAL(*ppszFullName, "/"))
   12013        8334 :         (*ppszFullName)[0] = '\0';
   12014             : 
   12015       18634 :     return CE_None;
   12016             : }
   12017             : 
   12018       10092 : CPLString NCDFGetGroupFullName(int nGroupId)
   12019             : {
   12020       10092 :     char *pszFullname = nullptr;
   12021       10092 :     NCDFGetGroupFullName(nGroupId, &pszFullname, false);
   12022       10092 :     CPLString osRet(pszFullname ? pszFullname : "");
   12023       10092 :     CPLFree(pszFullname);
   12024       20184 :     return osRet;
   12025             : }
   12026             : 
   12027             : // Get the full name of a given NetCDF variable ID
   12028             : // (e.g. /group1/group2/.../groupn/var).
   12029             : // Handle also NC_GLOBAL as nVarId.
   12030             : // bNC3Compat remove the leading slash for top-level variables for
   12031             : // backward compatibility (top-level variables are the ones in the root group).
   12032        8493 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
   12033             :                                  bool bNC3Compat)
   12034             : {
   12035        8493 :     *ppszFullName = nullptr;
   12036        8493 :     char *pszGroupFullName = nullptr;
   12037        8493 :     ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
   12038             :     char szVarName[NC_MAX_NAME + 1];
   12039        8493 :     if (nVarId == NC_GLOBAL)
   12040             :     {
   12041        1142 :         strcpy(szVarName, "NC_GLOBAL");
   12042             :     }
   12043             :     else
   12044             :     {
   12045        7351 :         int status = nc_inq_varname(nGroupId, nVarId, szVarName);
   12046        7351 :         if (status != NC_NOERR)
   12047             :         {
   12048           0 :             CPLFree(pszGroupFullName);
   12049           0 :             NCDF_ERR_RET(status);
   12050             :         }
   12051             :     }
   12052        8493 :     const char *pszSep = "/";
   12053        8493 :     if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
   12054        8287 :         pszSep = "";
   12055        8493 :     *ppszFullName =
   12056        8493 :         CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
   12057        8493 :     CPLFree(pszGroupFullName);
   12058        8493 :     return CE_None;
   12059             : }
   12060             : 
   12061             : // Get the NetCDF root group ID of a given group ID.
   12062           0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
   12063             : {
   12064           0 :     *pnRootGroupId = -1;
   12065             :     // Recurse on parent group.
   12066             :     int nParentGroupId;
   12067           0 :     int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
   12068           0 :     if (status == NC_NOERR)
   12069           0 :         return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
   12070           0 :     else if (status != NC_ENOGRP)
   12071           0 :         NCDF_ERR_RET(status);
   12072             :     else  // No more parent group.
   12073             :     {
   12074           0 :         *pnRootGroupId = nStartGroupId;
   12075             :     }
   12076             : 
   12077           0 :     return CE_None;
   12078             : }
   12079             : 
   12080             : // Implementation of NCDFResolveVar/Att.
   12081       13555 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
   12082             :                               const char *pszAtt, int *pnGroupId, int *pnId,
   12083             :                               bool bMandatory)
   12084             : {
   12085       13555 :     if (!pszVar && !pszAtt)
   12086             :     {
   12087           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   12088             :                  "pszVar and pszAtt NCDFResolveElem() args are both null.");
   12089           0 :         return CE_Failure;
   12090             :     }
   12091             : 
   12092             :     enum
   12093             :     {
   12094             :         NCRM_PARENT,
   12095             :         NCRM_WIDTH_WISE
   12096       13555 :     } eNCResolveMode = NCRM_PARENT;
   12097             : 
   12098       27110 :     std::queue<int> aoQueueGroupIdsToVisit;
   12099       13555 :     aoQueueGroupIdsToVisit.push(nStartGroupId);
   12100             : 
   12101       15405 :     while (!aoQueueGroupIdsToVisit.empty())
   12102             :     {
   12103             :         // Get the first group of the FIFO queue.
   12104       13749 :         *pnGroupId = aoQueueGroupIdsToVisit.front();
   12105       13749 :         aoQueueGroupIdsToVisit.pop();
   12106             : 
   12107             :         // Look if this group contains the searched element.
   12108             :         int status;
   12109       13749 :         if (pszVar)
   12110       13524 :             status = nc_inq_varid(*pnGroupId, pszVar, pnId);
   12111             :         else  // pszAtt != nullptr.
   12112         225 :             status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
   12113             : 
   12114       13749 :         if (status == NC_NOERR)
   12115             :         {
   12116       11899 :             return CE_None;
   12117             :         }
   12118        1850 :         else if ((pszVar && status != NC_ENOTVAR) ||
   12119         222 :                  (pszAtt && status != NC_ENOTATT))
   12120             :         {
   12121           0 :             NCDF_ERR(status);
   12122             :         }
   12123             :         // Element not found, in NC4 case we must search in other groups
   12124             :         // following the CF logic.
   12125             : 
   12126             :         // The first resolve mode consists to search on parent groups.
   12127        1850 :         if (eNCResolveMode == NCRM_PARENT)
   12128             :         {
   12129        1731 :             int nParentGroupId = -1;
   12130        1731 :             int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
   12131        1731 :             if (status2 == NC_NOERR)
   12132          62 :                 aoQueueGroupIdsToVisit.push(nParentGroupId);
   12133        1669 :             else if (status2 != NC_ENOGRP)
   12134           0 :                 NCDF_ERR(status2);
   12135        1669 :             else if (pszVar)
   12136             :                 // When resolving a variable, if there is no more
   12137             :                 // parent group then we switch to width-wise search mode
   12138             :                 // starting from the latest found parent group.
   12139        1450 :                 eNCResolveMode = NCRM_WIDTH_WISE;
   12140             :         }
   12141             : 
   12142             :         // The second resolve mode is a width-wise search.
   12143        1850 :         if (eNCResolveMode == NCRM_WIDTH_WISE)
   12144             :         {
   12145             :             // Enqueue all direct sub-groups.
   12146        1569 :             int nSubGroups = 0;
   12147        1569 :             int *panSubGroupIds = nullptr;
   12148        1569 :             NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
   12149        1701 :             for (int i = 0; i < nSubGroups; i++)
   12150         132 :                 aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
   12151        1569 :             CPLFree(panSubGroupIds);
   12152             :         }
   12153             :     }
   12154             : 
   12155        1656 :     if (bMandatory)
   12156             :     {
   12157           0 :         char *pszStartGroupFullName = nullptr;
   12158           0 :         NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
   12159           0 :         CPLError(CE_Failure, CPLE_AppDefined,
   12160             :                  "Cannot resolve mandatory %s %s from group %s",
   12161             :                  (pszVar ? pszVar : pszAtt),
   12162             :                  (pszVar ? "variable" : "attribute"),
   12163           0 :                  (pszStartGroupFullName ? pszStartGroupFullName : ""));
   12164           0 :         CPLFree(pszStartGroupFullName);
   12165             :     }
   12166             : 
   12167        1656 :     *pnGroupId = -1;
   12168        1656 :     *pnId = -1;
   12169        1656 :     return CE_Failure;
   12170             : }
   12171             : 
   12172             : // Resolve a variable name from a given starting group following the CF logic:
   12173             : // - if var name is an absolute path then directly open it
   12174             : // - first search in the starting group and its parent groups
   12175             : // - then if there is no more parent group we switch to a width-wise search
   12176             : //   mode starting from the latest found parent group.
   12177             : // The full CF logic is described here:
   12178             : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
   12179             : // If bMandatory then print an error if resolving fails.
   12180             : // TODO: implement support of relative paths.
   12181             : // TODO: to follow strictly the CF logic, when searching for a coordinate
   12182             : //       variable, we must stop the parent search mode once the corresponding
   12183             : //       dimension is found and start the width-wise search from this group.
   12184             : // TODO: to follow strictly the CF logic, when searching in width-wise mode
   12185             : //       we should skip every groups already visited during the parent
   12186             : //       search mode (but revisiting them should have no impact so we could
   12187             : //       let as it is if it is simpler...)
   12188             : // TODO: CF specifies that the width-wise search order is "left-to-right" so
   12189             : //       maybe we must sort sibling groups alphabetically? but maybe not
   12190             : //       necessary if nc_inq_grps() already sort them?
   12191       13333 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
   12192             :                       int *pnVarId, bool bMandatory)
   12193             : {
   12194       13333 :     *pnGroupId = -1;
   12195       13333 :     *pnVarId = -1;
   12196       13333 :     int nGroupId = nStartGroupId, nVarId;
   12197       13333 :     if (pszVar[0] == '/')
   12198             :     {
   12199             :         // This is an absolute path: we can open the var directly.
   12200             :         int nRootGroupId;
   12201           0 :         ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
   12202           0 :         ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
   12203             :     }
   12204             :     else
   12205             :     {
   12206             :         // We have to search the variable following the CF logic.
   12207       13333 :         ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
   12208             :                                 &nVarId, bMandatory));
   12209             :     }
   12210       11896 :     *pnGroupId = nGroupId;
   12211       11896 :     *pnVarId = nVarId;
   12212       11896 :     return CE_None;
   12213             : }
   12214             : 
   12215             : // Like NCDFResolveVar but returns directly the var full name.
   12216        1416 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
   12217             :                                      char **ppszFullName, bool bMandatory)
   12218             : {
   12219        1416 :     *ppszFullName = nullptr;
   12220             :     int nGroupId, nVarId;
   12221        1416 :     ERR_RET(
   12222             :         NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
   12223        1390 :     return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
   12224             : }
   12225             : 
   12226             : // Like NCDFResolveVar but resolves an attribute instead a variable and
   12227             : // returns its integer value.
   12228             : // Only GLOBAL attributes are supported for the moment.
   12229         222 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
   12230             :                                 const char *pszAtt, int *pnAtt, bool bMandatory)
   12231             : {
   12232         222 :     int nGroupId = nStartGroupId, nAttId = nStartVarId;
   12233         222 :     ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
   12234             :                             bMandatory));
   12235           3 :     NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
   12236           3 :     return CE_None;
   12237             : }
   12238             : 
   12239             : // Filter variables to keep only valid 2+D raster bands and vector fields in
   12240             : // a given a NetCDF (or group) ID and its sub-groups.
   12241             : // Coordinate or boundary variables are ignored.
   12242             : // It also creates corresponding vector layers.
   12243         548 : CPLErr netCDFDataset::FilterVars(
   12244             :     int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
   12245             :     int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
   12246             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
   12247             :         &oMap2DDimsToGroupAndVar)
   12248             : {
   12249         548 :     int nVars = 0;
   12250         548 :     int nRasterVars = 0;
   12251         548 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12252             : 
   12253        1096 :     std::vector<int> anPotentialVectorVarID;
   12254             :     // oMapDimIdToCount[x] = number of times dim x is the first dimension of
   12255             :     // potential vector variables
   12256        1096 :     std::map<int, int> oMapDimIdToCount;
   12257         548 :     int nVarXId = -1;
   12258         548 :     int nVarYId = -1;
   12259         548 :     int nVarZId = -1;
   12260         548 :     int nVarTimeId = -1;
   12261         548 :     int nVarTimeDimId = -1;
   12262         548 :     bool bIsVectorOnly = true;
   12263         548 :     int nProfileDimId = -1;
   12264         548 :     int nParentIndexVarID = -1;
   12265             : 
   12266        3311 :     for (int v = 0; v < nVars; v++)
   12267             :     {
   12268             :         int nVarDims;
   12269        2763 :         NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
   12270             :         // Should we ignore this variable?
   12271             :         char szTemp[NC_MAX_NAME + 1];
   12272        2763 :         szTemp[0] = '\0';
   12273        2763 :         NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
   12274             : 
   12275        2763 :         if (strstr(szTemp, "_node_coordinates") ||
   12276        2763 :             strstr(szTemp, "_node_count"))
   12277             :         {
   12278             :             // Ignore CF-1.8 Simple Geometries helper variables
   12279          69 :             continue;
   12280             :         }
   12281             : 
   12282        4003 :         if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
   12283        1309 :                               NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
   12284             :         {
   12285         365 :             nVarXId = v;
   12286             :         }
   12287        3273 :         else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
   12288         944 :                                    NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
   12289             :         {
   12290         364 :             nVarYId = v;
   12291             :         }
   12292        1965 :         else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
   12293             :         {
   12294          78 :             nVarZId = v;
   12295             :         }
   12296             :         else
   12297             :         {
   12298        1887 :             char *pszVarFullName = nullptr;
   12299        1887 :             CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
   12300        1887 :             if (eErr != CE_None)
   12301             :             {
   12302           0 :                 CPLFree(pszVarFullName);
   12303           0 :                 continue;
   12304             :             }
   12305             :             bool bIgnoreVar =
   12306        1887 :                 (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
   12307        1887 :             CPLFree(pszVarFullName);
   12308        1887 :             if (bIgnoreVar)
   12309             :             {
   12310         120 :                 if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
   12311             :                 {
   12312          11 :                     nVarTimeId = v;
   12313          11 :                     nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
   12314             :                 }
   12315         109 :                 else if (nVarDims > 1)
   12316             :                 {
   12317         105 :                     (*pnIgnoredVars)++;
   12318         105 :                     CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
   12319             :                              szTemp);
   12320             :                 }
   12321             :             }
   12322             :             // Only accept 2+D vars.
   12323        1767 :             else if (nVarDims >= 2)
   12324             :             {
   12325         724 :                 bool bRasterCandidate = true;
   12326             :                 // Identify variables that might be vector variables
   12327         724 :                 if (nVarDims == 2)
   12328             :                 {
   12329         648 :                     int anDimIds[2] = {-1, -1};
   12330         648 :                     nc_inq_vardimid(nCdfId, v, anDimIds);
   12331             : 
   12332         648 :                     nc_type vartype = NC_NAT;
   12333         648 :                     nc_inq_vartype(nCdfId, v, &vartype);
   12334             : 
   12335             :                     char szDimNameFirst[NC_MAX_NAME + 1];
   12336             :                     char szDimNameSecond[NC_MAX_NAME + 1];
   12337         648 :                     szDimNameFirst[0] = '\0';
   12338         648 :                     szDimNameSecond[0] = '\0';
   12339        1453 :                     if (vartype == NC_CHAR &&
   12340         157 :                         nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
   12341         157 :                             NC_NOERR &&
   12342         157 :                         nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
   12343         157 :                             NC_NOERR &&
   12344         157 :                         !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
   12345         157 :                         !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
   12346         962 :                         !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
   12347         157 :                         !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
   12348             :                     {
   12349         157 :                         anPotentialVectorVarID.push_back(v);
   12350         157 :                         oMapDimIdToCount[anDimIds[0]]++;
   12351         157 :                         if (strstr(szDimNameSecond, "_max_width"))
   12352             :                         {
   12353         127 :                             bRasterCandidate = false;
   12354             :                         }
   12355             :                         else
   12356             :                         {
   12357          30 :                             std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12358          30 :                                                     vartype};
   12359          30 :                             oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12360          30 :                                 std::pair(nCdfId, v));
   12361             :                         }
   12362             :                     }
   12363             :                     else
   12364             :                     {
   12365         491 :                         std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12366         491 :                                                 vartype};
   12367         491 :                         oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12368         491 :                             std::pair(nCdfId, v));
   12369         491 :                         bIsVectorOnly = false;
   12370             :                     }
   12371             :                 }
   12372             :                 else
   12373             :                 {
   12374          76 :                     bIsVectorOnly = false;
   12375             :                 }
   12376         724 :                 if (bKeepRasters && bRasterCandidate)
   12377             :                 {
   12378         568 :                     *pnGroupId = nCdfId;
   12379         568 :                     *pnVarId = v;
   12380         568 :                     nRasterVars++;
   12381             :                 }
   12382             :             }
   12383        1043 :             else if (nVarDims == 1)
   12384             :             {
   12385         730 :                 nc_type atttype = NC_NAT;
   12386         730 :                 size_t attlen = 0;
   12387         730 :                 if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
   12388          14 :                                &attlen) == NC_NOERR &&
   12389         730 :                     atttype == NC_CHAR && attlen < NC_MAX_NAME)
   12390             :                 {
   12391             :                     char szInstanceDimension[NC_MAX_NAME + 1];
   12392          14 :                     if (nc_get_att_text(nCdfId, v, "instance_dimension",
   12393          14 :                                         szInstanceDimension) == NC_NOERR)
   12394             :                     {
   12395          14 :                         szInstanceDimension[attlen] = 0;
   12396          14 :                         int status = nc_inq_dimid(nCdfId, szInstanceDimension,
   12397             :                                                   &nProfileDimId);
   12398          14 :                         if (status == NC_NOERR)
   12399          14 :                             nParentIndexVarID = v;
   12400             :                         else
   12401           0 :                             nProfileDimId = -1;
   12402          14 :                         if (status == NC_EBADDIM)
   12403           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
   12404             :                                      "Attribute instance_dimension='%s' refers "
   12405             :                                      "to a non existing dimension",
   12406             :                                      szInstanceDimension);
   12407             :                         else
   12408          14 :                             NCDF_ERR(status);
   12409             :                     }
   12410             :                 }
   12411         730 :                 if (v != nParentIndexVarID)
   12412             :                 {
   12413         716 :                     anPotentialVectorVarID.push_back(v);
   12414         716 :                     int nDimId = -1;
   12415         716 :                     nc_inq_vardimid(nCdfId, v, &nDimId);
   12416         716 :                     oMapDimIdToCount[nDimId]++;
   12417             :                 }
   12418             :             }
   12419             :         }
   12420             :     }
   12421             : 
   12422             :     // If we are opened in raster-only mode and that there are only 1D or 2D
   12423             :     // variables and that the 2D variables have no X/Y dim, and all
   12424             :     // variables refer to the same main dimension (or 2 dimensions for
   12425             :     // featureType=profile), then it is a pure vector dataset
   12426             :     CPLString osFeatureType(
   12427         548 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
   12428         437 :     if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
   12429         985 :         !anPotentialVectorVarID.empty() &&
   12430           0 :         (oMapDimIdToCount.size() == 1 ||
   12431           0 :          (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
   12432           0 :           nProfileDimId >= 0)))
   12433             :     {
   12434           0 :         anPotentialVectorVarID.resize(0);
   12435             :     }
   12436             :     else
   12437             :     {
   12438         548 :         *pnRasterVars += nRasterVars;
   12439             :     }
   12440             : 
   12441         548 :     if (!anPotentialVectorVarID.empty() && bKeepVectors)
   12442             :     {
   12443             :         // Take the dimension that is referenced the most times.
   12444          64 :         if (!(oMapDimIdToCount.size() == 1 ||
   12445          27 :               (EQUAL(osFeatureType, "profile") &&
   12446          26 :                oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
   12447             :         {
   12448           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12449             :                      "The dataset has several variables that could be "
   12450             :                      "identified as vector fields, but not all share the same "
   12451             :                      "primary dimension. Consequently they will be ignored.");
   12452             :         }
   12453             :         else
   12454             :         {
   12455          50 :             if (nVarTimeId >= 0 &&
   12456          50 :                 oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
   12457             :             {
   12458           1 :                 anPotentialVectorVarID.push_back(nVarTimeId);
   12459             :             }
   12460          49 :             CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
   12461             :                                   oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
   12462             :                                   nProfileDimId, nParentIndexVarID,
   12463             :                                   bKeepRasters);
   12464             :         }
   12465             :     }
   12466             : 
   12467             :     // Recurse on sub-groups.
   12468         548 :     int nSubGroups = 0;
   12469         548 :     int *panSubGroupIds = nullptr;
   12470         548 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12471         583 :     for (int i = 0; i < nSubGroups; i++)
   12472             :     {
   12473          35 :         FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
   12474             :                    papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
   12475             :                    pnIgnoredVars, oMap2DDimsToGroupAndVar);
   12476             :     }
   12477         548 :     CPLFree(panSubGroupIds);
   12478             : 
   12479         548 :     return CE_None;
   12480             : }
   12481             : 
   12482             : // Create vector layers from given potentially identified vector variables
   12483             : // resulting from the scanning of a NetCDF (or group) ID.
   12484          49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
   12485             :     int nCdfId, const CPLString &osFeatureType,
   12486             :     const std::vector<int> &anPotentialVectorVarID,
   12487             :     const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
   12488             :     int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
   12489             : {
   12490          49 :     char *pszGroupName = nullptr;
   12491          49 :     NCDFGetGroupFullName(nCdfId, &pszGroupName);
   12492          49 :     if (pszGroupName == nullptr || pszGroupName[0] == '\0')
   12493             :     {
   12494          47 :         CPLFree(pszGroupName);
   12495          47 :         pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
   12496             :     }
   12497          49 :     OGRwkbGeometryType eGType = wkbUnknown;
   12498             :     CPLString osLayerName = CSLFetchNameValueDef(
   12499          98 :         papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
   12500          49 :     CPLFree(pszGroupName);
   12501          49 :     papszMetadata =
   12502          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
   12503             : 
   12504          49 :     if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
   12505             :     {
   12506          33 :         papszMetadata =
   12507          33 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
   12508          33 :         eGType = wkbPoint;
   12509             :     }
   12510             : 
   12511             :     const char *pszLayerType =
   12512          49 :         CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
   12513          49 :     if (pszLayerType != nullptr)
   12514             :     {
   12515           9 :         eGType = OGRFromOGCGeomType(pszLayerType);
   12516           9 :         papszMetadata =
   12517           9 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
   12518             :     }
   12519             : 
   12520             :     CPLString osGeometryField =
   12521          98 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
   12522          49 :     papszMetadata =
   12523          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
   12524             : 
   12525          49 :     int nFirstVarId = -1;
   12526          49 :     int nVectorDim = oMapDimIdToCount.rbegin()->first;
   12527          49 :     if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
   12528             :     {
   12529          13 :         if (nVectorDim == nProfileDimId)
   12530           0 :             nVectorDim = oMapDimIdToCount.begin()->first;
   12531             :     }
   12532             :     else
   12533             :     {
   12534          36 :         nProfileDimId = -1;
   12535             :     }
   12536          62 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12537             :     {
   12538          62 :         int anDimIds[2] = {-1, -1};
   12539          62 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12540          62 :         if (nVectorDim == anDimIds[0])
   12541             :         {
   12542          49 :             nFirstVarId = anPotentialVectorVarID[j];
   12543          49 :             break;
   12544             :         }
   12545             :     }
   12546             : 
   12547             :     // In case where coordinates are explicitly specified for one of the
   12548             :     // field/variable, use them in priority over the ones that might have been
   12549             :     // identified above.
   12550          49 :     char *pszCoordinates = nullptr;
   12551          49 :     if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
   12552             :         CE_None)
   12553             :     {
   12554             :         const CPLStringList aosTokens(
   12555          68 :             NCDFTokenizeCoordinatesAttribute(pszCFCoordinates));
   12556          34 :         for (int i = 0; i < aosTokens.size(); i++)
   12557             :         {
   12558           0 :             if (NCDFIsVarLongitude(nCdfId, -1, aosTokens[i]) ||
   12559           0 :                 NCDFIsVarProjectionX(nCdfId, -1, aosTokens[i]))
   12560             :             {
   12561           0 :                 nVarXId = -1;
   12562           0 :                 CPL_IGNORE_RET_VAL(
   12563           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarXId));
   12564             :             }
   12565           0 :             else if (NCDFIsVarLatitude(nCdfId, -1, aosTokens[i]) ||
   12566           0 :                      NCDFIsVarProjectionY(nCdfId, -1, aosTokens[i]))
   12567             :             {
   12568           0 :                 nVarYId = -1;
   12569           0 :                 CPL_IGNORE_RET_VAL(
   12570           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarYId));
   12571             :             }
   12572           0 :             else if (NCDFIsVarVerticalCoord(nCdfId, -1, aosTokens[i]))
   12573             :             {
   12574           0 :                 nVarZId = -1;
   12575           0 :                 CPL_IGNORE_RET_VAL(
   12576           0 :                     nc_inq_varid(nCdfId, aosTokens[i], &nVarZId));
   12577             :             }
   12578             :         }
   12579             :     }
   12580          49 :     CPLFree(pszCoordinates);
   12581             : 
   12582             :     // Check that the X,Y,Z vars share 1D and share the same dimension as
   12583             :     // attribute variables.
   12584          49 :     if (nVarXId >= 0 && nVarYId >= 0)
   12585             :     {
   12586          38 :         int nVarDimCount = -1;
   12587          38 :         int nVarDimId = -1;
   12588          38 :         if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
   12589          38 :             nVarDimCount != 1 ||
   12590          38 :             nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
   12591          38 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
   12592          35 :             nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
   12593          35 :             nVarDimCount != 1 ||
   12594         111 :             nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
   12595          35 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
   12596             :         {
   12597           3 :             nVarXId = nVarYId = -1;
   12598             :         }
   12599          69 :         else if (nVarZId >= 0 &&
   12600          34 :                  (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
   12601          34 :                   nVarDimCount != 1 ||
   12602          34 :                   nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
   12603          34 :                   nVarDimId != nVectorDim))
   12604             :         {
   12605           0 :             nVarZId = -1;
   12606             :         }
   12607             :     }
   12608             : 
   12609          49 :     if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
   12610             :     {
   12611           2 :         eGType = wkbPoint;
   12612             :     }
   12613          49 :     if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
   12614             :     {
   12615          34 :         eGType = wkbPoint25D;
   12616             :     }
   12617          49 :     if (eGType == wkbUnknown && osGeometryField.empty())
   12618             :     {
   12619           5 :         eGType = wkbNone;
   12620             :     }
   12621             : 
   12622             :     // Read projection info
   12623          49 :     char **papszMetadataBackup = CSLDuplicate(papszMetadata);
   12624          49 :     ReadAttributes(nCdfId, nFirstVarId);
   12625          49 :     if (!this->bSGSupport)
   12626          49 :         SetProjectionFromVar(nCdfId, nFirstVarId, true);
   12627          49 :     const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
   12628          49 :     char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
   12629          49 :     CSLDestroy(papszMetadata);
   12630          49 :     papszMetadata = papszMetadataBackup;
   12631             : 
   12632          49 :     OGRSpatialReference *poSRS = nullptr;
   12633          49 :     if (!m_oSRS.IsEmpty())
   12634             :     {
   12635          21 :         poSRS = m_oSRS.Clone();
   12636             :     }
   12637             :     // Reset if there's a 2D raster
   12638          49 :     m_bHasProjection = false;
   12639          49 :     m_bHasGeoTransform = false;
   12640             : 
   12641          49 :     if (!bKeepRasters)
   12642             :     {
   12643             :         // Strip out uninteresting metadata.
   12644          45 :         papszMetadata =
   12645          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
   12646          45 :         papszMetadata =
   12647          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
   12648          45 :         papszMetadata =
   12649          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
   12650             :     }
   12651             : 
   12652             :     std::shared_ptr<netCDFLayer> poLayer(
   12653          49 :         new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
   12654          49 :     if (poSRS != nullptr)
   12655          21 :         poSRS->Release();
   12656          49 :     poLayer->SetRecordDimID(nVectorDim);
   12657          49 :     if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
   12658             :     {
   12659          35 :         poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
   12660             :     }
   12661          14 :     else if (!osGeometryField.empty())
   12662             :     {
   12663           9 :         poLayer->SetWKTGeometryField(osGeometryField);
   12664             :     }
   12665          49 :     if (pszGridMapping != nullptr)
   12666             :     {
   12667          21 :         poLayer->SetGridMapping(pszGridMapping);
   12668          21 :         CPLFree(pszGridMapping);
   12669             :     }
   12670          49 :     poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
   12671             : 
   12672         574 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12673             :     {
   12674         525 :         int anDimIds[2] = {-1, -1};
   12675         525 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12676         525 :         if (anDimIds[0] == nVectorDim ||
   12677          24 :             (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
   12678             :         {
   12679             : #ifdef NCDF_DEBUG
   12680             :             char szTemp2[NC_MAX_NAME + 1] = {};
   12681             :             CPL_IGNORE_RET_VAL(
   12682             :                 nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
   12683             :             CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
   12684             : #endif
   12685         525 :             poLayer->AddField(anPotentialVectorVarID[j]);
   12686             :         }
   12687             :     }
   12688             : 
   12689          49 :     if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
   12690           0 :         poLayer->GetGeomType() != wkbNone)
   12691             :     {
   12692          49 :         papoLayers.push_back(poLayer);
   12693             :     }
   12694             : 
   12695          98 :     return CE_None;
   12696             : }
   12697             : 
   12698             : // Get all coordinate and boundary variables full names referenced in
   12699             : // a given a NetCDF (or group) ID and its sub-groups.
   12700             : // These variables are identified in other variable's
   12701             : // "coordinates" and "bounds" attribute.
   12702             : // Searching coordinate and boundary variables may need to explore
   12703             : // parents groups (or other groups in case of reference given in form of an
   12704             : // absolute path).
   12705             : // See CF sections 5.2, 5.6 and 7.1
   12706         549 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
   12707             : {
   12708         549 :     int nVars = 0;
   12709         549 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12710             : 
   12711        3326 :     for (int v = 0; v < nVars; v++)
   12712             :     {
   12713        2777 :         char *pszTemp = nullptr;
   12714        5554 :         CPLStringList aosTokens;
   12715        2777 :         if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
   12716         454 :             aosTokens.Assign(NCDFTokenizeCoordinatesAttribute(pszTemp));
   12717        2777 :         CPLFree(pszTemp);
   12718        2777 :         pszTemp = nullptr;
   12719        2777 :         if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
   12720        2777 :             pszTemp != nullptr && !EQUAL(pszTemp, ""))
   12721          17 :             aosTokens.AddString(pszTemp);
   12722        2777 :         CPLFree(pszTemp);
   12723        4081 :         for (int i = 0; i < aosTokens.size(); i++)
   12724             :         {
   12725        1304 :             char *pszVarFullName = nullptr;
   12726        1304 :             if (NCDFResolveVarFullName(nCdfId, aosTokens[i], &pszVarFullName) ==
   12727             :                 CE_None)
   12728        1278 :                 *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
   12729        1304 :             CPLFree(pszVarFullName);
   12730             :         }
   12731             :     }
   12732             : 
   12733             :     // Recurse on sub-groups.
   12734             :     int nSubGroups;
   12735         549 :     int *panSubGroupIds = nullptr;
   12736         549 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12737         584 :     for (int i = 0; i < nSubGroups; i++)
   12738             :     {
   12739          35 :         NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
   12740             :     }
   12741         549 :     CPLFree(panSubGroupIds);
   12742             : 
   12743         549 :     return CE_None;
   12744             : }
   12745             : 
   12746             : // Check if give type is user defined
   12747        1927 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
   12748             : {
   12749        1927 :     return type >= NC_FIRSTUSERTYPEID;
   12750             : }
   12751             : 
   12752         593 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
   12753             : {
   12754             :     // CF conventions use space as the separator for variable names in the
   12755             :     // coordinates attribute, but some products such as
   12756             :     // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
   12757             :     // use comma.
   12758         593 :     return CSLTokenizeString2(pszCoordinates, ", ", 0);
   12759             : }

Generated by: LCOV version 1.14