LCOV - code coverage report
Current view: top level - frmts/netcdf - netcdfdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4956 5866 84.5 %
Date: 2025-03-28 11:40:40 Functions: 156 163 95.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  netCDF read/write Driver
       4             :  * Purpose:  GDAL bindings over netCDF library.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *           Even Rouault <even.rouault at spatialys.com>
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2004, Frank Warmerdam
      10             :  * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com>
      11             :  * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com>
      12             :  * Copyright (c) 2021, CLS
      13             :  *
      14             :  * SPDX-License-Identifier: MIT
      15             :  ****************************************************************************/
      16             : 
      17             : #include "cpl_port.h"
      18             : 
      19             : #include <array>
      20             : #include <cassert>
      21             : #include <cctype>
      22             : #include <cerrno>
      23             : #include <climits>
      24             : #include <cmath>
      25             : #include <cstdio>
      26             : #include <cstdlib>
      27             : #include <cstring>
      28             : #include <ctime>
      29             : #include <algorithm>
      30             : #include <limits>
      31             : #include <map>
      32             : #include <set>
      33             : #include <queue>
      34             : #include <string>
      35             : #include <tuple>
      36             : #include <utility>
      37             : #include <vector>
      38             : 
      39             : // Must be included after standard includes, otherwise VS2015 fails when
      40             : // including <ctime>
      41             : #include "netcdfdataset.h"
      42             : #include "netcdfdrivercore.h"
      43             : #include "netcdfsg.h"
      44             : #include "netcdfuffd.h"
      45             : 
      46             : #include "netcdf_mem.h"
      47             : 
      48             : #include "cpl_conv.h"
      49             : #include "cpl_error.h"
      50             : #include "cpl_float.h"
      51             : #include "cpl_json.h"
      52             : #include "cpl_minixml.h"
      53             : #include "cpl_multiproc.h"
      54             : #include "cpl_progress.h"
      55             : #include "cpl_time.h"
      56             : #include "gdal.h"
      57             : #include "gdal_frmts.h"
      58             : #include "gdal_priv_templates.hpp"
      59             : #include "ogr_core.h"
      60             : #include "ogr_srs_api.h"
      61             : 
      62             : // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows
      63             : // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/,
      64             : // this is apparently back to expecting filenames in current codepage...
      65             : // Detect netCDF 4.8 with NC_ENCZARR
      66             : // Detect netCDF 4.9 with NC_NOATTCREORD
      67             : #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD)
      68             : #define NETCDF_USES_UTF8
      69             : #endif
      70             : 
      71             : // Internal function declarations.
      72             : 
      73             : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget);
      74             : 
      75             : static void
      76             : NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion,
      77             :                    bool bWriteGDALHistory, const char *pszOldHist,
      78             :                    const char *pszFunctionName,
      79             :                    const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS);
      80             : 
      81             : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
      82             :                            const char *pszOldHist);
      83             : 
      84             : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
      85             :                              size_t *nDestSize);
      86             : 
      87             : // Var / attribute helper functions.
      88             : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
      89             :                           const char *pszValue);
      90             : 
      91             : // Replace this where used.
      92             : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue);
      93             : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue);
      94             : 
      95             : // Replace this where used.
      96             : static char **NCDFTokenizeArray(const char *pszValue);
      97             : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
      98             :                          GDALRasterBand *poDstBand, int fpImage, int CDFVarID,
      99             :                          const char *pszMatchPrefix = nullptr);
     100             : 
     101             : // NetCDF-4 groups helper functions.
     102             : // They all work also for NetCDF-3 files which are considered as
     103             : // NetCDF-4 file with only one group.
     104             : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
     105             :                                  int *pnGroupId, int *pnVarId);
     106             : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds);
     107             : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
     108             :                                int **ppanSubGroupIds);
     109             : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
     110             :                                    bool bNC3Compat = true);
     111             : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
     112             :                                  bool bNC3Compat = true);
     113             : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId);
     114             : 
     115             : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
     116             :                                      char **ppszFullName,
     117             :                                      bool bMandatory = false);
     118             : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
     119             :                                 const char *pszAtt, int *pnAtt,
     120             :                                 bool bMandatory = false);
     121             : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars);
     122             : 
     123             : // Uncomment this for more debug output.
     124             : // #define NCDF_DEBUG 1
     125             : 
     126             : CPLMutex *hNCMutex = nullptr;
     127             : 
     128             : // Workaround https://github.com/OSGeo/gdal/issues/6253
     129             : // Having 2 netCDF handles on the same file doesn't work in a multi-threaded
     130             : // way. Apparently having the same handle works better (this is OK since
     131             : // we have a global mutex on the netCDF library)
     132             : static std::map<std::string, int> goMapNameToNetCDFId;
     133             : static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount;
     134             : 
     135         663 : int GDAL_nc_open(const char *pszFilename, int nMode, int *pID)
     136             : {
     137        1326 :     std::string osKey(pszFilename);
     138         663 :     osKey += "#####";
     139         663 :     osKey += std::to_string(nMode);
     140         663 :     auto oIter = goMapNameToNetCDFId.find(osKey);
     141         663 :     if (oIter == goMapNameToNetCDFId.end())
     142             :     {
     143         614 :         int ret = nc_open(pszFilename, nMode, pID);
     144         614 :         if (ret != NC_NOERR)
     145           3 :             return ret;
     146         611 :         goMapNameToNetCDFId[osKey] = *pID;
     147         611 :         goMapNetCDFIdToKeyAndCount[*pID] =
     148        1222 :             std::pair<std::string, int>(osKey, 1);
     149         611 :         return ret;
     150             :     }
     151             :     else
     152             :     {
     153          49 :         *pID = oIter->second;
     154          49 :         goMapNetCDFIdToKeyAndCount[oIter->second].second++;
     155          49 :         return NC_NOERR;
     156             :     }
     157             : }
     158             : 
     159         918 : int GDAL_nc_close(int cdfid)
     160             : {
     161         918 :     int ret = NC_NOERR;
     162         918 :     auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid);
     163         918 :     if (oIter != goMapNetCDFIdToKeyAndCount.end())
     164             :     {
     165         660 :         if (--oIter->second.second == 0)
     166             :         {
     167         611 :             ret = nc_close(cdfid);
     168         611 :             goMapNameToNetCDFId.erase(oIter->second.first);
     169         611 :             goMapNetCDFIdToKeyAndCount.erase(oIter);
     170             :         }
     171             :     }
     172             :     else
     173             :     {
     174             :         // we can go here if file opened with nc_open_mem() or nc_create()
     175         258 :         ret = nc_close(cdfid);
     176             :     }
     177         918 :     return ret;
     178             : }
     179             : 
     180             : /************************************************************************/
     181             : /* ==================================================================== */
     182             : /*                         netCDFRasterBand                             */
     183             : /* ==================================================================== */
     184             : /************************************************************************/
     185             : 
     186             : class netCDFRasterBand final : public GDALPamRasterBand
     187             : {
     188             :     friend class netCDFDataset;
     189             : 
     190             :     nc_type nc_datatype;
     191             :     int cdfid;
     192             :     int nZId;
     193             :     int nZDim;
     194             :     int nLevel;
     195             :     int nBandXPos;
     196             :     int nBandYPos;
     197             :     int *panBandZPos;
     198             :     int *panBandZLev;
     199             :     bool m_bNoDataSet = false;
     200             :     double m_dfNoDataValue = 0;
     201             :     bool m_bNoDataSetAsInt64 = false;
     202             :     int64_t m_nNodataValueInt64 = 0;
     203             :     bool m_bNoDataSetAsUInt64 = false;
     204             :     uint64_t m_nNodataValueUInt64 = 0;
     205             :     bool bValidRangeValid = false;
     206             :     double adfValidRange[2]{0, 0};
     207             :     bool m_bHaveScale = false;
     208             :     bool m_bHaveOffset = false;
     209             :     double m_dfScale = 1;
     210             :     double m_dfOffset = 0;
     211             :     CPLString m_osUnitType{};
     212             :     bool bSignedData;
     213             :     bool bCheckLongitude;
     214             :     bool m_bCreateMetadataFromOtherVarsDone = false;
     215             : 
     216             :     void CreateMetadataFromAttributes();
     217             :     void CreateMetadataFromOtherVars();
     218             : 
     219             :     template <class T>
     220             :     void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     221             :                    size_t nTmpBlockYSize, bool bCheckIsNan = false);
     222             :     template <class T>
     223             :     void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize,
     224             :                       size_t nTmpBlockYSize, bool bCheckIsNan = false);
     225             :     void SetBlockSize();
     226             : 
     227             :     bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage);
     228             : 
     229             :     void SetNoDataValueNoUpdate(double dfNoData);
     230             :     void SetNoDataValueNoUpdate(int64_t nNoData);
     231             :     void SetNoDataValueNoUpdate(uint64_t nNoData);
     232             : 
     233             :     void SetOffsetNoUpdate(double dfVal);
     234             :     void SetScaleNoUpdate(double dfVal);
     235             :     void SetUnitTypeNoUpdate(const char *pszNewValue);
     236             : 
     237             :   protected:
     238             :     CPLXMLNode *SerializeToXML(const char *pszUnused) override;
     239             : 
     240             :   public:
     241             :     struct CONSTRUCTOR_OPEN
     242             :     {
     243             :     };
     244             : 
     245             :     struct CONSTRUCTOR_CREATE
     246             :     {
     247             :     };
     248             : 
     249             :     netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS,
     250             :                      int nGroupId, int nZId, int nZDim, int nLevel,
     251             :                      const int *panBandZLen, const int *panBandPos, int nBand);
     252             :     netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS,
     253             :                      GDALDataType eType, int nBand, bool bSigned = true,
     254             :                      const char *pszBandName = nullptr,
     255             :                      const char *pszLongName = nullptr, int nZId = -1,
     256             :                      int nZDim = 2, int nLevel = 0,
     257             :                      const int *panBandZLev = nullptr,
     258             :                      const int *panBandZPos = nullptr,
     259             :                      const int *paDimIds = nullptr);
     260             :     virtual ~netCDFRasterBand();
     261             : 
     262             :     virtual double GetNoDataValue(int *) override;
     263             :     virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override;
     264             :     virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override;
     265             :     virtual CPLErr SetNoDataValue(double) override;
     266             :     virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override;
     267             :     virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override;
     268             :     // virtual CPLErr DeleteNoDataValue();
     269             :     virtual double GetOffset(int *) override;
     270             :     virtual CPLErr SetOffset(double) override;
     271             :     virtual double GetScale(int *) override;
     272             :     virtual CPLErr SetScale(double) override;
     273             :     virtual const char *GetUnitType() override;
     274             :     virtual CPLErr SetUnitType(const char *) override;
     275             :     virtual CPLErr IReadBlock(int, int, void *) override;
     276             :     virtual CPLErr IWriteBlock(int, int, void *) override;
     277             : 
     278             :     char **GetMetadata(const char *pszDomain = "") override;
     279             :     const char *GetMetadataItem(const char *pszName,
     280             :                                 const char *pszDomain = "") override;
     281             : 
     282             :     virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
     283             :                                    const char *pszDomain = "") override;
     284             :     virtual CPLErr SetMetadata(char **papszMD,
     285             :                                const char *pszDomain = "") override;
     286             : };
     287             : 
     288             : /************************************************************************/
     289             : /*                          netCDFRasterBand()                          */
     290             : /************************************************************************/
     291             : 
     292         469 : netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &,
     293             :                                    netCDFDataset *poNCDFDS, int nGroupId,
     294             :                                    int nZIdIn, int nZDimIn, int nLevelIn,
     295             :                                    const int *panBandZLevIn,
     296         469 :                                    const int *panBandZPosIn, int nBandIn)
     297             :     : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn),
     298         469 :       nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]),
     299         469 :       nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr),
     300             :       panBandZLev(nullptr),
     301             :       bSignedData(true),  // Default signed, except for Byte.
     302         938 :       bCheckLongitude(false)
     303             : {
     304         469 :     poDS = poNCDFDS;
     305         469 :     nBand = nBandIn;
     306             : 
     307             :     // Take care of all other dimensions.
     308         469 :     if (nZDim > 2)
     309             :     {
     310         168 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     311         168 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     312             : 
     313         462 :         for (int i = 0; i < nZDim - 2; i++)
     314             :         {
     315         294 :             panBandZPos[i] = panBandZPosIn[i + 2];
     316         294 :             panBandZLev[i] = panBandZLevIn[i];
     317             :         }
     318             :     }
     319             : 
     320         469 :     nRasterXSize = poDS->GetRasterXSize();
     321         469 :     nRasterYSize = poDS->GetRasterYSize();
     322         469 :     nBlockXSize = poDS->GetRasterXSize();
     323         469 :     nBlockYSize = 1;
     324             : 
     325             :     // Get the type of the "z" variable, our target raster array.
     326         469 :     if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr,
     327         469 :                    nullptr) != NC_NOERR)
     328             :     {
     329           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'.");
     330           0 :         return;
     331             :     }
     332             : 
     333         469 :     if (NCDFIsUserDefinedType(cdfid, nc_datatype))
     334             :     {
     335             :         // First enquire and check that the number of fields is 2
     336             :         size_t nfields, compoundsize;
     337           5 :         if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize,
     338           5 :                             &nfields) != NC_NOERR)
     339             :         {
     340           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     341             :                      "Error in nc_inq_compound() on 'z'.");
     342           0 :             return;
     343             :         }
     344             : 
     345           5 :         if (nfields != 2)
     346             :         {
     347           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     348             :                      "Unsupported data type encountered in nc_inq_compound() "
     349             :                      "on 'z'.");
     350           0 :             return;
     351             :         }
     352             : 
     353             :         // Now check that that two types are the same in the struct.
     354             :         nc_type field_type1, field_type2;
     355             :         int field_dims1, field_dims2;
     356           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     357             :                                   &field_type1, &field_dims1,
     358           5 :                                   nullptr) != NC_NOERR)
     359             :         {
     360           0 :             CPLError(
     361             :                 CE_Failure, CPLE_AppDefined,
     362             :                 "Error in querying Field 1 in nc_inq_compound_field() on 'z'.");
     363           0 :             return;
     364             :         }
     365             : 
     366           5 :         if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr,
     367             :                                   &field_type2, &field_dims2,
     368           5 :                                   nullptr) != NC_NOERR)
     369             :         {
     370           0 :             CPLError(
     371             :                 CE_Failure, CPLE_AppDefined,
     372             :                 "Error in querying Field 2 in nc_inq_compound_field() on 'z'.");
     373           0 :             return;
     374             :         }
     375             : 
     376           5 :         if ((field_type1 != field_type2) || (field_dims1 != field_dims2) ||
     377           5 :             (field_dims1 != 0))
     378             :         {
     379           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     380             :                      "Error in interpreting compound data type on 'z'.");
     381           0 :             return;
     382             :         }
     383             : 
     384           5 :         if (field_type1 == NC_SHORT)
     385           0 :             eDataType = GDT_CInt16;
     386           5 :         else if (field_type1 == NC_INT)
     387           0 :             eDataType = GDT_CInt32;
     388           5 :         else if (field_type1 == NC_FLOAT)
     389           4 :             eDataType = GDT_CFloat32;
     390           1 :         else if (field_type1 == NC_DOUBLE)
     391           1 :             eDataType = GDT_CFloat64;
     392             :         else
     393             :         {
     394           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     395             :                      "Unsupported netCDF compound data type encountered.");
     396           0 :             return;
     397             :         }
     398             :     }
     399             :     else
     400             :     {
     401         464 :         if (nc_datatype == NC_BYTE)
     402         143 :             eDataType = GDT_Byte;
     403         321 :         else if (nc_datatype == NC_CHAR)
     404           0 :             eDataType = GDT_Byte;
     405         321 :         else if (nc_datatype == NC_SHORT)
     406          41 :             eDataType = GDT_Int16;
     407         280 :         else if (nc_datatype == NC_INT)
     408          89 :             eDataType = GDT_Int32;
     409         191 :         else if (nc_datatype == NC_FLOAT)
     410         115 :             eDataType = GDT_Float32;
     411          76 :         else if (nc_datatype == NC_DOUBLE)
     412          40 :             eDataType = GDT_Float64;
     413          36 :         else if (nc_datatype == NC_UBYTE)
     414          14 :             eDataType = GDT_Byte;
     415          22 :         else if (nc_datatype == NC_USHORT)
     416           4 :             eDataType = GDT_UInt16;
     417          18 :         else if (nc_datatype == NC_UINT)
     418           3 :             eDataType = GDT_UInt32;
     419          15 :         else if (nc_datatype == NC_INT64)
     420           8 :             eDataType = GDT_Int64;
     421           7 :         else if (nc_datatype == NC_UINT64)
     422           7 :             eDataType = GDT_UInt64;
     423             :         else
     424             :         {
     425           0 :             if (nBand == 1)
     426           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     427             :                          "Unsupported netCDF datatype (%d), treat as Float32.",
     428           0 :                          static_cast<int>(nc_datatype));
     429           0 :             eDataType = GDT_Float32;
     430           0 :             nc_datatype = NC_FLOAT;
     431             :         }
     432             :     }
     433             : 
     434             :     // Find and set No Data for this variable.
     435         469 :     nc_type atttype = NC_NAT;
     436         469 :     size_t attlen = 0;
     437         469 :     const char *pszNoValueName = nullptr;
     438             : 
     439             :     // Find attribute name, either _FillValue or missing_value.
     440         469 :     int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen);
     441         469 :     if (status == NC_NOERR)
     442             :     {
     443         248 :         pszNoValueName = NCDF_FillValue;
     444             :     }
     445             :     else
     446             :     {
     447         221 :         status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen);
     448         221 :         if (status == NC_NOERR)
     449             :         {
     450          12 :             pszNoValueName = "missing_value";
     451             :         }
     452             :     }
     453             : 
     454             :     // Fetch missing value.
     455         469 :     double dfNoData = 0.0;
     456         469 :     bool bGotNoData = false;
     457         469 :     int64_t nNoDataAsInt64 = 0;
     458         469 :     bool bGotNoDataAsInt64 = false;
     459         469 :     uint64_t nNoDataAsUInt64 = 0;
     460         469 :     bool bGotNoDataAsUInt64 = false;
     461         469 :     if (status == NC_NOERR)
     462             :     {
     463         260 :         nc_type nAttrType = NC_NAT;
     464         260 :         size_t nAttrLen = 0;
     465         260 :         status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen);
     466         260 :         if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64)
     467             :         {
     468             :             long long v;
     469           7 :             nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v);
     470           7 :             bGotNoData = true;
     471           7 :             bGotNoDataAsInt64 = true;
     472           7 :             nNoDataAsInt64 = static_cast<int64_t>(v);
     473             :         }
     474         253 :         else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64)
     475             :         {
     476             :             unsigned long long v;
     477           7 :             nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v);
     478           7 :             bGotNoData = true;
     479           7 :             bGotNoDataAsUInt64 = true;
     480           7 :             nNoDataAsUInt64 = static_cast<uint64_t>(v);
     481             :         }
     482         246 :         else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None)
     483             :         {
     484         245 :             bGotNoData = true;
     485             :         }
     486             :     }
     487             : 
     488             :     // If NoData was not found, use the default value, but for non-Byte types
     489             :     // as it is not recommended:
     490             :     // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html
     491         469 :     nc_type vartype = NC_NAT;
     492         469 :     if (!bGotNoData)
     493             :     {
     494         210 :         nc_inq_vartype(cdfid, nZId, &vartype);
     495         210 :         if (vartype == NC_INT64)
     496             :         {
     497             :             nNoDataAsInt64 =
     498           1 :                 NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData);
     499           1 :             bGotNoDataAsInt64 = bGotNoData;
     500             :         }
     501         209 :         else if (vartype == NC_UINT64)
     502             :         {
     503             :             nNoDataAsUInt64 =
     504           0 :                 NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData);
     505           0 :             bGotNoDataAsUInt64 = bGotNoData;
     506             :         }
     507         209 :         else if (vartype != NC_CHAR && vartype != NC_BYTE &&
     508          89 :                  vartype != NC_UBYTE)
     509             :         {
     510          81 :             dfNoData =
     511          81 :                 NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData);
     512          81 :             if (bGotNoData)
     513             :             {
     514          70 :                 CPLDebug("GDAL_netCDF",
     515             :                          "did not get nodata value for variable #%d, using "
     516             :                          "default %f",
     517             :                          nZId, dfNoData);
     518             :             }
     519             :         }
     520             :     }
     521             : 
     522         469 :     bool bHasUnderscoreUnsignedAttr = false;
     523         469 :     bool bUnderscoreUnsignedAttrVal = false;
     524             :     {
     525         469 :         char *pszTemp = nullptr;
     526         469 :         if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None)
     527             :         {
     528         135 :             if (EQUAL(pszTemp, "true"))
     529             :             {
     530         127 :                 bHasUnderscoreUnsignedAttr = true;
     531         127 :                 bUnderscoreUnsignedAttrVal = true;
     532             :             }
     533           8 :             else if (EQUAL(pszTemp, "false"))
     534             :             {
     535           8 :                 bHasUnderscoreUnsignedAttr = true;
     536           8 :                 bUnderscoreUnsignedAttrVal = false;
     537             :             }
     538         135 :             CPLFree(pszTemp);
     539             :         }
     540             :     }
     541             : 
     542             :     // Look for valid_range or valid_min/valid_max.
     543             : 
     544             :     // First look for valid_range.
     545         469 :     if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true))
     546             :     {
     547         467 :         char *pszValidRange = nullptr;
     548         467 :         if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) ==
     549         128 :                 CE_None &&
     550         595 :             pszValidRange[0] == '{' &&
     551         128 :             pszValidRange[strlen(pszValidRange) - 1] == '}')
     552             :         {
     553             :             const std::string osValidRange =
     554         384 :                 std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2);
     555             :             const CPLStringList aosValidRange(
     556         256 :                 CSLTokenizeString2(osValidRange.c_str(), ",", 0));
     557         128 :             if (aosValidRange.size() == 2 &&
     558         256 :                 CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING &&
     559         128 :                 CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING)
     560             :             {
     561         128 :                 bValidRangeValid = true;
     562         128 :                 adfValidRange[0] = CPLAtof(aosValidRange[0]);
     563         128 :                 adfValidRange[1] = CPLAtof(aosValidRange[1]);
     564             :             }
     565             :         }
     566         467 :         CPLFree(pszValidRange);
     567             : 
     568             :         // If not found look for valid_min and valid_max.
     569         467 :         if (!bValidRangeValid)
     570             :         {
     571         339 :             double dfMin = 0;
     572         339 :             double dfMax = 0;
     573         354 :             if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None &&
     574          15 :                 NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None)
     575             :             {
     576           8 :                 adfValidRange[0] = dfMin;
     577           8 :                 adfValidRange[1] = dfMax;
     578           8 :                 bValidRangeValid = true;
     579             :             }
     580             :         }
     581             : 
     582         467 :         if (bValidRangeValid &&
     583         136 :             (adfValidRange[0] < 0 || adfValidRange[1] < 0) &&
     584          17 :             nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr &&
     585             :             bUnderscoreUnsignedAttrVal)
     586             :         {
     587           2 :             if (adfValidRange[0] < 0)
     588           0 :                 adfValidRange[0] += 65536;
     589           2 :             if (adfValidRange[1] < 0)
     590           2 :                 adfValidRange[1] += 65536;
     591           2 :             if (adfValidRange[0] <= adfValidRange[1])
     592             :             {
     593             :                 // Updating metadata item
     594           2 :                 GDALPamRasterBand::SetMetadataItem(
     595             :                     "valid_range",
     596           2 :                     CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]),
     597           2 :                                static_cast<int>(adfValidRange[1])));
     598             :             }
     599             :         }
     600             : 
     601         467 :         if (bValidRangeValid && adfValidRange[0] > adfValidRange[1])
     602             :         {
     603           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     604             :                      "netCDFDataset::valid_range: min > max:\n"
     605             :                      "  min: %lf\n  max: %lf\n",
     606             :                      adfValidRange[0], adfValidRange[1]);
     607           0 :             bValidRangeValid = false;
     608           0 :             adfValidRange[0] = 0.0;
     609           0 :             adfValidRange[1] = 0.0;
     610             :         }
     611             :     }
     612             : 
     613             :     // Special For Byte Bands: check for signed/unsigned byte.
     614         469 :     if (nc_datatype == NC_BYTE)
     615             :     {
     616             :         // netcdf uses signed byte by default, but GDAL uses unsigned by default
     617             :         // This may cause unexpected results, but is needed for back-compat.
     618         143 :         if (poNCDFDS->bIsGdalFile)
     619         122 :             bSignedData = false;
     620             :         else
     621          21 :             bSignedData = true;
     622             : 
     623             :         // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned.
     624             :         // But in case a NC3 file was converted automatically and has hints
     625             :         // that it is unsigned, take them into account
     626         143 :         if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     627             :         {
     628           3 :             bSignedData = true;
     629             :         }
     630             : 
     631             :         // If we got valid_range, test for signed/unsigned range.
     632             :         // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html
     633         143 :         if (bValidRangeValid)
     634             :         {
     635             :             // If we got valid_range={0,255}, treat as unsigned.
     636         124 :             if (adfValidRange[0] == 0 && adfValidRange[1] == 255)
     637             :             {
     638         116 :                 bSignedData = false;
     639             :                 // Reset valid_range.
     640         116 :                 bValidRangeValid = false;
     641             :             }
     642             :             // If we got valid_range={-128,127}, treat as signed.
     643           8 :             else if (adfValidRange[0] == -128 && adfValidRange[1] == 127)
     644             :             {
     645           8 :                 bSignedData = true;
     646             :                 // Reset valid_range.
     647           8 :                 bValidRangeValid = false;
     648             :             }
     649             :         }
     650             :         // Else test for _Unsigned.
     651             :         // https://docs.unidata.ucar.edu/nug/current/best_practices.html
     652             :         else
     653             :         {
     654          19 :             if (bHasUnderscoreUnsignedAttr)
     655           7 :                 bSignedData = !bUnderscoreUnsignedAttrVal;
     656             :         }
     657             : 
     658         143 :         if (bSignedData)
     659             :         {
     660          20 :             eDataType = GDT_Int8;
     661             :         }
     662         123 :         else if (dfNoData < 0)
     663             :         {
     664             :             // Fix nodata value as it was stored signed.
     665           6 :             dfNoData += 256;
     666           6 :             if (pszNoValueName)
     667             :             {
     668             :                 // Updating metadata item
     669           6 :                 GDALPamRasterBand::SetMetadataItem(
     670             :                     pszNoValueName,
     671             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     672             :             }
     673             :         }
     674             :     }
     675         326 :     else if (nc_datatype == NC_SHORT)
     676             :     {
     677          41 :         if (bHasUnderscoreUnsignedAttr)
     678             :         {
     679           4 :             bSignedData = !bUnderscoreUnsignedAttrVal;
     680           4 :             if (!bSignedData)
     681           4 :                 eDataType = GDT_UInt16;
     682             :         }
     683             : 
     684             :         // Fix nodata value as it was stored signed.
     685          41 :         if (!bSignedData && dfNoData < 0)
     686             :         {
     687           4 :             dfNoData += 65536;
     688           4 :             if (pszNoValueName)
     689             :             {
     690             :                 // Updating metadata item
     691           4 :                 GDALPamRasterBand::SetMetadataItem(
     692             :                     pszNoValueName,
     693             :                     CPLSPrintf("%d", static_cast<int>(dfNoData)));
     694             :             }
     695             :         }
     696             :     }
     697             : 
     698         285 :     else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT ||
     699         267 :              nc_datatype == NC_UINT || nc_datatype == NC_UINT64)
     700             :     {
     701          28 :         bSignedData = false;
     702             :     }
     703             : 
     704         469 :     CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d",
     705         469 :              nc_datatype, eDataType, static_cast<int>(bSignedData));
     706             : 
     707         469 :     if (bGotNoData)
     708             :     {
     709             :         // Set nodata value.
     710         330 :         if (bGotNoDataAsInt64)
     711             :         {
     712           8 :             if (eDataType == GDT_Int64)
     713             :             {
     714           8 :                 SetNoDataValueNoUpdate(nNoDataAsInt64);
     715             :             }
     716           0 :             else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0)
     717             :             {
     718           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64));
     719             :             }
     720             :             else
     721             :             {
     722           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64));
     723             :             }
     724             :         }
     725         322 :         else if (bGotNoDataAsUInt64)
     726             :         {
     727           7 :             if (eDataType == GDT_UInt64)
     728             :             {
     729           7 :                 SetNoDataValueNoUpdate(nNoDataAsUInt64);
     730             :             }
     731           0 :             else if (eDataType == GDT_Int64 &&
     732             :                      nNoDataAsUInt64 <=
     733           0 :                          static_cast<uint64_t>(
     734           0 :                              std::numeric_limits<int64_t>::max()))
     735             :             {
     736           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64));
     737             :             }
     738             :             else
     739             :             {
     740           0 :                 SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64));
     741             :             }
     742             :         }
     743             :         else
     744             :         {
     745             : #ifdef NCDF_DEBUG
     746             :             CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData);
     747             : #endif
     748         315 :             if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData))
     749             :             {
     750           0 :                 SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData));
     751             :             }
     752         315 :             else if (eDataType == GDT_UInt64 &&
     753           0 :                      GDALIsValueExactAs<uint64_t>(dfNoData))
     754             :             {
     755           0 :                 SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData));
     756             :             }
     757             :             else
     758             :             {
     759         315 :                 SetNoDataValueNoUpdate(dfNoData);
     760             :             }
     761             :         }
     762             :     }
     763             : 
     764         469 :     CreateMetadataFromAttributes();
     765             : 
     766             :     // Attempt to fetch the scale_factor and add_offset attributes for the
     767             :     // variable and set them.  If these values are not available, set
     768             :     // offset to 0 and scale to 1.
     769         469 :     if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR)
     770             :     {
     771          16 :         double dfOffset = 0;
     772          16 :         status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset);
     773          16 :         CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset,
     774             :                  status);
     775          16 :         SetOffsetNoUpdate(dfOffset);
     776             :     }
     777             : 
     778         469 :     bool bHasScale = false;
     779         469 :     if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR)
     780             :     {
     781          20 :         bHasScale = true;
     782          20 :         double dfScale = 1;
     783          20 :         status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale);
     784          20 :         CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale,
     785             :                  status);
     786          20 :         SetScaleNoUpdate(dfScale);
     787             :     }
     788             : 
     789          12 :     if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) &&
     790           4 :         eDataType != GDT_Int64 && eDataType != GDT_UInt64 &&
     791           4 :         (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 ||
     792         481 :          std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) &&
     793           1 :         CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") ==
     794             :             nullptr)
     795             :     {
     796           1 :         CPLError(CE_Warning, CPLE_AppDefined,
     797             :                  "validity range = %f, %f contains floating-point values, "
     798             :                  "whereas data type is integer. valid_range is thus likely "
     799             :                  "wrong%s. Ignoring it.",
     800             :                  adfValidRange[0], adfValidRange[1],
     801             :                  bHasScale ? " (likely scaled using scale_factor/add_factor "
     802             :                              "whereas it should be using the packed data type)"
     803             :                            : "");
     804           1 :         bValidRangeValid = false;
     805           1 :         adfValidRange[0] = 0.0;
     806           1 :         adfValidRange[1] = 0.0;
     807             :     }
     808             : 
     809             :     // Should we check for longitude values > 360?
     810         469 :     bCheckLongitude =
     811         938 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) &&
     812         469 :         NCDFIsVarLongitude(cdfid, nZId, nullptr);
     813             : 
     814             :     // Attempt to fetch the units attribute for the variable and set it.
     815         469 :     SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS));
     816             : 
     817         469 :     SetBlockSize();
     818             : }
     819             : 
     820         650 : void netCDFRasterBand::SetBlockSize()
     821             : {
     822             :     // Check for variable chunking (netcdf-4 only).
     823             :     // GDAL block size should be set to hdf5 chunk size.
     824         650 :     int nTmpFormat = 0;
     825         650 :     int status = nc_inq_format(cdfid, &nTmpFormat);
     826         650 :     NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
     827         650 :     if ((status == NC_NOERR) &&
     828         558 :         (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C))
     829             :     {
     830         108 :         size_t chunksize[MAX_NC_DIMS] = {};
     831             :         // Check for chunksize and set it as the blocksize (optimizes read).
     832         108 :         status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize);
     833         108 :         if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED))
     834             :         {
     835          13 :             nBlockXSize = (int)chunksize[nZDim - 1];
     836          13 :             if (nZDim >= 2)
     837          13 :                 nBlockYSize = (int)chunksize[nZDim - 2];
     838             :             else
     839           0 :                 nBlockYSize = 1;
     840             :         }
     841             :     }
     842             : 
     843             :     // Deal with bottom-up datasets and nBlockYSize != 1.
     844         650 :     auto poGDS = static_cast<netCDFDataset *>(poDS);
     845         650 :     if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr)
     846             :     {
     847           5 :         if (poGDS->eAccess == GA_ReadOnly)
     848             :         {
     849             :             // Try to cache 1 or 2 'rows' of netCDF chunks along the whole
     850             :             // width of the raster
     851           5 :             size_t nChunks =
     852           5 :                 static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize));
     853           5 :             if ((nRasterYSize % nBlockYSize) != 0)
     854           1 :                 nChunks *= 2;
     855             :             const size_t nChunkSize =
     856           5 :                 static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
     857           5 :                 nBlockXSize * nBlockYSize;
     858           5 :             constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024;
     859           5 :             nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize);
     860           5 :             if (nChunks)
     861             :             {
     862           5 :                 poGDS->poChunkCache.reset(
     863           5 :                     new netCDFDataset::ChunkCacheType(nChunks));
     864             :             }
     865             :         }
     866             :         else
     867             :         {
     868           0 :             nBlockYSize = 1;
     869             :         }
     870             :     }
     871         650 : }
     872             : 
     873             : // Constructor in create mode.
     874             : // If nZId and following variables are not passed, the band will have 2
     875             : // dimensions.
     876             : // TODO: Get metadata, missing val from band #1 if nZDim > 2.
     877         181 : netCDFRasterBand::netCDFRasterBand(
     878             :     const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS,
     879             :     const GDALDataType eTypeIn, int nBandIn, bool bSigned,
     880             :     const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn,
     881             :     int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn,
     882         181 :     const int *paDimIds)
     883         181 :     : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn),
     884             :       nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0),
     885             :       panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned),
     886         181 :       bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true)
     887             : {
     888         181 :     poDS = poNCDFDS;
     889         181 :     nBand = nBandIn;
     890             : 
     891         181 :     nRasterXSize = poDS->GetRasterXSize();
     892         181 :     nRasterYSize = poDS->GetRasterYSize();
     893         181 :     nBlockXSize = poDS->GetRasterXSize();
     894         181 :     nBlockYSize = 1;
     895             : 
     896         181 :     if (poDS->GetAccess() != GA_Update)
     897             :     {
     898           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     899             :                  "Dataset is not in update mode, "
     900             :                  "wrong netCDFRasterBand constructor");
     901           0 :         return;
     902             :     }
     903             : 
     904             :     // Take care of all other dimensions.
     905         181 :     if (nZDim > 2 && paDimIds != nullptr)
     906             :     {
     907          27 :         nBandXPos = panBandZPosIn[0];
     908          27 :         nBandYPos = panBandZPosIn[1];
     909             : 
     910          27 :         panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     911          27 :         panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int)));
     912             : 
     913          76 :         for (int i = 0; i < nZDim - 2; i++)
     914             :         {
     915          49 :             panBandZPos[i] = panBandZPosIn[i + 2];
     916          49 :             panBandZLev[i] = panBandZLevIn[i];
     917             :         }
     918             :     }
     919             : 
     920             :     // Get the type of the "z" variable, our target raster array.
     921         181 :     eDataType = eTypeIn;
     922             : 
     923         181 :     switch (eDataType)
     924             :     {
     925          76 :         case GDT_Byte:
     926          76 :             nc_datatype = NC_BYTE;
     927             :             // NC_UBYTE (unsigned byte) is only available for NC4.
     928          76 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     929           3 :                 nc_datatype = NC_UBYTE;
     930          76 :             break;
     931           7 :         case GDT_Int8:
     932           7 :             nc_datatype = NC_BYTE;
     933           7 :             break;
     934          11 :         case GDT_Int16:
     935          11 :             nc_datatype = NC_SHORT;
     936          11 :             break;
     937          24 :         case GDT_Int32:
     938          24 :             nc_datatype = NC_INT;
     939          24 :             break;
     940          13 :         case GDT_Float32:
     941          13 :             nc_datatype = NC_FLOAT;
     942          13 :             break;
     943           8 :         case GDT_Float64:
     944           8 :             nc_datatype = NC_DOUBLE;
     945           8 :             break;
     946           7 :         case GDT_Int64:
     947           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     948             :             {
     949           7 :                 nc_datatype = NC_INT64;
     950             :             }
     951             :             else
     952             :             {
     953           0 :                 if (nBand == 1)
     954           0 :                     CPLError(
     955             :                         CE_Warning, CPLE_AppDefined,
     956             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     957             :                         "Int64");
     958           0 :                 nc_datatype = NC_DOUBLE;
     959           0 :                 eDataType = GDT_Float64;
     960             :             }
     961           7 :             break;
     962           7 :         case GDT_UInt64:
     963           7 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     964             :             {
     965           7 :                 nc_datatype = NC_UINT64;
     966             :             }
     967             :             else
     968             :             {
     969           0 :                 if (nBand == 1)
     970           0 :                     CPLError(
     971             :                         CE_Warning, CPLE_AppDefined,
     972             :                         "Unsupported GDAL datatype %s, treat as NC_DOUBLE.",
     973             :                         "UInt64");
     974           0 :                 nc_datatype = NC_DOUBLE;
     975           0 :                 eDataType = GDT_Float64;
     976             :             }
     977           7 :             break;
     978           6 :         case GDT_UInt16:
     979           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     980             :             {
     981           6 :                 nc_datatype = NC_USHORT;
     982           6 :                 break;
     983             :             }
     984             :             [[fallthrough]];
     985             :         case GDT_UInt32:
     986           6 :             if (poNCDFDS->eFormat == NCDF_FORMAT_NC4)
     987             :             {
     988           6 :                 nc_datatype = NC_UINT;
     989           6 :                 break;
     990             :             }
     991             :             [[fallthrough]];
     992             :         default:
     993          16 :             if (nBand == 1)
     994           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
     995             :                          "Unsupported GDAL datatype (%d), treat as NC_FLOAT.",
     996           8 :                          static_cast<int>(eDataType));
     997          16 :             nc_datatype = NC_FLOAT;
     998          16 :             eDataType = GDT_Float32;
     999          16 :             break;
    1000             :     }
    1001             : 
    1002             :     // Define the variable if necessary (if nZId == -1).
    1003         181 :     bool bDefineVar = false;
    1004             : 
    1005         181 :     if (nZId == -1)
    1006             :     {
    1007         159 :         bDefineVar = true;
    1008             : 
    1009             :         // Make sure we are in define mode.
    1010         159 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1011             : 
    1012             :         char szTempPrivate[256 + 1];
    1013         159 :         const char *pszTemp = nullptr;
    1014         159 :         if (!pszBandName || EQUAL(pszBandName, ""))
    1015             :         {
    1016         137 :             snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand);
    1017         137 :             pszTemp = szTempPrivate;
    1018             :         }
    1019             :         else
    1020             :         {
    1021          22 :             pszTemp = pszBandName;
    1022             :         }
    1023             : 
    1024             :         int status;
    1025         159 :         if (nZDim > 2 && paDimIds != nullptr)
    1026             :         {
    1027           5 :             status =
    1028           5 :                 nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId);
    1029             :         }
    1030             :         else
    1031             :         {
    1032         154 :             int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID};
    1033             :             status =
    1034         154 :                 nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId);
    1035             :         }
    1036         159 :         NCDF_ERR(status);
    1037         159 :         CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp,
    1038             :                  nc_datatype, nZId);
    1039             : 
    1040         159 :         if (!pszLongName || EQUAL(pszLongName, ""))
    1041             :         {
    1042         152 :             snprintf(szTempPrivate, sizeof(szTempPrivate),
    1043             :                      "GDAL Band Number %d", nBand);
    1044         152 :             pszTemp = szTempPrivate;
    1045             :         }
    1046             :         else
    1047             :         {
    1048           7 :             pszTemp = pszLongName;
    1049             :         }
    1050             :         status =
    1051         159 :             nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp);
    1052         159 :         NCDF_ERR(status);
    1053             : 
    1054         159 :         poNCDFDS->DefVarDeflate(nZId, true);
    1055             :     }
    1056             : 
    1057             :     // For Byte data add signed/unsigned info.
    1058         181 :     if (eDataType == GDT_Byte || eDataType == GDT_Int8)
    1059             :     {
    1060          83 :         if (bDefineVar)
    1061             :         {
    1062             :             // Only add attributes if creating variable.
    1063             :             // For unsigned NC_BYTE (except NC4 format),
    1064             :             // add valid_range and _Unsigned ( defined in CF-1 and NUG ).
    1065          75 :             if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4)
    1066             :             {
    1067          72 :                 CPLDebug("GDAL_netCDF",
    1068             :                          "adding valid_range attributes for Byte Band");
    1069          72 :                 short l_adfValidRange[2] = {0, 0};
    1070             :                 int status;
    1071          72 :                 if (bSignedData || eDataType == GDT_Int8)
    1072             :                 {
    1073           7 :                     l_adfValidRange[0] = -128;
    1074           7 :                     l_adfValidRange[1] = 127;
    1075           7 :                     status =
    1076           7 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false");
    1077             :                 }
    1078             :                 else
    1079             :                 {
    1080          65 :                     l_adfValidRange[0] = 0;
    1081          65 :                     l_adfValidRange[1] = 255;
    1082             :                     status =
    1083          65 :                         nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true");
    1084             :                 }
    1085          72 :                 NCDF_ERR(status);
    1086          72 :                 status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT,
    1087             :                                           2, l_adfValidRange);
    1088          72 :                 NCDF_ERR(status);
    1089             :             }
    1090             :         }
    1091             :     }
    1092             : 
    1093         181 :     if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR &&
    1094         101 :         nc_datatype != NC_UBYTE)
    1095             :     {
    1096             :         // Set default nodata.
    1097          98 :         bool bIgnored = false;
    1098             :         double dfNoData =
    1099          98 :             NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored);
    1100             : #ifdef NCDF_DEBUG
    1101             :         CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData);
    1102             : #endif
    1103          98 :         netCDFRasterBand::SetNoDataValue(dfNoData);
    1104             :     }
    1105             : 
    1106         181 :     SetBlockSize();
    1107             : }
    1108             : 
    1109             : /************************************************************************/
    1110             : /*                         ~netCDFRasterBand()                          */
    1111             : /************************************************************************/
    1112             : 
    1113        1300 : netCDFRasterBand::~netCDFRasterBand()
    1114             : {
    1115         650 :     netCDFRasterBand::FlushCache(true);
    1116         650 :     CPLFree(panBandZPos);
    1117         650 :     CPLFree(panBandZLev);
    1118        1300 : }
    1119             : 
    1120             : /************************************************************************/
    1121             : /*                          GetMetadata()                               */
    1122             : /************************************************************************/
    1123             : 
    1124          47 : char **netCDFRasterBand::GetMetadata(const char *pszDomain)
    1125             : {
    1126          47 :     if (!m_bCreateMetadataFromOtherVarsDone)
    1127          47 :         CreateMetadataFromOtherVars();
    1128          47 :     return GDALPamRasterBand::GetMetadata(pszDomain);
    1129             : }
    1130             : 
    1131             : /************************************************************************/
    1132             : /*                        GetMetadataItem()                             */
    1133             : /************************************************************************/
    1134             : 
    1135         538 : const char *netCDFRasterBand::GetMetadataItem(const char *pszName,
    1136             :                                               const char *pszDomain)
    1137             : {
    1138         538 :     if (!m_bCreateMetadataFromOtherVarsDone &&
    1139         522 :         STARTS_WITH(pszName, "NETCDF_DIM_") &&
    1140           1 :         (!pszDomain || pszDomain[0] == 0))
    1141           1 :         CreateMetadataFromOtherVars();
    1142         538 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
    1143             : }
    1144             : 
    1145             : /************************************************************************/
    1146             : /*                        SetMetadataItem()                             */
    1147             : /************************************************************************/
    1148             : 
    1149           7 : CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName,
    1150             :                                          const char *pszValue,
    1151             :                                          const char *pszDomain)
    1152             : {
    1153           9 :     if (GetAccess() == GA_Update &&
    1154           9 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    1155             :     {
    1156             :         // Same logic as in CopyMetadata()
    1157             : 
    1158           2 :         const char *const papszIgnoreBand[] = {
    1159             :             CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    1160             :             NCDF_FillValue, "coordinates",   nullptr};
    1161             :         // Do not copy varname, stats, NETCDF_DIM_*, nodata
    1162             :         // and items in papszIgnoreBand.
    1163           6 :         if (STARTS_WITH(pszName, "NETCDF_VARNAME") ||
    1164           2 :             STARTS_WITH(pszName, "STATISTICS_") ||
    1165           2 :             STARTS_WITH(pszName, "NETCDF_DIM_") ||
    1166           2 :             STARTS_WITH(pszName, "missing_value") ||
    1167           6 :             STARTS_WITH(pszName, "_FillValue") ||
    1168           2 :             CSLFindString(papszIgnoreBand, pszName) != -1)
    1169             :         {
    1170             :             // do nothing
    1171             :         }
    1172             :         else
    1173             :         {
    1174           2 :             cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1175             : 
    1176           2 :             if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue))
    1177           2 :                 return CE_Failure;
    1178             :         }
    1179             :     }
    1180             : 
    1181           5 :     return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);
    1182             : }
    1183             : 
    1184             : /************************************************************************/
    1185             : /*                          SetMetadata()                               */
    1186             : /************************************************************************/
    1187             : 
    1188           2 : CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain)
    1189             : {
    1190           4 :     if (GetAccess() == GA_Update &&
    1191           2 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    1192             :     {
    1193             :         // We don't handle metadata item removal for now
    1194           4 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    1195             :              ++papszIter)
    1196             :         {
    1197           2 :             char *pszName = nullptr;
    1198           2 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    1199           2 :             if (pszName && pszValue)
    1200           2 :                 SetMetadataItem(pszName, pszValue);
    1201           2 :             CPLFree(pszName);
    1202             :         }
    1203             :     }
    1204           2 :     return GDALPamRasterBand::SetMetadata(papszMD, pszDomain);
    1205             : }
    1206             : 
    1207             : /************************************************************************/
    1208             : /*                             GetOffset()                              */
    1209             : /************************************************************************/
    1210          49 : double netCDFRasterBand::GetOffset(int *pbSuccess)
    1211             : {
    1212          49 :     if (pbSuccess != nullptr)
    1213          44 :         *pbSuccess = static_cast<int>(m_bHaveOffset);
    1214             : 
    1215          49 :     return m_dfOffset;
    1216             : }
    1217             : 
    1218             : /************************************************************************/
    1219             : /*                             SetOffset()                              */
    1220             : /************************************************************************/
    1221           1 : CPLErr netCDFRasterBand::SetOffset(double dfNewOffset)
    1222             : {
    1223           2 :     CPLMutexHolderD(&hNCMutex);
    1224             : 
    1225             :     // Write value if in update mode.
    1226           1 :     if (poDS->GetAccess() == GA_Update)
    1227             :     {
    1228             :         // Make sure we are in define mode.
    1229           1 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1230             : 
    1231           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET,
    1232             :                                              NC_DOUBLE, 1, &dfNewOffset);
    1233             : 
    1234           1 :         NCDF_ERR(status);
    1235           1 :         if (status == NC_NOERR)
    1236             :         {
    1237           1 :             SetOffsetNoUpdate(dfNewOffset);
    1238           1 :             return CE_None;
    1239             :         }
    1240             : 
    1241           0 :         return CE_Failure;
    1242             :     }
    1243             : 
    1244           0 :     SetOffsetNoUpdate(dfNewOffset);
    1245           0 :     return CE_None;
    1246             : }
    1247             : 
    1248             : /************************************************************************/
    1249             : /*                         SetOffsetNoUpdate()                          */
    1250             : /************************************************************************/
    1251          17 : void netCDFRasterBand::SetOffsetNoUpdate(double dfVal)
    1252             : {
    1253          17 :     m_dfOffset = dfVal;
    1254          17 :     m_bHaveOffset = true;
    1255          17 : }
    1256             : 
    1257             : /************************************************************************/
    1258             : /*                              GetScale()                              */
    1259             : /************************************************************************/
    1260          49 : double netCDFRasterBand::GetScale(int *pbSuccess)
    1261             : {
    1262          49 :     if (pbSuccess != nullptr)
    1263          44 :         *pbSuccess = static_cast<int>(m_bHaveScale);
    1264             : 
    1265          49 :     return m_dfScale;
    1266             : }
    1267             : 
    1268             : /************************************************************************/
    1269             : /*                              SetScale()                              */
    1270             : /************************************************************************/
    1271           1 : CPLErr netCDFRasterBand::SetScale(double dfNewScale)
    1272             : {
    1273           2 :     CPLMutexHolderD(&hNCMutex);
    1274             : 
    1275             :     // Write value if in update mode.
    1276           1 :     if (poDS->GetAccess() == GA_Update)
    1277             :     {
    1278             :         // Make sure we are in define mode.
    1279           1 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1280             : 
    1281           1 :         const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR,
    1282             :                                              NC_DOUBLE, 1, &dfNewScale);
    1283             : 
    1284           1 :         NCDF_ERR(status);
    1285           1 :         if (status == NC_NOERR)
    1286             :         {
    1287           1 :             SetScaleNoUpdate(dfNewScale);
    1288           1 :             return CE_None;
    1289             :         }
    1290             : 
    1291           0 :         return CE_Failure;
    1292             :     }
    1293             : 
    1294           0 :     SetScaleNoUpdate(dfNewScale);
    1295           0 :     return CE_None;
    1296             : }
    1297             : 
    1298             : /************************************************************************/
    1299             : /*                         SetScaleNoUpdate()                           */
    1300             : /************************************************************************/
    1301          21 : void netCDFRasterBand::SetScaleNoUpdate(double dfVal)
    1302             : {
    1303          21 :     m_dfScale = dfVal;
    1304          21 :     m_bHaveScale = true;
    1305          21 : }
    1306             : 
    1307             : /************************************************************************/
    1308             : /*                            GetUnitType()                             */
    1309             : /************************************************************************/
    1310             : 
    1311          21 : const char *netCDFRasterBand::GetUnitType()
    1312             : 
    1313             : {
    1314          21 :     if (!m_osUnitType.empty())
    1315           6 :         return m_osUnitType;
    1316             : 
    1317          15 :     return GDALRasterBand::GetUnitType();
    1318             : }
    1319             : 
    1320             : /************************************************************************/
    1321             : /*                           SetUnitType()                              */
    1322             : /************************************************************************/
    1323             : 
    1324           1 : CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue)
    1325             : 
    1326             : {
    1327           2 :     CPLMutexHolderD(&hNCMutex);
    1328             : 
    1329           2 :     const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1330             : 
    1331           1 :     if (!osUnitType.empty())
    1332             :     {
    1333             :         // Write value if in update mode.
    1334           1 :         if (poDS->GetAccess() == GA_Update)
    1335             :         {
    1336             :             // Make sure we are in define mode.
    1337           1 :             static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE);
    1338             : 
    1339           1 :             const int status = nc_put_att_text(
    1340             :                 cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str());
    1341             : 
    1342           1 :             NCDF_ERR(status);
    1343           1 :             if (status == NC_NOERR)
    1344             :             {
    1345           1 :                 SetUnitTypeNoUpdate(pszNewValue);
    1346           1 :                 return CE_None;
    1347             :             }
    1348             : 
    1349           0 :             return CE_Failure;
    1350             :         }
    1351             :     }
    1352             : 
    1353           0 :     SetUnitTypeNoUpdate(pszNewValue);
    1354             : 
    1355           0 :     return CE_None;
    1356             : }
    1357             : 
    1358             : /************************************************************************/
    1359             : /*                       SetUnitTypeNoUpdate()                          */
    1360             : /************************************************************************/
    1361             : 
    1362         470 : void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue)
    1363             : {
    1364         470 :     m_osUnitType = (pszNewValue != nullptr ? pszNewValue : "");
    1365         470 : }
    1366             : 
    1367             : /************************************************************************/
    1368             : /*                           GetNoDataValue()                           */
    1369             : /************************************************************************/
    1370             : 
    1371         153 : double netCDFRasterBand::GetNoDataValue(int *pbSuccess)
    1372             : 
    1373             : {
    1374         153 :     if (m_bNoDataSetAsInt64)
    1375             :     {
    1376           0 :         if (pbSuccess)
    1377           0 :             *pbSuccess = TRUE;
    1378           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64);
    1379             :     }
    1380             : 
    1381         153 :     if (m_bNoDataSetAsUInt64)
    1382             :     {
    1383           0 :         if (pbSuccess)
    1384           0 :             *pbSuccess = TRUE;
    1385           0 :         return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64);
    1386             :     }
    1387             : 
    1388         153 :     if (m_bNoDataSet)
    1389             :     {
    1390         118 :         if (pbSuccess)
    1391         102 :             *pbSuccess = TRUE;
    1392         118 :         return m_dfNoDataValue;
    1393             :     }
    1394             : 
    1395          35 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
    1396             : }
    1397             : 
    1398             : /************************************************************************/
    1399             : /*                        GetNoDataValueAsInt64()                       */
    1400             : /************************************************************************/
    1401             : 
    1402           4 : int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
    1403             : 
    1404             : {
    1405           4 :     if (m_bNoDataSetAsInt64)
    1406             :     {
    1407           4 :         if (pbSuccess)
    1408           4 :             *pbSuccess = TRUE;
    1409             : 
    1410           4 :         return m_nNodataValueInt64;
    1411             :     }
    1412             : 
    1413           0 :     return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess);
    1414             : }
    1415             : 
    1416             : /************************************************************************/
    1417             : /*                        GetNoDataValueAsUInt64()                      */
    1418             : /************************************************************************/
    1419             : 
    1420           4 : uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
    1421             : 
    1422             : {
    1423           4 :     if (m_bNoDataSetAsUInt64)
    1424             :     {
    1425           4 :         if (pbSuccess)
    1426           4 :             *pbSuccess = TRUE;
    1427             : 
    1428           4 :         return m_nNodataValueUInt64;
    1429             :     }
    1430             : 
    1431           0 :     return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess);
    1432             : }
    1433             : 
    1434             : /************************************************************************/
    1435             : /*                           SetNoDataValue()                           */
    1436             : /************************************************************************/
    1437             : 
    1438         134 : CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData)
    1439             : 
    1440             : {
    1441         268 :     CPLMutexHolderD(&hNCMutex);
    1442             : 
    1443             :     // If already set to new value, don't do anything.
    1444         134 :     if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue))
    1445          19 :         return CE_None;
    1446             : 
    1447             :     // Write value if in update mode.
    1448         115 :     if (poDS->GetAccess() == GA_Update)
    1449             :     {
    1450             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1451             :         // but it is ok if variable has not been written to, so only print
    1452             :         // debug. See bug #4484.
    1453         125 :         if (m_bNoDataSet &&
    1454          10 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1455             :         {
    1456           0 :             CPLDebug("GDAL_netCDF",
    1457             :                      "Setting NoDataValue to %.17g (previously set to %.17g) "
    1458             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1459             :                      dfNoData, m_dfNoDataValue, cdfid, nBand);
    1460             :         }
    1461             : #ifdef NCDF_DEBUG
    1462             :         else
    1463             :         {
    1464             :             CPLDebug("GDAL_netCDF",
    1465             :                      "Setting NoDataValue to %.17g (id #%d, band #%d)",
    1466             :                      dfNoData, cdfid, nBand);
    1467             :         }
    1468             : #endif
    1469             :         // Make sure we are in define mode.
    1470         115 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1471             : 
    1472             :         int status;
    1473         115 :         if (eDataType == GDT_Byte)
    1474             :         {
    1475           6 :             if (bSignedData)
    1476             :             {
    1477           0 :                 signed char cNoDataValue = static_cast<signed char>(dfNoData);
    1478           0 :                 status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue,
    1479             :                                           nc_datatype, 1, &cNoDataValue);
    1480             :             }
    1481             :             else
    1482             :             {
    1483           6 :                 const unsigned char ucNoDataValue =
    1484           6 :                     static_cast<unsigned char>(dfNoData);
    1485           6 :                 status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue,
    1486             :                                           nc_datatype, 1, &ucNoDataValue);
    1487             :             }
    1488             :         }
    1489         109 :         else if (eDataType == GDT_Int16)
    1490             :         {
    1491          14 :             short nsNoDataValue = static_cast<short>(dfNoData);
    1492          14 :             status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1493             :                                       1, &nsNoDataValue);
    1494             :         }
    1495          95 :         else if (eDataType == GDT_Int32)
    1496             :         {
    1497          27 :             int nNoDataValue = static_cast<int>(dfNoData);
    1498          27 :             status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1,
    1499             :                                     &nNoDataValue);
    1500             :         }
    1501          68 :         else if (eDataType == GDT_Float32)
    1502             :         {
    1503          31 :             float fNoDataValue = static_cast<float>(dfNoData);
    1504          31 :             status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1505             :                                       1, &fNoDataValue);
    1506             :         }
    1507          37 :         else if (eDataType == GDT_UInt16 &&
    1508           6 :                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
    1509             :                      NCDF_FORMAT_NC4)
    1510             :         {
    1511           6 :             unsigned short usNoDataValue =
    1512           6 :                 static_cast<unsigned short>(dfNoData);
    1513           6 :             status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1514           6 :                                        1, &usNoDataValue);
    1515             :         }
    1516          31 :         else if (eDataType == GDT_UInt32 &&
    1517           7 :                  reinterpret_cast<netCDFDataset *>(poDS)->eFormat ==
    1518             :                      NCDF_FORMAT_NC4)
    1519             :         {
    1520           7 :             unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData);
    1521           7 :             status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1522           7 :                                      1, &unNoDataValue);
    1523             :         }
    1524             :         else
    1525             :         {
    1526          24 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1527             :                                        1, &dfNoData);
    1528             :         }
    1529             : 
    1530         115 :         NCDF_ERR(status);
    1531             : 
    1532             :         // Update status if write worked.
    1533         115 :         if (status == NC_NOERR)
    1534             :         {
    1535         115 :             SetNoDataValueNoUpdate(dfNoData);
    1536         115 :             return CE_None;
    1537             :         }
    1538             : 
    1539           0 :         return CE_Failure;
    1540             :     }
    1541             : 
    1542           0 :     SetNoDataValueNoUpdate(dfNoData);
    1543           0 :     return CE_None;
    1544             : }
    1545             : 
    1546             : /************************************************************************/
    1547             : /*                       SetNoDataValueNoUpdate()                       */
    1548             : /************************************************************************/
    1549             : 
    1550         430 : void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData)
    1551             : {
    1552         430 :     m_dfNoDataValue = dfNoData;
    1553         430 :     m_bNoDataSet = true;
    1554         430 :     m_bNoDataSetAsInt64 = false;
    1555         430 :     m_bNoDataSetAsUInt64 = false;
    1556         430 : }
    1557             : 
    1558             : /************************************************************************/
    1559             : /*                        SetNoDataValueAsInt64()                       */
    1560             : /************************************************************************/
    1561             : 
    1562           3 : CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData)
    1563             : 
    1564             : {
    1565           6 :     CPLMutexHolderD(&hNCMutex);
    1566             : 
    1567             :     // If already set to new value, don't do anything.
    1568           3 :     if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64)
    1569           0 :         return CE_None;
    1570             : 
    1571             :     // Write value if in update mode.
    1572           3 :     if (poDS->GetAccess() == GA_Update)
    1573             :     {
    1574             :         // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode,
    1575             :         // but it is ok if variable has not been written to, so only print
    1576             :         // debug. See bug #4484.
    1577           3 :         if (m_bNoDataSetAsInt64 &&
    1578           0 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1579             :         {
    1580           0 :             CPLDebug("GDAL_netCDF",
    1581             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1582             :                      " (previously set to " CPL_FRMT_GIB ") "
    1583             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1584             :                      static_cast<GIntBig>(nNoData),
    1585           0 :                      static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand);
    1586             :         }
    1587             : #ifdef NCDF_DEBUG
    1588             :         else
    1589             :         {
    1590             :             CPLDebug("GDAL_netCDF",
    1591             :                      "Setting NoDataValue to " CPL_FRMT_GIB
    1592             :                      " (id #%d, band #%d)",
    1593             :                      static_cast<GIntBig>(nNoData), cdfid, nBand);
    1594             :         }
    1595             : #endif
    1596             :         // Make sure we are in define mode.
    1597           3 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1598             : 
    1599             :         int status;
    1600           3 :         if (eDataType == GDT_Int64 &&
    1601           3 :             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1602             :         {
    1603           3 :             long long tmp = static_cast<long long>(nNoData);
    1604           3 :             status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue,
    1605           3 :                                          nc_datatype, 1, &tmp);
    1606             :         }
    1607             :         else
    1608             :         {
    1609           0 :             double dfNoData = static_cast<double>(nNoData);
    1610           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1611             :                                        1, &dfNoData);
    1612             :         }
    1613             : 
    1614           3 :         NCDF_ERR(status);
    1615             : 
    1616             :         // Update status if write worked.
    1617           3 :         if (status == NC_NOERR)
    1618             :         {
    1619           3 :             SetNoDataValueNoUpdate(nNoData);
    1620           3 :             return CE_None;
    1621             :         }
    1622             : 
    1623           0 :         return CE_Failure;
    1624             :     }
    1625             : 
    1626           0 :     SetNoDataValueNoUpdate(nNoData);
    1627           0 :     return CE_None;
    1628             : }
    1629             : 
    1630             : /************************************************************************/
    1631             : /*                       SetNoDataValueNoUpdate()                       */
    1632             : /************************************************************************/
    1633             : 
    1634          11 : void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData)
    1635             : {
    1636          11 :     m_nNodataValueInt64 = nNoData;
    1637          11 :     m_bNoDataSet = false;
    1638          11 :     m_bNoDataSetAsInt64 = true;
    1639          11 :     m_bNoDataSetAsUInt64 = false;
    1640          11 : }
    1641             : 
    1642             : /************************************************************************/
    1643             : /*                        SetNoDataValueAsUInt64()                      */
    1644             : /************************************************************************/
    1645             : 
    1646           3 : CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData)
    1647             : 
    1648             : {
    1649           6 :     CPLMutexHolderD(&hNCMutex);
    1650             : 
    1651             :     // If already set to new value, don't do anything.
    1652           3 :     if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64)
    1653           0 :         return CE_None;
    1654             : 
    1655             :     // Write value if in update mode.
    1656           3 :     if (poDS->GetAccess() == GA_Update)
    1657             :     {
    1658             :         // netcdf-4 does not allow to set _FillValue after leaving define mode,
    1659             :         // but it is ok if variable has not been written to, so only print
    1660             :         // debug. See bug #4484.
    1661           3 :         if (m_bNoDataSetAsUInt64 &&
    1662           0 :             !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode())
    1663             :         {
    1664           0 :             CPLDebug("GDAL_netCDF",
    1665             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1666             :                      " (previously set to " CPL_FRMT_GUIB ") "
    1667             :                      "but file is no longer in define mode (id #%d, band #%d)",
    1668             :                      static_cast<GUIntBig>(nNoData),
    1669           0 :                      static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand);
    1670             :         }
    1671             : #ifdef NCDF_DEBUG
    1672             :         else
    1673             :         {
    1674             :             CPLDebug("GDAL_netCDF",
    1675             :                      "Setting NoDataValue to " CPL_FRMT_GUIB
    1676             :                      " (id #%d, band #%d)",
    1677             :                      static_cast<GUIntBig>(nNoData), cdfid, nBand);
    1678             :         }
    1679             : #endif
    1680             :         // Make sure we are in define mode.
    1681           3 :         reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1682             : 
    1683             :         int status;
    1684           3 :         if (eDataType == GDT_UInt64 &&
    1685           3 :             reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    1686             :         {
    1687           3 :             unsigned long long tmp = static_cast<long long>(nNoData);
    1688           3 :             status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue,
    1689           3 :                                           nc_datatype, 1, &tmp);
    1690             :         }
    1691             :         else
    1692             :         {
    1693           0 :             double dfNoData = static_cast<double>(nNoData);
    1694           0 :             status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype,
    1695             :                                        1, &dfNoData);
    1696             :         }
    1697             : 
    1698           3 :         NCDF_ERR(status);
    1699             : 
    1700             :         // Update status if write worked.
    1701           3 :         if (status == NC_NOERR)
    1702             :         {
    1703           3 :             SetNoDataValueNoUpdate(nNoData);
    1704           3 :             return CE_None;
    1705             :         }
    1706             : 
    1707           0 :         return CE_Failure;
    1708             :     }
    1709             : 
    1710           0 :     SetNoDataValueNoUpdate(nNoData);
    1711           0 :     return CE_None;
    1712             : }
    1713             : 
    1714             : /************************************************************************/
    1715             : /*                       SetNoDataValueNoUpdate()                       */
    1716             : /************************************************************************/
    1717             : 
    1718          10 : void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData)
    1719             : {
    1720          10 :     m_nNodataValueUInt64 = nNoData;
    1721          10 :     m_bNoDataSet = false;
    1722          10 :     m_bNoDataSetAsInt64 = false;
    1723          10 :     m_bNoDataSetAsUInt64 = true;
    1724          10 : }
    1725             : 
    1726             : /************************************************************************/
    1727             : /*                        DeleteNoDataValue()                           */
    1728             : /************************************************************************/
    1729             : 
    1730             : #ifdef notdef
    1731             : CPLErr netCDFRasterBand::DeleteNoDataValue()
    1732             : 
    1733             : {
    1734             :     CPLMutexHolderD(&hNCMutex);
    1735             : 
    1736             :     if (!bNoDataSet)
    1737             :         return CE_None;
    1738             : 
    1739             :     // Write value if in update mode.
    1740             :     if (poDS->GetAccess() == GA_Update)
    1741             :     {
    1742             :         // Make sure we are in define mode.
    1743             :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(true);
    1744             : 
    1745             :         status = nc_del_att(cdfid, nZId, NCDF_FillValue);
    1746             : 
    1747             :         NCDF_ERR(status);
    1748             : 
    1749             :         // Update status if write worked.
    1750             :         if (status == NC_NOERR)
    1751             :         {
    1752             :             dfNoDataValue = 0.0;
    1753             :             bNoDataSet = false;
    1754             :             return CE_None;
    1755             :         }
    1756             : 
    1757             :         return CE_Failure;
    1758             :     }
    1759             : 
    1760             :     dfNoDataValue = 0.0;
    1761             :     bNoDataSet = false;
    1762             :     return CE_None;
    1763             : }
    1764             : #endif
    1765             : 
    1766             : /************************************************************************/
    1767             : /*                           SerializeToXML()                           */
    1768             : /************************************************************************/
    1769             : 
    1770           5 : CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */)
    1771             : {
    1772             :     // Overridden from GDALPamDataset to add only band histogram
    1773             :     // and statistics. See bug #4244.
    1774           5 :     if (psPam == nullptr)
    1775           0 :         return nullptr;
    1776             : 
    1777             :     // Setup root node and attributes.
    1778             :     CPLXMLNode *psTree =
    1779           5 :         CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand");
    1780             : 
    1781           5 :     if (GetBand() > 0)
    1782             :     {
    1783          10 :         CPLString oFmt;
    1784           5 :         CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand()));
    1785             :     }
    1786             : 
    1787             :     // Histograms.
    1788           5 :     if (psPam->psSavedHistograms != nullptr)
    1789           1 :         CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms));
    1790             : 
    1791             :     // Metadata (statistics only).
    1792           5 :     GDALMultiDomainMetadata oMDMDStats;
    1793           5 :     const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM",
    1794             :                                   "STATISTICS_MEAN", "STATISTICS_STDDEV",
    1795             :                                   nullptr};
    1796          25 :     for (int i = 0; i < CSLCount(papszMDStats); i++)
    1797             :     {
    1798          20 :         const char *pszMDI = GetMetadataItem(papszMDStats[i]);
    1799          20 :         if (pszMDI)
    1800           4 :             oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI);
    1801             :     }
    1802           5 :     CPLXMLNode *psMD = oMDMDStats.Serialize();
    1803             : 
    1804           5 :     if (psMD != nullptr)
    1805             :     {
    1806           1 :         if (psMD->psChild == nullptr)
    1807           0 :             CPLDestroyXMLNode(psMD);
    1808             :         else
    1809           1 :             CPLAddXMLChild(psTree, psMD);
    1810             :     }
    1811             : 
    1812             :     // We don't want to return anything if we had no metadata to attach.
    1813           5 :     if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr)
    1814             :     {
    1815           3 :         CPLDestroyXMLNode(psTree);
    1816           3 :         psTree = nullptr;
    1817             :     }
    1818             : 
    1819           5 :     return psTree;
    1820             : }
    1821             : 
    1822             : /************************************************************************/
    1823             : /*               Get1DVariableIndexedByDimension()                      */
    1824             : /************************************************************************/
    1825             : 
    1826          79 : static int Get1DVariableIndexedByDimension(int cdfid, int nDimId,
    1827             :                                            const char *pszDimName,
    1828             :                                            bool bVerboseError, int *pnGroupID)
    1829             : {
    1830          79 :     *pnGroupID = -1;
    1831          79 :     int nVarID = -1;
    1832             :     // First try to find a variable whose name is identical to the dimension
    1833             :     // name, and check that it is indeed indexed by this dimension
    1834          79 :     if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None)
    1835             :     {
    1836          65 :         int nDimCountOfVariable = 0;
    1837          65 :         nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable);
    1838          65 :         if (nDimCountOfVariable == 1)
    1839             :         {
    1840          65 :             int nDimIdOfVariable = -1;
    1841          65 :             nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable);
    1842          65 :             if (nDimIdOfVariable == nDimId)
    1843             :             {
    1844          65 :                 return nVarID;
    1845             :             }
    1846             :         }
    1847             :     }
    1848             : 
    1849             :     // Otherwise iterate over the variables to find potential candidates
    1850             :     // TODO: should be modified to search also in other groups using the same
    1851             :     //       logic than in NCDFResolveVar(), but maybe not needed if it's a
    1852             :     //       very rare case? and I think this is not CF compliant.
    1853          14 :     int nvars = 0;
    1854          14 :     CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr));
    1855             : 
    1856          14 :     int nCountCandidateVars = 0;
    1857          14 :     int nCandidateVarID = -1;
    1858          65 :     for (int k = 0; k < nvars; k++)
    1859             :     {
    1860          51 :         int nDimCountOfVariable = 0;
    1861          51 :         nc_inq_varndims(cdfid, k, &nDimCountOfVariable);
    1862          51 :         if (nDimCountOfVariable == 1)
    1863             :         {
    1864          27 :             int nDimIdOfVariable = -1;
    1865          27 :             nc_inq_vardimid(cdfid, k, &nDimIdOfVariable);
    1866          27 :             if (nDimIdOfVariable == nDimId)
    1867             :             {
    1868           7 :                 nCountCandidateVars++;
    1869           7 :                 nCandidateVarID = k;
    1870             :             }
    1871             :         }
    1872             :     }
    1873          14 :     if (nCountCandidateVars > 1)
    1874             :     {
    1875           1 :         if (bVerboseError)
    1876             :         {
    1877           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1878             :                      "Several 1D variables are indexed by dimension %s",
    1879             :                      pszDimName);
    1880             :         }
    1881           1 :         *pnGroupID = -1;
    1882           1 :         return -1;
    1883             :     }
    1884          13 :     else if (nCandidateVarID < 0)
    1885             :     {
    1886           8 :         if (bVerboseError)
    1887             :         {
    1888           8 :             CPLError(CE_Warning, CPLE_AppDefined,
    1889             :                      "No 1D variable is indexed by dimension %s", pszDimName);
    1890             :         }
    1891             :     }
    1892          13 :     *pnGroupID = cdfid;
    1893          13 :     return nCandidateVarID;
    1894             : }
    1895             : 
    1896             : /************************************************************************/
    1897             : /*                      CreateMetadataFromAttributes()                  */
    1898             : /************************************************************************/
    1899             : 
    1900         469 : void netCDFRasterBand::CreateMetadataFromAttributes()
    1901             : {
    1902         469 :     char szVarName[NC_MAX_NAME + 1] = {};
    1903         469 :     int status = nc_inq_varname(cdfid, nZId, szVarName);
    1904         469 :     NCDF_ERR(status);
    1905             : 
    1906         469 :     GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName);
    1907             : 
    1908             :     // Get attribute metadata.
    1909         469 :     int nAtt = 0;
    1910         469 :     NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt));
    1911             : 
    1912        1977 :     for (int i = 0; i < nAtt; i++)
    1913             :     {
    1914        1508 :         char szMetaName[NC_MAX_NAME + 1] = {};
    1915        1508 :         status = nc_inq_attname(cdfid, nZId, i, szMetaName);
    1916        1508 :         if (status != NC_NOERR)
    1917          12 :             continue;
    1918             : 
    1919        1508 :         if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr)
    1920             :         {
    1921          12 :             continue;
    1922             :         }
    1923             : 
    1924        1496 :         char *pszMetaValue = nullptr;
    1925        1496 :         if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None)
    1926             :         {
    1927        1496 :             GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue);
    1928             :         }
    1929             :         else
    1930             :         {
    1931           0 :             CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName);
    1932             :         }
    1933             : 
    1934        1496 :         if (pszMetaValue)
    1935             :         {
    1936        1496 :             CPLFree(pszMetaValue);
    1937        1496 :             pszMetaValue = nullptr;
    1938             :         }
    1939             :     }
    1940         469 : }
    1941             : 
    1942             : /************************************************************************/
    1943             : /*                      CreateMetadataFromOtherVars()                   */
    1944             : /************************************************************************/
    1945             : 
    1946          48 : void netCDFRasterBand::CreateMetadataFromOtherVars()
    1947             : 
    1948             : {
    1949          48 :     CPLAssert(!m_bCreateMetadataFromOtherVarsDone);
    1950          48 :     m_bCreateMetadataFromOtherVarsDone = true;
    1951             : 
    1952          48 :     netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS);
    1953          48 :     const int nPamFlagsBackup = l_poDS->nPamFlags;
    1954             : 
    1955             :     // Compute all dimensions from Band number and save in Metadata.
    1956          48 :     int nd = 0;
    1957          48 :     nc_inq_varndims(cdfid, nZId, &nd);
    1958             :     // Compute multidimention band position.
    1959             :     //
    1960             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    1961             :     // if Data[2,3,4,x,y]
    1962             :     //
    1963             :     //  BandPos0 = (nBand) / (3*4)
    1964             :     //  BandPos1 = (nBand - BandPos0*(3*4)) / (4)
    1965             :     //  BandPos2 = (nBand - BandPos0*(3*4)) % (4)
    1966             : 
    1967          48 :     int Sum = 1;
    1968          48 :     if (nd == 3)
    1969             :     {
    1970           5 :         Sum *= panBandZLev[0];
    1971             :     }
    1972             : 
    1973             :     // Loop over non-spatial dimensions.
    1974          48 :     int Taken = 0;
    1975             : 
    1976          88 :     for (int i = 0; i < nd - 2; i++)
    1977             :     {
    1978             :         int result;
    1979          40 :         if (i != nd - 2 - 1)
    1980             :         {
    1981          18 :             Sum = 1;
    1982          37 :             for (int j = i + 1; j < nd - 2; j++)
    1983             :             {
    1984          19 :                 Sum *= panBandZLev[j];
    1985             :             }
    1986          18 :             result = static_cast<int>((nLevel - Taken) / Sum);
    1987             :         }
    1988             :         else
    1989             :         {
    1990          22 :             result = static_cast<int>((nLevel - Taken) % Sum);
    1991             :         }
    1992             : 
    1993          40 :         char szName[NC_MAX_NAME + 1] = {};
    1994          40 :         snprintf(szName, sizeof(szName), "%s",
    1995          40 :                  l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]);
    1996             : 
    1997             :         char szMetaName[NC_MAX_NAME + 1 + 32];
    1998          40 :         snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName);
    1999             : 
    2000          40 :         const int nGroupID = l_poDS->m_anExtraDimGroupIds[i];
    2001          40 :         const int nVarID = l_poDS->m_anExtraDimVarIds[i];
    2002          40 :         if (nVarID < 0)
    2003             :         {
    2004           2 :             GDALPamRasterBand::SetMetadataItem(szMetaName,
    2005             :                                                CPLSPrintf("%d", result + 1));
    2006             :         }
    2007             :         else
    2008             :         {
    2009             :             // TODO: Make sure all the status checks make sense.
    2010             : 
    2011          38 :             nc_type nVarType = NC_NAT;
    2012          38 :             /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType);
    2013             : 
    2014          38 :             int nDims = 0;
    2015          38 :             /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims);
    2016             : 
    2017          38 :             char szMetaTemp[256] = {};
    2018          38 :             if (nDims == 1)
    2019             :             {
    2020          38 :                 size_t count[1] = {1};
    2021          38 :                 size_t start[1] = {static_cast<size_t>(result)};
    2022             : 
    2023          38 :                 switch (nVarType)
    2024             :                 {
    2025           0 :                     case NC_BYTE:
    2026             :                         // TODO: Check for signed/unsigned byte.
    2027             :                         signed char cData;
    2028           0 :                         /* status = */ nc_get_vara_schar(nGroupID, nVarID,
    2029             :                                                          start, count, &cData);
    2030           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData);
    2031           0 :                         break;
    2032           0 :                     case NC_SHORT:
    2033             :                         short sData;
    2034           0 :                         /* status = */ nc_get_vara_short(nGroupID, nVarID,
    2035             :                                                          start, count, &sData);
    2036           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData);
    2037           0 :                         break;
    2038          19 :                     case NC_INT:
    2039             :                     {
    2040             :                         int nData;
    2041          19 :                         /* status = */ nc_get_vara_int(nGroupID, nVarID, start,
    2042             :                                                        count, &nData);
    2043          19 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData);
    2044          19 :                         break;
    2045             :                     }
    2046           0 :                     case NC_FLOAT:
    2047             :                         float fData;
    2048           0 :                         /* status = */ nc_get_vara_float(nGroupID, nVarID,
    2049             :                                                          start, count, &fData);
    2050           0 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g",
    2051             :                                     fData);
    2052           0 :                         break;
    2053          18 :                     case NC_DOUBLE:
    2054             :                         double dfData;
    2055          18 :                         /* status = */ nc_get_vara_double(
    2056             :                             nGroupID, nVarID, start, count, &dfData);
    2057          18 :                         CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g",
    2058             :                                     dfData);
    2059          18 :                         break;
    2060           0 :                     case NC_UBYTE:
    2061             :                         unsigned char ucData;
    2062           0 :                         /* status = */ nc_get_vara_uchar(nGroupID, nVarID,
    2063             :                                                          start, count, &ucData);
    2064           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData);
    2065           0 :                         break;
    2066           0 :                     case NC_USHORT:
    2067             :                         unsigned short usData;
    2068           0 :                         /* status = */ nc_get_vara_ushort(
    2069             :                             nGroupID, nVarID, start, count, &usData);
    2070           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData);
    2071           0 :                         break;
    2072           0 :                     case NC_UINT:
    2073             :                     {
    2074             :                         unsigned int unData;
    2075           0 :                         /* status = */ nc_get_vara_uint(nGroupID, nVarID, start,
    2076             :                                                         count, &unData);
    2077           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData);
    2078           0 :                         break;
    2079             :                     }
    2080           1 :                     case NC_INT64:
    2081             :                     {
    2082             :                         long long nData;
    2083           1 :                         /* status = */ nc_get_vara_longlong(
    2084             :                             nGroupID, nVarID, start, count, &nData);
    2085           1 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB,
    2086             :                                  nData);
    2087           1 :                         break;
    2088             :                     }
    2089           0 :                     case NC_UINT64:
    2090             :                     {
    2091             :                         unsigned long long unData;
    2092           0 :                         /* status = */ nc_get_vara_ulonglong(
    2093             :                             nGroupID, nVarID, start, count, &unData);
    2094           0 :                         snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB,
    2095             :                                  unData);
    2096           0 :                         break;
    2097             :                     }
    2098           0 :                     default:
    2099           0 :                         CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d",
    2100             :                                  szMetaTemp, nVarType);
    2101           0 :                         break;
    2102             :                 }
    2103             :             }
    2104             :             else
    2105             :             {
    2106           0 :                 snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1);
    2107             :             }
    2108             : 
    2109             :             // Save dimension value.
    2110             :             // NOTE: removed #original_units as not part of CF-1.
    2111             : 
    2112          38 :             GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp);
    2113             :         }
    2114             : 
    2115             :         // Avoid int32 overflow. Perhaps something more sensible to do here ?
    2116          40 :         if (result > 0 && Sum > INT_MAX / result)
    2117           0 :             break;
    2118          40 :         if (Taken > INT_MAX - result * Sum)
    2119           0 :             break;
    2120             : 
    2121          40 :         Taken += result * Sum;
    2122             :     }  // End loop non-spatial dimensions.
    2123             : 
    2124          48 :     l_poDS->nPamFlags = nPamFlagsBackup;
    2125          48 : }
    2126             : 
    2127             : /************************************************************************/
    2128             : /*                             CheckData()                              */
    2129             : /************************************************************************/
    2130             : template <class T>
    2131        5731 : void netCDFRasterBand::CheckData(void *pImage, void *pImageNC,
    2132             :                                  size_t nTmpBlockXSize, size_t nTmpBlockYSize,
    2133             :                                  bool bCheckIsNan)
    2134             : {
    2135        5731 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2136             : 
    2137             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2138             :     // the data this is because partial blocks are not arranged the same way in
    2139             :     // netcdf and gdal.
    2140        5731 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2141             :     {
    2142           6 :         T *ptrWrite = static_cast<T *>(pImage);
    2143           6 :         T *ptrRead = static_cast<T *>(pImageNC);
    2144          29 :         for (size_t j = 0; j < nTmpBlockYSize;
    2145          23 :              j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize)
    2146             :         {
    2147          23 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T));
    2148             :         }
    2149             :     }
    2150             : 
    2151             :     // Is valid data checking needed or requested?
    2152        5731 :     if (bValidRangeValid || bCheckIsNan)
    2153             :     {
    2154        1265 :         T *ptrImage = static_cast<T *>(pImage);
    2155        2584 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2156             :         {
    2157             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2158        1319 :             size_t k = j * nBlockXSize;
    2159       96938 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2160             :             {
    2161             :                 // Check for nodata and nan.
    2162       95619 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2163        6301 :                     continue;
    2164       89318 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2165             :                 {
    2166        5737 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2167        5737 :                     continue;
    2168             :                 }
    2169             :                 // Check for valid_range.
    2170       83581 :                 if (bValidRangeValid)
    2171             :                 {
    2172       40986 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2173       40986 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2174       40983 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2175       40983 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2176             :                     {
    2177           4 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2178             :                     }
    2179             :                 }
    2180             :             }
    2181             :         }
    2182             :     }
    2183             : 
    2184             :     // If minimum longitude is > 180, subtract 360 from all.
    2185             :     // If not, disable checking for further calls (check just once).
    2186             :     // Only check first and last block elements since lon must be monotonic.
    2187        5731 :     const bool bIsSigned = std::numeric_limits<T>::is_signed;
    2188        5419 :     if (bCheckLongitude && bIsSigned &&
    2189           9 :         !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) &&
    2190           8 :         !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1],
    2191        2714 :                     m_dfNoDataValue) &&
    2192           8 :         std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0)
    2193             :     {
    2194           0 :         T *ptrImage = static_cast<T *>(pImage);
    2195           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2196             :         {
    2197           0 :             size_t k = j * nBlockXSize;
    2198           0 :             for (size_t i = 0; i < nTmpBlockXSize; i++, k++)
    2199             :             {
    2200           0 :                 if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2201           0 :                     ptrImage[k] = static_cast<T>(ptrImage[k] - 360);
    2202             :             }
    2203             :         }
    2204             :     }
    2205             :     else
    2206             :     {
    2207        5731 :         bCheckLongitude = false;
    2208             :     }
    2209        5731 : }
    2210             : 
    2211             : /************************************************************************/
    2212             : /*                             CheckDataCpx()                              */
    2213             : /************************************************************************/
    2214             : template <class T>
    2215          25 : void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC,
    2216             :                                     size_t nTmpBlockXSize,
    2217             :                                     size_t nTmpBlockYSize, bool bCheckIsNan)
    2218             : {
    2219          25 :     CPLAssert(pImage != nullptr && pImageNC != nullptr);
    2220             : 
    2221             :     // If this block is not a full block (in the x axis), we need to re-arrange
    2222             :     // the data this is because partial blocks are not arranged the same way in
    2223             :     // netcdf and gdal.
    2224          25 :     if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize))
    2225             :     {
    2226           0 :         T *ptrWrite = static_cast<T *>(pImage);
    2227           0 :         T *ptrRead = static_cast<T *>(pImageNC);
    2228           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++,
    2229           0 :                     ptrWrite += (2 * nBlockXSize),
    2230           0 :                     ptrRead += (2 * nTmpBlockXSize))
    2231             :         {
    2232           0 :             memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2);
    2233             :         }
    2234             :     }
    2235             : 
    2236             :     // Is valid data checking needed or requested?
    2237          25 :     if (bValidRangeValid || bCheckIsNan)
    2238             :     {
    2239           0 :         T *ptrImage = static_cast<T *>(pImage);
    2240           0 :         for (size_t j = 0; j < nTmpBlockYSize; j++)
    2241             :         {
    2242             :             // k moves along the gdal block, skipping the out-of-range pixels.
    2243           0 :             size_t k = 2 * j * nBlockXSize;
    2244           0 :             for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++)
    2245             :             {
    2246             :                 // Check for nodata and nan.
    2247           0 :                 if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue))
    2248           0 :                     continue;
    2249           0 :                 if (bCheckIsNan && std::isnan((double)ptrImage[k]))
    2250             :                 {
    2251           0 :                     ptrImage[k] = (T)m_dfNoDataValue;
    2252           0 :                     continue;
    2253             :                 }
    2254             :                 // Check for valid_range.
    2255           0 :                 if (bValidRangeValid)
    2256             :                 {
    2257           0 :                     if (((adfValidRange[0] != m_dfNoDataValue) &&
    2258           0 :                          (ptrImage[k] < (T)adfValidRange[0])) ||
    2259           0 :                         ((adfValidRange[1] != m_dfNoDataValue) &&
    2260           0 :                          (ptrImage[k] > (T)adfValidRange[1])))
    2261             :                     {
    2262           0 :                         ptrImage[k] = (T)m_dfNoDataValue;
    2263             :                     }
    2264             :                 }
    2265             :             }
    2266             :         }
    2267             :     }
    2268          25 : }
    2269             : 
    2270             : /************************************************************************/
    2271             : /*                         FetchNetcdfChunk()                           */
    2272             : /************************************************************************/
    2273             : 
    2274        5756 : bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart,
    2275             :                                         void *pImage)
    2276             : {
    2277        5756 :     size_t start[MAX_NC_DIMS] = {};
    2278        5756 :     size_t edge[MAX_NC_DIMS] = {};
    2279             : 
    2280        5756 :     start[nBandXPos] = xstart;
    2281        5756 :     edge[nBandXPos] = nBlockXSize;
    2282        5756 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2283           6 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2284        5756 :     if (nBandYPos >= 0)
    2285             :     {
    2286        5752 :         start[nBandYPos] = ystart;
    2287        5752 :         edge[nBandYPos] = nBlockYSize;
    2288        5752 :         if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2289           4 :             edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2290             :     }
    2291        5756 :     const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos];
    2292             : 
    2293             : #ifdef NCDF_DEBUG
    2294             :     CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d",
    2295             :              start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos],
    2296             :              edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp);
    2297             : #endif
    2298             : 
    2299        5756 :     int nd = 0;
    2300        5756 :     nc_inq_varndims(cdfid, nZId, &nd);
    2301        5756 :     if (nd == 3)
    2302             :     {
    2303        1078 :         start[panBandZPos[0]] = nLevel;  // z
    2304        1078 :         edge[panBandZPos[0]] = 1;
    2305             :     }
    2306             : 
    2307             :     // Compute multidimention band position.
    2308             :     //
    2309             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2310             :     // if Data[2,3,4,x,y]
    2311             :     //
    2312             :     //  BandPos0 = (nBand) / (3*4)
    2313             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2314             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2315        5756 :     if (nd > 3)
    2316             :     {
    2317         160 :         int Sum = -1;
    2318         160 :         int Taken = 0;
    2319         480 :         for (int i = 0; i < nd - 2; i++)
    2320             :         {
    2321         320 :             if (i != nd - 2 - 1)
    2322             :             {
    2323         160 :                 Sum = 1;
    2324         320 :                 for (int j = i + 1; j < nd - 2; j++)
    2325             :                 {
    2326         160 :                     Sum *= panBandZLev[j];
    2327             :                 }
    2328         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2329         160 :                 edge[panBandZPos[i]] = 1;
    2330             :             }
    2331             :             else
    2332             :             {
    2333         160 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2334         160 :                 edge[panBandZPos[i]] = 1;
    2335             :             }
    2336         320 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2337             :         }
    2338             :     }
    2339             : 
    2340             :     // Make sure we are in data mode.
    2341        5756 :     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2342             : 
    2343             :     // If this block is not a full block in the x axis, we need to
    2344             :     // re-arrange the data because partial blocks are not arranged the
    2345             :     // same way in netcdf and gdal, so we first we read the netcdf data at
    2346             :     // the end of the gdal block buffer then re-arrange rows in CheckData().
    2347        5756 :     void *pImageNC = pImage;
    2348        5756 :     if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize))
    2349             :     {
    2350           6 :         pImageNC = static_cast<GByte *>(pImage) +
    2351           6 :                    ((static_cast<size_t>(nBlockXSize) * nBlockYSize -
    2352          12 :                      edge[nBandXPos] * nYChunkSize) *
    2353           6 :                     (GDALGetDataTypeSize(eDataType) / 8));
    2354             :     }
    2355             : 
    2356             :     // Read data according to type.
    2357             :     int status;
    2358        5756 :     if (eDataType == GDT_Byte)
    2359             :     {
    2360        3004 :         if (bSignedData)
    2361             :         {
    2362           0 :             status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2363             :                                        static_cast<signed char *>(pImageNC));
    2364           0 :             if (status == NC_NOERR)
    2365           0 :                 CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2366             :                                        nYChunkSize, false);
    2367             :         }
    2368             :         else
    2369             :         {
    2370        3004 :             status = nc_get_vara_uchar(cdfid, nZId, start, edge,
    2371             :                                        static_cast<unsigned char *>(pImageNC));
    2372        3004 :             if (status == NC_NOERR)
    2373        3004 :                 CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos],
    2374             :                                          nYChunkSize, false);
    2375             :         }
    2376             :     }
    2377        2752 :     else if (eDataType == GDT_Int8)
    2378             :     {
    2379          60 :         status = nc_get_vara_schar(cdfid, nZId, start, edge,
    2380             :                                    static_cast<signed char *>(pImageNC));
    2381          60 :         if (status == NC_NOERR)
    2382          60 :             CheckData<signed char>(pImage, pImageNC, edge[nBandXPos],
    2383             :                                    nYChunkSize, false);
    2384             :     }
    2385        2692 :     else if (nc_datatype == NC_SHORT)
    2386             :     {
    2387         465 :         status = nc_get_vara_short(cdfid, nZId, start, edge,
    2388             :                                    static_cast<short *>(pImageNC));
    2389         465 :         if (status == NC_NOERR)
    2390             :         {
    2391         465 :             if (eDataType == GDT_Int16)
    2392             :             {
    2393         462 :                 CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos],
    2394             :                                   nYChunkSize, false);
    2395             :             }
    2396             :             else
    2397             :             {
    2398           3 :                 CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos],
    2399             :                                    nYChunkSize, false);
    2400             :             }
    2401             :         }
    2402             :     }
    2403        2227 :     else if (eDataType == GDT_Int32)
    2404             :     {
    2405             : #if SIZEOF_UNSIGNED_LONG == 4
    2406             :         status = nc_get_vara_long(cdfid, nZId, start, edge,
    2407             :                                   static_cast<long *>(pImageNC));
    2408             :         if (status == NC_NOERR)
    2409             :             CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2410             :                             false);
    2411             : #else
    2412         912 :         status = nc_get_vara_int(cdfid, nZId, start, edge,
    2413             :                                  static_cast<int *>(pImageNC));
    2414         912 :         if (status == NC_NOERR)
    2415         912 :             CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2416             :                            false);
    2417             : #endif
    2418             :     }
    2419        1315 :     else if (eDataType == GDT_Float32)
    2420             :     {
    2421        1178 :         status = nc_get_vara_float(cdfid, nZId, start, edge,
    2422             :                                    static_cast<float *>(pImageNC));
    2423        1178 :         if (status == NC_NOERR)
    2424        1178 :             CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2425             :                              true);
    2426             :     }
    2427         137 :     else if (eDataType == GDT_Float64)
    2428             :     {
    2429          86 :         status = nc_get_vara_double(cdfid, nZId, start, edge,
    2430             :                                     static_cast<double *>(pImageNC));
    2431          86 :         if (status == NC_NOERR)
    2432          86 :             CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2433             :                               true);
    2434             :     }
    2435          51 :     else if (eDataType == GDT_UInt16)
    2436             :     {
    2437           6 :         status = nc_get_vara_ushort(cdfid, nZId, start, edge,
    2438             :                                     static_cast<unsigned short *>(pImageNC));
    2439           6 :         if (status == NC_NOERR)
    2440           6 :             CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos],
    2441             :                                       nYChunkSize, false);
    2442             :     }
    2443          45 :     else if (eDataType == GDT_UInt32)
    2444             :     {
    2445           6 :         status = nc_get_vara_uint(cdfid, nZId, start, edge,
    2446             :                                   static_cast<unsigned int *>(pImageNC));
    2447           6 :         if (status == NC_NOERR)
    2448           6 :             CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos],
    2449             :                                     nYChunkSize, false);
    2450             :     }
    2451          39 :     else if (eDataType == GDT_Int64)
    2452             :     {
    2453           7 :         status = nc_get_vara_longlong(cdfid, nZId, start, edge,
    2454             :                                       static_cast<long long *>(pImageNC));
    2455           7 :         if (status == NC_NOERR)
    2456           7 :             CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos],
    2457             :                                     nYChunkSize, false);
    2458             :     }
    2459          32 :     else if (eDataType == GDT_UInt64)
    2460             :     {
    2461             :         status =
    2462           7 :             nc_get_vara_ulonglong(cdfid, nZId, start, edge,
    2463             :                                   static_cast<unsigned long long *>(pImageNC));
    2464           7 :         if (status == NC_NOERR)
    2465           7 :             CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos],
    2466             :                                      nYChunkSize, false);
    2467             :     }
    2468          25 :     else if (eDataType == GDT_CInt16)
    2469             :     {
    2470           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2471           0 :         if (status == NC_NOERR)
    2472           0 :             CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2473             :                                 false);
    2474             :     }
    2475          25 :     else if (eDataType == GDT_CInt32)
    2476             :     {
    2477           0 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2478           0 :         if (status == NC_NOERR)
    2479           0 :             CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2480             :                               false);
    2481             :     }
    2482          25 :     else if (eDataType == GDT_CFloat32)
    2483             :     {
    2484          20 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2485          20 :         if (status == NC_NOERR)
    2486          20 :             CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2487             :                                 false);
    2488             :     }
    2489           5 :     else if (eDataType == GDT_CFloat64)
    2490             :     {
    2491           5 :         status = nc_get_vara(cdfid, nZId, start, edge, pImageNC);
    2492           5 :         if (status == NC_NOERR)
    2493           5 :             CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize,
    2494             :                                  false);
    2495             :     }
    2496             : 
    2497             :     else
    2498           0 :         status = NC_EBADTYPE;
    2499             : 
    2500        5756 :     if (status != NC_NOERR)
    2501             :     {
    2502           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2503             :                  "netCDF chunk fetch failed: #%d (%s)", status,
    2504             :                  nc_strerror(status));
    2505           0 :         return false;
    2506             :     }
    2507        5756 :     return true;
    2508             : }
    2509             : 
    2510             : /************************************************************************/
    2511             : /*                             IReadBlock()                             */
    2512             : /************************************************************************/
    2513             : 
    2514        5756 : CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2515             :                                     void *pImage)
    2516             : 
    2517             : {
    2518       11512 :     CPLMutexHolderD(&hNCMutex);
    2519             : 
    2520             :     // Locate X, Y and Z position in the array.
    2521             : 
    2522        5756 :     size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2523        5756 :     size_t ystart = 0;
    2524             : 
    2525             :     // Check y order.
    2526        5756 :     if (nBandYPos >= 0)
    2527             :     {
    2528        5752 :         auto poGDS = static_cast<netCDFDataset *>(poDS);
    2529        5752 :         if (poGDS->bBottomUp)
    2530             :         {
    2531        4837 :             if (nBlockYSize == 1)
    2532             :             {
    2533        4824 :                 ystart = nRasterYSize - 1 - nBlockYOff;
    2534             :             }
    2535             :             else
    2536             :             {
    2537             :                 // in GDAL space
    2538          13 :                 ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2539             :                 const size_t yend =
    2540          26 :                     std::min(ystart + nBlockYSize - 1,
    2541          13 :                              static_cast<size_t>(nRasterYSize - 1));
    2542             :                 // in netCDF space
    2543          13 :                 const size_t nFirstChunkLine = nRasterYSize - 1 - yend;
    2544          13 :                 const size_t nLastChunkLine = nRasterYSize - 1 - ystart;
    2545          13 :                 const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize;
    2546          13 :                 const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize;
    2547             : 
    2548             :                 const auto firstKey = netCDFDataset::ChunkKey(
    2549          13 :                     nBlockXOff, nFirstChunkBlock, nBand);
    2550             :                 const auto secondKey =
    2551          13 :                     netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand);
    2552             : 
    2553             :                 // Retrieve data from the one or 2 needed netCDF chunks
    2554          13 :                 std::shared_ptr<std::vector<GByte>> firstChunk;
    2555          13 :                 std::shared_ptr<std::vector<GByte>> secondChunk;
    2556          13 :                 if (poGDS->poChunkCache)
    2557             :                 {
    2558          13 :                     poGDS->poChunkCache->tryGet(firstKey, firstChunk);
    2559          13 :                     if (firstKey != secondKey)
    2560           6 :                         poGDS->poChunkCache->tryGet(secondKey, secondChunk);
    2561             :                 }
    2562             :                 const size_t nChunkLineSize =
    2563          13 :                     static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
    2564          13 :                     nBlockXSize;
    2565          13 :                 const size_t nChunkSize = nChunkLineSize * nBlockYSize;
    2566          13 :                 if (!firstChunk)
    2567             :                 {
    2568          11 :                     firstChunk.reset(new std::vector<GByte>(nChunkSize));
    2569          11 :                     if (!FetchNetcdfChunk(xstart,
    2570          11 :                                           nFirstChunkBlock * nBlockYSize,
    2571          11 :                                           firstChunk.get()->data()))
    2572           0 :                         return CE_Failure;
    2573          11 :                     if (poGDS->poChunkCache)
    2574          11 :                         poGDS->poChunkCache->insert(firstKey, firstChunk);
    2575             :                 }
    2576          13 :                 if (!secondChunk && firstKey != secondKey)
    2577             :                 {
    2578           2 :                     secondChunk.reset(new std::vector<GByte>(nChunkSize));
    2579           2 :                     if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize,
    2580           2 :                                           secondChunk.get()->data()))
    2581           0 :                         return CE_Failure;
    2582           2 :                     if (poGDS->poChunkCache)
    2583           2 :                         poGDS->poChunkCache->insert(secondKey, secondChunk);
    2584             :                 }
    2585             : 
    2586             :                 // Assemble netCDF chunks into GDAL block
    2587          13 :                 GByte *pabyImage = static_cast<GByte *>(pImage);
    2588          13 :                 const size_t nFirstChunkBlockLine =
    2589          13 :                     nFirstChunkBlock * nBlockYSize;
    2590          13 :                 const size_t nLastChunkBlockLine =
    2591          13 :                     nLastChunkBlock * nBlockYSize;
    2592         146 :                 for (size_t iLine = ystart; iLine <= yend; iLine++)
    2593             :                 {
    2594         133 :                     const size_t nLineFromBottom = nRasterYSize - 1 - iLine;
    2595         133 :                     const size_t nChunkY = nLineFromBottom / nBlockYSize;
    2596         133 :                     if (nChunkY == nFirstChunkBlock)
    2597             :                     {
    2598         121 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2599         121 :                                firstChunk.get()->data() +
    2600         121 :                                    (nLineFromBottom - nFirstChunkBlockLine) *
    2601             :                                        nChunkLineSize,
    2602             :                                nChunkLineSize);
    2603             :                     }
    2604             :                     else
    2605             :                     {
    2606          12 :                         CPLAssert(nChunkY == nLastChunkBlock);
    2607          12 :                         assert(secondChunk);
    2608          12 :                         memcpy(pabyImage + nChunkLineSize * (iLine - ystart),
    2609          12 :                                secondChunk.get()->data() +
    2610          12 :                                    (nLineFromBottom - nLastChunkBlockLine) *
    2611             :                                        nChunkLineSize,
    2612             :                                nChunkLineSize);
    2613             :                     }
    2614             :                 }
    2615          13 :                 return CE_None;
    2616             :             }
    2617             :         }
    2618             :         else
    2619             :         {
    2620         915 :             ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize;
    2621             :         }
    2622             :     }
    2623             : 
    2624        5743 :     return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure;
    2625             : }
    2626             : 
    2627             : /************************************************************************/
    2628             : /*                             IWriteBlock()                            */
    2629             : /************************************************************************/
    2630             : 
    2631        2801 : CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
    2632             :                                      void *pImage)
    2633             : {
    2634        5602 :     CPLMutexHolderD(&hNCMutex);
    2635             : 
    2636             : #ifdef NCDF_DEBUG
    2637             :     if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1))
    2638             :         CPLDebug("GDAL_netCDF",
    2639             :                  "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d",
    2640             :                  nBlockXOff, nBlockYOff, nBand);
    2641             : #endif
    2642             : 
    2643        2801 :     int nd = 0;
    2644        2801 :     nc_inq_varndims(cdfid, nZId, &nd);
    2645             : 
    2646             :     // Locate X, Y and Z position in the array.
    2647             : 
    2648             :     size_t start[MAX_NC_DIMS];
    2649        2801 :     memset(start, 0, sizeof(start));
    2650        2801 :     start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize;
    2651             : 
    2652             :     // check y order.
    2653        2801 :     if (static_cast<netCDFDataset *>(poDS)->bBottomUp)
    2654             :     {
    2655        2777 :         if (nBlockYSize == 1)
    2656             :         {
    2657        2777 :             start[nBandYPos] = nRasterYSize - 1 - nBlockYOff;
    2658             :         }
    2659             :         else
    2660             :         {
    2661           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2662             :                      "nBlockYSize = %d, only 1 supported when "
    2663             :                      "writing bottom-up dataset",
    2664             :                      nBlockYSize);
    2665           0 :             return CE_Failure;
    2666             :         }
    2667             :     }
    2668             :     else
    2669             :     {
    2670          24 :         start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize;  // y
    2671             :     }
    2672             : 
    2673        2801 :     size_t edge[MAX_NC_DIMS] = {};
    2674             : 
    2675        2801 :     edge[nBandXPos] = nBlockXSize;
    2676        2801 :     if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize)
    2677           0 :         edge[nBandXPos] = nRasterXSize - start[nBandXPos];
    2678        2801 :     edge[nBandYPos] = nBlockYSize;
    2679        2801 :     if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize)
    2680           0 :         edge[nBandYPos] = nRasterYSize - start[nBandYPos];
    2681             : 
    2682        2801 :     if (nd == 3)
    2683             :     {
    2684         610 :         start[panBandZPos[0]] = nLevel;  // z
    2685         610 :         edge[panBandZPos[0]] = 1;
    2686             :     }
    2687             : 
    2688             :     // Compute multidimention band position.
    2689             :     //
    2690             :     // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels)
    2691             :     // if Data[2,3,4,x,y]
    2692             :     //
    2693             :     //  BandPos0 = (nBand) / (3*4)
    2694             :     //  BandPos1 = (nBand - (3*4)) / (4)
    2695             :     //  BandPos2 = (nBand - (3*4)) % (4)
    2696        2801 :     if (nd > 3)
    2697             :     {
    2698         178 :         int Sum = -1;
    2699         178 :         int Taken = 0;
    2700         534 :         for (int i = 0; i < nd - 2; i++)
    2701             :         {
    2702         356 :             if (i != nd - 2 - 1)
    2703             :             {
    2704         178 :                 Sum = 1;
    2705         356 :                 for (int j = i + 1; j < nd - 2; j++)
    2706             :                 {
    2707         178 :                     Sum *= panBandZLev[j];
    2708             :                 }
    2709         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum);
    2710         178 :                 edge[panBandZPos[i]] = 1;
    2711             :             }
    2712             :             else
    2713             :             {
    2714         178 :                 start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum);
    2715         178 :                 edge[panBandZPos[i]] = 1;
    2716             :             }
    2717         356 :             Taken += static_cast<int>(start[panBandZPos[i]]) * Sum;
    2718             :         }
    2719             :     }
    2720             : 
    2721             :     // Make sure we are in data mode.
    2722        2801 :     static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    2723             : 
    2724             :     // Copy data according to type.
    2725        2801 :     int status = 0;
    2726        2801 :     if (eDataType == GDT_Byte)
    2727             :     {
    2728        2242 :         if (bSignedData)
    2729           0 :             status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2730             :                                        static_cast<signed char *>(pImage));
    2731             :         else
    2732        2242 :             status = nc_put_vara_uchar(cdfid, nZId, start, edge,
    2733             :                                        static_cast<unsigned char *>(pImage));
    2734             :     }
    2735         559 :     else if (eDataType == GDT_Int8)
    2736             :     {
    2737          40 :         status = nc_put_vara_schar(cdfid, nZId, start, edge,
    2738             :                                    static_cast<signed char *>(pImage));
    2739             :     }
    2740         519 :     else if (nc_datatype == NC_SHORT)
    2741             :     {
    2742         101 :         status = nc_put_vara_short(cdfid, nZId, start, edge,
    2743             :                                    static_cast<short *>(pImage));
    2744             :     }
    2745         418 :     else if (eDataType == GDT_Int32)
    2746             :     {
    2747         210 :         status = nc_put_vara_int(cdfid, nZId, start, edge,
    2748             :                                  static_cast<int *>(pImage));
    2749             :     }
    2750         208 :     else if (eDataType == GDT_Float32)
    2751             :     {
    2752         128 :         status = nc_put_vara_float(cdfid, nZId, start, edge,
    2753             :                                    static_cast<float *>(pImage));
    2754             :     }
    2755          80 :     else if (eDataType == GDT_Float64)
    2756             :     {
    2757          50 :         status = nc_put_vara_double(cdfid, nZId, start, edge,
    2758             :                                     static_cast<double *>(pImage));
    2759             :     }
    2760          30 :     else if (eDataType == GDT_UInt16 &&
    2761          12 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2762             :     {
    2763          12 :         status = nc_put_vara_ushort(cdfid, nZId, start, edge,
    2764             :                                     static_cast<unsigned short *>(pImage));
    2765             :     }
    2766          18 :     else if (eDataType == GDT_UInt32 &&
    2767          12 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2768             :     {
    2769          12 :         status = nc_put_vara_uint(cdfid, nZId, start, edge,
    2770             :                                   static_cast<unsigned int *>(pImage));
    2771             :     }
    2772           6 :     else if (eDataType == GDT_UInt64 &&
    2773           3 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2774             :     {
    2775           3 :         status =
    2776           3 :             nc_put_vara_ulonglong(cdfid, nZId, start, edge,
    2777             :                                   static_cast<unsigned long long *>(pImage));
    2778             :     }
    2779           3 :     else if (eDataType == GDT_Int64 &&
    2780           3 :              static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4)
    2781             :     {
    2782           3 :         status = nc_put_vara_longlong(cdfid, nZId, start, edge,
    2783             :                                       static_cast<long long *>(pImage));
    2784             :     }
    2785             :     else
    2786             :     {
    2787           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2788             :                  "The NetCDF driver does not support GDAL data type %d",
    2789           0 :                  eDataType);
    2790           0 :         status = NC_EBADTYPE;
    2791             :     }
    2792        2801 :     NCDF_ERR(status);
    2793             : 
    2794        2801 :     if (status != NC_NOERR)
    2795             :     {
    2796           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2797             :                  "netCDF scanline write failed: %s", nc_strerror(status));
    2798           0 :         return CE_Failure;
    2799             :     }
    2800             : 
    2801        2801 :     return CE_None;
    2802             : }
    2803             : 
    2804             : /************************************************************************/
    2805             : /* ==================================================================== */
    2806             : /*                              netCDFDataset                           */
    2807             : /* ==================================================================== */
    2808             : /************************************************************************/
    2809             : 
    2810             : /************************************************************************/
    2811             : /*                           netCDFDataset()                            */
    2812             : /************************************************************************/
    2813             : 
    2814         999 : netCDFDataset::netCDFDataset()
    2815             :     :
    2816             : // Basic dataset vars.
    2817             : #ifdef ENABLE_NCDUMP
    2818             :       bFileToDestroyAtClosing(false),
    2819             : #endif
    2820             :       cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr),
    2821             :       papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE),
    2822             :       bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr),
    2823             :       pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false),
    2824         999 :       eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid),
    2825         999 :       GeometryScribe(vcdf, this->generateLogName()),
    2826         999 :       FieldScribe(vcdf, this->generateLogName()),
    2827        1998 :       bufManager(CPLGetUsablePhysicalRAM() / 5),
    2828             : 
    2829             :       // projection/GT.
    2830             :       nXDimID(-1), nYDimID(-1), bIsProjected(false),
    2831             :       bIsGeographic(false),  // Can be not projected, and also not geographic
    2832             :       // State vars.
    2833             :       bDefineMode(true), bAddedGridMappingRef(false),
    2834             : 
    2835             :       // Create vars.
    2836             :       papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE),
    2837             :       nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER),
    2838        2997 :       bSignedData(true)
    2839             : {
    2840         999 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2841             : 
    2842             :     // Projection/GT.
    2843         999 :     m_adfGeoTransform[0] = 0.0;
    2844         999 :     m_adfGeoTransform[1] = 1.0;
    2845         999 :     m_adfGeoTransform[2] = 0.0;
    2846         999 :     m_adfGeoTransform[3] = 0.0;
    2847         999 :     m_adfGeoTransform[4] = 0.0;
    2848         999 :     m_adfGeoTransform[5] = 1.0;
    2849             : 
    2850             :     // Set buffers
    2851         999 :     bufManager.addBuffer(&(GeometryScribe.getMemBuffer()));
    2852         999 :     bufManager.addBuffer(&(FieldScribe.getMemBuffer()));
    2853         999 : }
    2854             : 
    2855             : /************************************************************************/
    2856             : /*                           ~netCDFDataset()                           */
    2857             : /************************************************************************/
    2858             : 
    2859        1924 : netCDFDataset::~netCDFDataset()
    2860             : 
    2861             : {
    2862         999 :     netCDFDataset::Close();
    2863        1924 : }
    2864             : 
    2865             : /************************************************************************/
    2866             : /*                              Close()                                 */
    2867             : /************************************************************************/
    2868             : 
    2869        1741 : CPLErr netCDFDataset::Close()
    2870             : {
    2871        1741 :     CPLErr eErr = CE_None;
    2872        1741 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    2873             :     {
    2874        1998 :         CPLMutexHolderD(&hNCMutex);
    2875             : 
    2876             : #ifdef NCDF_DEBUG
    2877             :         CPLDebug("GDAL_netCDF",
    2878             :                  "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid,
    2879             :                  osFilename.c_str());
    2880             : #endif
    2881             : 
    2882             :         // Write data related to geotransform
    2883        1230 :         if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData &&
    2884         231 :             (m_bHasProjection || m_bHasGeoTransform))
    2885             :         {
    2886             :             // Ensure projection is written if GeoTransform OR Projection are
    2887             :             // missing.
    2888          37 :             if (!m_bAddedProjectionVarsDefs)
    2889             :             {
    2890           2 :                 AddProjectionVars(true, nullptr, nullptr);
    2891             :             }
    2892          37 :             AddProjectionVars(false, nullptr, nullptr);
    2893             :         }
    2894             : 
    2895         999 :         if (netCDFDataset::FlushCache(true) != CE_None)
    2896           0 :             eErr = CE_Failure;
    2897             : 
    2898         999 :         if (GetAccess() == GA_Update && !SGCommitPendingTransaction())
    2899           0 :             eErr = CE_Failure;
    2900             : 
    2901        1001 :         for (size_t i = 0; i < apoVectorDatasets.size(); i++)
    2902           2 :             delete apoVectorDatasets[i];
    2903             : 
    2904             :         // Make sure projection variable is written to band variable.
    2905         999 :         if (GetAccess() == GA_Update && !bAddedGridMappingRef)
    2906             :         {
    2907         247 :             if (!AddGridMappingRef())
    2908           0 :                 eErr = CE_Failure;
    2909             :         }
    2910             : 
    2911         999 :         CSLDestroy(papszMetadata);
    2912         999 :         CSLDestroy(papszSubDatasets);
    2913         999 :         CSLDestroy(papszCreationOptions);
    2914             : 
    2915         999 :         CPLFree(pszCFProjection);
    2916             : 
    2917         999 :         if (cdfid > 0)
    2918             :         {
    2919             : #ifdef NCDF_DEBUG
    2920             :             CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid);
    2921             : #endif
    2922         654 :             int status = GDAL_nc_close(cdfid);
    2923             : #ifdef ENABLE_UFFD
    2924         654 :             NETCDF_UFFD_UNMAP(pCtx);
    2925             : #endif
    2926         654 :             NCDF_ERR(status);
    2927         654 :             if (status != NC_NOERR)
    2928           0 :                 eErr = CE_Failure;
    2929             :         }
    2930             : 
    2931         999 :         if (fpVSIMEM)
    2932          15 :             VSIFCloseL(fpVSIMEM);
    2933             : 
    2934             : #ifdef ENABLE_NCDUMP
    2935         999 :         if (bFileToDestroyAtClosing)
    2936           0 :             VSIUnlink(osFilename);
    2937             : #endif
    2938             : 
    2939         999 :         if (GDALPamDataset::Close() != CE_None)
    2940           0 :             eErr = CE_Failure;
    2941             :     }
    2942        1741 :     return eErr;
    2943             : }
    2944             : 
    2945             : /************************************************************************/
    2946             : /*                            SetDefineMode()                           */
    2947             : /************************************************************************/
    2948       10618 : bool netCDFDataset::SetDefineMode(bool bNewDefineMode)
    2949             : {
    2950             :     // Do nothing if already in new define mode
    2951             :     // or if dataset is in read-only mode or if dataset is true NC4 dataset.
    2952       11174 :     if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly ||
    2953         556 :         eFormat == NCDF_FORMAT_NC4)
    2954       10209 :         return true;
    2955             : 
    2956         409 :     CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d",
    2957         409 :              static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode));
    2958             : 
    2959         409 :     bDefineMode = bNewDefineMode;
    2960             : 
    2961             :     int status;
    2962         409 :     if (bDefineMode)
    2963         142 :         status = nc_redef(cdfid);
    2964             :     else
    2965         267 :         status = nc_enddef(cdfid);
    2966             : 
    2967         409 :     NCDF_ERR(status);
    2968         409 :     return status == NC_NOERR;
    2969             : }
    2970             : 
    2971             : /************************************************************************/
    2972             : /*                      GetMetadataDomainList()                         */
    2973             : /************************************************************************/
    2974             : 
    2975          27 : char **netCDFDataset::GetMetadataDomainList()
    2976             : {
    2977          27 :     char **papszDomains = BuildMetadataDomainList(
    2978             :         GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr);
    2979          28 :     for (const auto &kv : m_oMapDomainToJSon)
    2980           1 :         papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str());
    2981          27 :     return papszDomains;
    2982             : }
    2983             : 
    2984             : /************************************************************************/
    2985             : /*                            GetMetadata()                             */
    2986             : /************************************************************************/
    2987         359 : char **netCDFDataset::GetMetadata(const char *pszDomain)
    2988             : {
    2989         359 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
    2990          37 :         return papszSubDatasets;
    2991             : 
    2992         322 :     if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:"))
    2993             :     {
    2994           1 :         auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:"));
    2995           1 :         if (iter != m_oMapDomainToJSon.end())
    2996           1 :             return iter->second.List();
    2997             :     }
    2998             : 
    2999         321 :     return GDALDataset::GetMetadata(pszDomain);
    3000             : }
    3001             : 
    3002             : /************************************************************************/
    3003             : /*                        SetMetadataItem()                             */
    3004             : /************************************************************************/
    3005             : 
    3006          43 : CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue,
    3007             :                                       const char *pszDomain)
    3008             : {
    3009          85 :     if (GetAccess() == GA_Update &&
    3010          85 :         (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr)
    3011             :     {
    3012          42 :         std::string osName(pszName);
    3013             : 
    3014             :         // Same logic as in CopyMetadata()
    3015          42 :         if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#"))
    3016           8 :             osName = osName.substr(strlen("NC_GLOBAL#"));
    3017          34 :         else if (strchr(osName.c_str(), '#') == nullptr)
    3018           5 :             osName = "GDAL_" + osName;
    3019             : 
    3020          84 :         if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") ||
    3021          42 :             strchr(osName.c_str(), '#') != nullptr)
    3022             :         {
    3023             :             // do nothing
    3024          29 :             return CE_None;
    3025             :         }
    3026             :         else
    3027             :         {
    3028          13 :             SetDefineMode(true);
    3029             : 
    3030          13 :             if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue))
    3031          13 :                 return CE_Failure;
    3032             :         }
    3033             :     }
    3034             : 
    3035           1 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    3036             : }
    3037             : 
    3038             : /************************************************************************/
    3039             : /*                          SetMetadata()                               */
    3040             : /************************************************************************/
    3041             : 
    3042           8 : CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain)
    3043             : {
    3044          13 :     if (GetAccess() == GA_Update &&
    3045           5 :         (pszDomain == nullptr || pszDomain[0] == '\0'))
    3046             :     {
    3047             :         // We don't handle metadata item removal for now
    3048          50 :         for (const char *const *papszIter = papszMD; papszIter && *papszIter;
    3049             :              ++papszIter)
    3050             :         {
    3051          42 :             char *pszName = nullptr;
    3052          42 :             const char *pszValue = CPLParseNameValue(*papszIter, &pszName);
    3053          42 :             if (pszName && pszValue)
    3054          42 :                 SetMetadataItem(pszName, pszValue);
    3055          42 :             CPLFree(pszName);
    3056             :         }
    3057           8 :         return CE_None;
    3058             :     }
    3059           0 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
    3060             : }
    3061             : 
    3062             : /************************************************************************/
    3063             : /*                          GetSpatialRef()                             */
    3064             : /************************************************************************/
    3065             : 
    3066         181 : const OGRSpatialReference *netCDFDataset::GetSpatialRef() const
    3067             : {
    3068         181 :     if (m_bHasProjection)
    3069          72 :         return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3070             : 
    3071         109 :     return GDALPamDataset::GetSpatialRef();
    3072             : }
    3073             : 
    3074             : /************************************************************************/
    3075             : /*                           FetchCopyParam()                            */
    3076             : /************************************************************************/
    3077             : 
    3078         312 : double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue,
    3079             :                                      const char *pszParam, double dfDefault,
    3080             :                                      bool *pbFound)
    3081             : 
    3082             : {
    3083             :     char *pszTemp =
    3084         312 :         CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam));
    3085         312 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp);
    3086         312 :     CPLFree(pszTemp);
    3087             : 
    3088         312 :     if (pbFound)
    3089             :     {
    3090         312 :         *pbFound = (pszValue != nullptr);
    3091             :     }
    3092             : 
    3093         312 :     if (pszValue)
    3094             :     {
    3095           0 :         return CPLAtofM(pszValue);
    3096             :     }
    3097             : 
    3098         312 :     return dfDefault;
    3099             : }
    3100             : 
    3101             : /************************************************************************/
    3102             : /*                           FetchStandardParallels()                   */
    3103             : /************************************************************************/
    3104             : 
    3105             : std::vector<std::string>
    3106           0 : netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue)
    3107             : {
    3108             :     // cf-1.0 tags
    3109           0 :     const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL);
    3110             : 
    3111           0 :     std::vector<std::string> ret;
    3112           0 :     if (pszValue != nullptr)
    3113             :     {
    3114           0 :         CPLStringList aosValues;
    3115           0 :         if (pszValue[0] != '{' &&
    3116           0 :             CPLString(pszValue).Trim().find(' ') != std::string::npos)
    3117             :         {
    3118             :             // Some files like
    3119             :             // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc
    3120             :             // do not use standard formatting for arrays, but just space
    3121             :             // separated syntax
    3122           0 :             aosValues = CSLTokenizeString2(pszValue, " ", 0);
    3123             :         }
    3124             :         else
    3125             :         {
    3126           0 :             aosValues = NCDFTokenizeArray(pszValue);
    3127             :         }
    3128           0 :         for (int i = 0; i < aosValues.size(); i++)
    3129             :         {
    3130           0 :             ret.push_back(aosValues[i]);
    3131             :         }
    3132             :     }
    3133             :     // Try gdal tags.
    3134             :     else
    3135             :     {
    3136           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1);
    3137             : 
    3138           0 :         if (pszValue != nullptr)
    3139           0 :             ret.push_back(pszValue);
    3140             : 
    3141           0 :         pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2);
    3142             : 
    3143           0 :         if (pszValue != nullptr)
    3144           0 :             ret.push_back(pszValue);
    3145             :     }
    3146             : 
    3147           0 :     return ret;
    3148             : }
    3149             : 
    3150             : /************************************************************************/
    3151             : /*                           FetchAttr()                                */
    3152             : /************************************************************************/
    3153             : 
    3154        3677 : const char *netCDFDataset::FetchAttr(const char *pszVarFullName,
    3155             :                                      const char *pszAttr)
    3156             : 
    3157             : {
    3158        3677 :     char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr));
    3159        3677 :     const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey);
    3160        3677 :     CPLFree(pszKey);
    3161        3677 :     return pszValue;
    3162             : }
    3163             : 
    3164        2436 : const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId,
    3165             :                                      const char *pszAttr)
    3166             : 
    3167             : {
    3168        2436 :     char *pszVarFullName = nullptr;
    3169        2436 :     NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName);
    3170        2436 :     const char *pszValue = FetchAttr(pszVarFullName, pszAttr);
    3171        2436 :     CPLFree(pszVarFullName);
    3172        2436 :     return pszValue;
    3173             : }
    3174             : 
    3175             : /************************************************************************/
    3176             : /*                       IsDifferenceBelow()                            */
    3177             : /************************************************************************/
    3178             : 
    3179        1073 : static bool IsDifferenceBelow(double dfA, double dfB, double dfError)
    3180             : {
    3181        1073 :     const double dfAbsDiff = fabs(dfA - dfB);
    3182        1073 :     return dfAbsDiff <= dfError;
    3183             : }
    3184             : 
    3185             : /************************************************************************/
    3186             : /*                      SetProjectionFromVar()                          */
    3187             : /************************************************************************/
    3188         511 : void netCDFDataset::SetProjectionFromVar(
    3189             :     int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM,
    3190             :     std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg,
    3191             :     std::vector<std::string> *paosRemovedMDItems)
    3192             : {
    3193         511 :     bool bGotGeogCS = false;
    3194         511 :     bool bGotCfSRS = false;
    3195         511 :     bool bGotCfWktSRS = false;
    3196         511 :     bool bGotGdalSRS = false;
    3197         511 :     bool bGotCfGT = false;
    3198         511 :     bool bGotGdalGT = false;
    3199             : 
    3200             :     // These values from CF metadata.
    3201         511 :     OGRSpatialReference oSRS;
    3202         511 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3203         511 :     size_t xdim = nRasterXSize;
    3204         511 :     size_t ydim = nRasterYSize;
    3205             : 
    3206             :     // These values from GDAL metadata.
    3207         511 :     const char *pszWKT = nullptr;
    3208         511 :     const char *pszGeoTransform = nullptr;
    3209             : 
    3210         511 :     netCDFDataset *poDS = this;  // Perhaps this should be removed for clarity.
    3211             : 
    3212         511 :     CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId,
    3213             :              nVarId);
    3214             : 
    3215             :     // Get x/y range information.
    3216             : 
    3217             :     // Temp variables to use in SetGeoTransform() and SetProjection().
    3218         511 :     double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
    3219             : 
    3220             :     // Look for grid_mapping metadata.
    3221         511 :     const char *pszValue = pszGivenGM;
    3222         511 :     CPLString osTmpGridMapping;  // let is in this outer scope as pszValue may
    3223             :         // point to it
    3224         511 :     if (pszValue == nullptr)
    3225             :     {
    3226         468 :         pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING);
    3227         468 :         if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' '))
    3228             :         {
    3229             :             // Expanded form of grid_mapping
    3230             :             // e.g. "crsOSGB: x y crsWGS84: lat lon"
    3231             :             // Pickup the grid_mapping whose coordinates are dimensions of the
    3232             :             // variable
    3233           6 :             CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0));
    3234           3 :             if ((aosTokens.size() % 3) == 0)
    3235             :             {
    3236           3 :                 for (int i = 0; i < aosTokens.size() / 3; i++)
    3237             :                 {
    3238           3 :                     if (CSLFindString(poDS->papszDimName,
    3239           9 :                                       aosTokens[3 * i + 1]) >= 0 &&
    3240           3 :                         CSLFindString(poDS->papszDimName,
    3241           3 :                                       aosTokens[3 * i + 2]) >= 0)
    3242             :                     {
    3243           3 :                         osTmpGridMapping = aosTokens[3 * i];
    3244           6 :                         if (!osTmpGridMapping.empty() &&
    3245           3 :                             osTmpGridMapping.back() == ':')
    3246             :                         {
    3247           3 :                             osTmpGridMapping.resize(osTmpGridMapping.size() -
    3248             :                                                     1);
    3249             :                         }
    3250           3 :                         pszValue = osTmpGridMapping.c_str();
    3251           3 :                         break;
    3252             :                     }
    3253             :                 }
    3254             :             }
    3255             :         }
    3256             :     }
    3257         511 :     char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : "");
    3258             : 
    3259         511 :     if (!EQUAL(pszGridMappingValue, ""))
    3260             :     {
    3261             :         // Read grid_mapping metadata.
    3262         217 :         int nProjGroupID = -1;
    3263         217 :         int nProjVarID = -1;
    3264         217 :         if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID,
    3265         217 :                            &nProjVarID) == CE_None)
    3266             :         {
    3267         216 :             poDS->ReadAttributes(nProjGroupID, nProjVarID);
    3268             : 
    3269             :             // Look for GDAL spatial_ref and GeoTransform within grid_mapping.
    3270         216 :             CPLFree(pszGridMappingValue);
    3271         216 :             pszGridMappingValue = nullptr;
    3272         216 :             NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue);
    3273         216 :             if (pszGridMappingValue)
    3274             :             {
    3275         216 :                 CPLDebug("GDAL_netCDF", "got grid_mapping %s",
    3276             :                          pszGridMappingValue);
    3277         216 :                 pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF);
    3278         216 :                 if (!pszWKT)
    3279             :                 {
    3280          34 :                     pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT);
    3281             :                 }
    3282             :                 else
    3283             :                 {
    3284         182 :                     bGotGdalSRS = true;
    3285         182 :                     CPLDebug("GDAL_netCDF", "setting WKT from GDAL");
    3286             :                 }
    3287         216 :                 if (pszWKT)
    3288             :                 {
    3289         186 :                     if (!bGotGdalSRS)
    3290             :                     {
    3291           4 :                         bGotCfWktSRS = true;
    3292           4 :                         CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3293             :                     }
    3294         186 :                     if (returnProjStr != nullptr)
    3295             :                     {
    3296          41 :                         (*returnProjStr) = std::string(pszWKT);
    3297             :                     }
    3298             :                     else
    3299             :                     {
    3300         145 :                         m_bAddedProjectionVarsDefs = true;
    3301         145 :                         m_bAddedProjectionVarsData = true;
    3302         290 :                         OGRSpatialReference oSRSTmp;
    3303         145 :                         oSRSTmp.SetAxisMappingStrategy(
    3304             :                             OAMS_TRADITIONAL_GIS_ORDER);
    3305         145 :                         oSRSTmp.importFromWkt(pszWKT);
    3306         145 :                         SetSpatialRefNoUpdate(&oSRSTmp);
    3307             :                     }
    3308             :                     pszGeoTransform =
    3309         186 :                         FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM);
    3310             :                 }
    3311             :             }
    3312             :             else
    3313             :             {
    3314           0 :                 pszGridMappingValue = CPLStrdup("");
    3315             :             }
    3316             :         }
    3317             :     }
    3318             : 
    3319             :     // Get information about the file.
    3320             :     //
    3321             :     // Was this file created by the GDAL netcdf driver?
    3322             :     // Was this file created by the newer (CF-conformant) driver?
    3323             :     //
    3324             :     // 1) If GDAL netcdf metadata is set, and version >= 1.9,
    3325             :     //    it was created with the new driver
    3326             :     // 2) Else, if spatial_ref and GeoTransform are present in the
    3327             :     //    grid_mapping variable, it was created by the old driver
    3328         511 :     pszValue = FetchAttr("NC_GLOBAL", "GDAL");
    3329             : 
    3330         511 :     if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900))
    3331             :     {
    3332         239 :         bIsGdalFile = true;
    3333         239 :         bIsGdalCfFile = true;
    3334             :     }
    3335         272 :     else if (pszWKT != nullptr && pszGeoTransform != nullptr)
    3336             :     {
    3337          17 :         bIsGdalFile = true;
    3338          17 :         bIsGdalCfFile = false;
    3339             :     }
    3340             : 
    3341             :     // Set default bottom-up default value.
    3342             :     // Y axis dimension and absence of GT can modify this value.
    3343             :     // Override with Config option GDAL_NETCDF_BOTTOMUP.
    3344             : 
    3345             :     // New driver is bottom-up by default.
    3346         511 :     if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY)
    3347          19 :         poDS->bBottomUp = false;
    3348             :     else
    3349         492 :         poDS->bBottomUp = true;
    3350             : 
    3351         511 :     CPLDebug("GDAL_netCDF",
    3352             :              "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d",
    3353         511 :              static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile),
    3354         511 :              static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp));
    3355             : 
    3356             :     // Read projection coordinates.
    3357             : 
    3358         511 :     int nGroupDimXID = -1;
    3359         511 :     int nVarDimXID = -1;
    3360         511 :     int nGroupDimYID = -1;
    3361         511 :     int nVarDimYID = -1;
    3362         511 :     if (sg != nullptr)
    3363             :     {
    3364          43 :         nGroupDimXID = sg->get_ncID();
    3365          43 :         nGroupDimYID = sg->get_ncID();
    3366          43 :         nVarDimXID = sg->getNodeCoordVars()[0];
    3367          43 :         nVarDimYID = sg->getNodeCoordVars()[1];
    3368             :     }
    3369             : 
    3370         511 :     if (!bReadSRSOnly)
    3371             :     {
    3372         345 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID,
    3373             :                        &nVarDimXID);
    3374         345 :         NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID,
    3375             :                        &nVarDimYID);
    3376             :         // TODO: if above resolving fails we should also search for coordinate
    3377             :         // variables without same name than dimension using the same resolving
    3378             :         // logic. This should handle for example NASA Ocean Color L2 products.
    3379             : 
    3380             :         const bool bIgnoreXYAxisNameChecks =
    3381         690 :             CPLTestBool(CSLFetchNameValueDef(
    3382         345 :                 papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS",
    3383             :                 CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS",
    3384         345 :                                    "NO"))) ||
    3385             :             // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res
    3386             :             // and transform attributes
    3387         345 :             (FetchAttr(nGroupId, nVarId, "res") != nullptr &&
    3388         690 :              FetchAttr(nGroupId, nVarId, "transform") != nullptr) ||
    3389         344 :             FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr;
    3390             : 
    3391             :         // Check that they are 1D or 2D variables
    3392         345 :         if (nVarDimXID >= 0)
    3393             :         {
    3394         251 :             int ndims = -1;
    3395         251 :             nc_inq_varndims(nGroupId, nVarDimXID, &ndims);
    3396         251 :             if (ndims == 0 || ndims > 2)
    3397           0 :                 nVarDimXID = -1;
    3398         251 :             else if (!bIgnoreXYAxisNameChecks)
    3399             :             {
    3400         249 :                 if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    3401         160 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) &&
    3402             :                     // In case of inversion of X/Y
    3403         440 :                     !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) &&
    3404          31 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr))
    3405             :                 {
    3406             :                     char szVarNameX[NC_MAX_NAME + 1];
    3407          31 :                     CPL_IGNORE_RET_VAL(
    3408          31 :                         nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    3409          31 :                     if (!(ndims == 1 &&
    3410          30 :                           (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) ||
    3411          29 :                            EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME))))
    3412             :                     {
    3413          30 :                         CPLDebug(
    3414             :                             "netCDF",
    3415             :                             "Georeferencing ignored due to non-specific "
    3416             :                             "enough X axis name. "
    3417             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3418             :                             "as configuration option to bypass this check");
    3419          30 :                         nVarDimXID = -1;
    3420             :                     }
    3421             :                 }
    3422             :             }
    3423             :         }
    3424             : 
    3425         345 :         if (nVarDimYID >= 0)
    3426             :         {
    3427         253 :             int ndims = -1;
    3428         253 :             nc_inq_varndims(nGroupId, nVarDimYID, &ndims);
    3429         253 :             if (ndims == 0 || ndims > 2)
    3430           1 :                 nVarDimYID = -1;
    3431         252 :             else if (!bIgnoreXYAxisNameChecks)
    3432             :             {
    3433         250 :                 if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) &&
    3434         161 :                     !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) &&
    3435             :                     // In case of inversion of X/Y
    3436         443 :                     !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    3437          32 :                     !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr))
    3438             :                 {
    3439             :                     char szVarNameY[NC_MAX_NAME + 1];
    3440          32 :                     CPL_IGNORE_RET_VAL(
    3441          32 :                         nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    3442          32 :                     if (!(ndims == 1 &&
    3443          32 :                           (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) ||
    3444          31 :                            EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME))))
    3445             :                     {
    3446          31 :                         CPLDebug(
    3447             :                             "netCDF",
    3448             :                             "Georeferencing ignored due to non-specific "
    3449             :                             "enough Y axis name. "
    3450             :                             "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES "
    3451             :                             "as configuration option to bypass this check");
    3452          31 :                         nVarDimYID = -1;
    3453             :                     }
    3454             :                 }
    3455             :             }
    3456             :         }
    3457             : 
    3458         345 :         if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1))
    3459             :         {
    3460           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3461             :                      "1-pixel width/height files not supported, "
    3462             :                      "xdim: %ld ydim: %ld",
    3463             :                      static_cast<long>(xdim), static_cast<long>(ydim));
    3464           0 :             nVarDimXID = -1;
    3465           0 :             nVarDimYID = -1;
    3466             :         }
    3467             :     }
    3468             : 
    3469         511 :     const char *pszUnits = nullptr;
    3470         511 :     if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3471             :     {
    3472         264 :         const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units");
    3473         264 :         const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units");
    3474             :         // Normalize degrees_east/degrees_north to degrees
    3475             :         // Cf https://github.com/OSGeo/gdal/issues/11009
    3476         264 :         if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east"))
    3477          78 :             pszUnitsX = "degrees";
    3478         264 :         if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north"))
    3479          78 :             pszUnitsY = "degrees";
    3480             : 
    3481         264 :         if (pszUnitsX && pszUnitsY)
    3482             :         {
    3483         217 :             if (EQUAL(pszUnitsX, pszUnitsY))
    3484         214 :                 pszUnits = pszUnitsX;
    3485           3 :             else if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3486             :             {
    3487           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3488             :                          "X axis unit (%s) is different from Y axis "
    3489             :                          "unit (%s). SRS will ignore axis unit and be "
    3490             :                          "likely wrong.",
    3491             :                          pszUnitsX, pszUnitsY);
    3492             :             }
    3493             :         }
    3494          47 :         else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3495             :         {
    3496           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3497             :                      "X axis unit is defined, but not Y one ."
    3498             :                      "SRS will ignore axis unit and be likely wrong.");
    3499             :         }
    3500          47 :         else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, ""))
    3501             :         {
    3502           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3503             :                      "Y axis unit is defined, but not X one ."
    3504             :                      "SRS will ignore axis unit and be likely wrong.");
    3505             :         }
    3506             :     }
    3507             : 
    3508         511 :     if (!pszWKT && !EQUAL(pszGridMappingValue, ""))
    3509             :     {
    3510          31 :         CPLStringList aosGridMappingKeyValues;
    3511          31 :         const size_t nLenGridMappingValue = strlen(pszGridMappingValue);
    3512         789 :         for (const char *const *papszIter = papszMetadata;
    3513         789 :              papszIter && *papszIter; ++papszIter)
    3514             :         {
    3515         758 :             if (STARTS_WITH(*papszIter, pszGridMappingValue) &&
    3516         236 :                 (*papszIter)[nLenGridMappingValue] == '#')
    3517             :             {
    3518         236 :                 char *pszKey = nullptr;
    3519         472 :                 pszValue = CPLParseNameValue(
    3520         236 :                     *papszIter + nLenGridMappingValue + 1, &pszKey);
    3521         236 :                 if (pszKey && pszValue)
    3522         236 :                     aosGridMappingKeyValues.SetNameValue(pszKey, pszValue);
    3523         236 :                 CPLFree(pszKey);
    3524             :             }
    3525             :         }
    3526             : 
    3527          31 :         bGotGeogCS = aosGridMappingKeyValues.FetchNameValue(
    3528             :                          CF_PP_SEMI_MAJOR_AXIS) != nullptr;
    3529             : 
    3530          31 :         oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits);
    3531          31 :         bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected();
    3532             :     }
    3533             :     else
    3534             :     {
    3535             :         // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs"
    3536             :         // attribute hold on the variable of interest that contains a PROJ.4
    3537             :         // string
    3538         480 :         pszValue = FetchAttr(nGroupId, nVarId, "crs");
    3539         481 :         if (pszValue &&
    3540           1 :             (strstr(pszValue, "+proj=") != nullptr ||
    3541           0 :              strstr(pszValue, "GEOGCS") != nullptr ||
    3542           0 :              strstr(pszValue, "PROJCS") != nullptr ||
    3543         481 :              strstr(pszValue, "EPSG:") != nullptr) &&
    3544           1 :             oSRS.SetFromUserInput(pszValue) == OGRERR_NONE)
    3545             :         {
    3546           1 :             bGotCfSRS = true;
    3547             :         }
    3548             :     }
    3549             : 
    3550             :     // Set Projection from CF.
    3551         511 :     double dfLinearUnitsConvFactor = 1.0;
    3552         511 :     if ((bGotGeogCS || bGotCfSRS))
    3553             :     {
    3554          31 :         if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0)
    3555             :         {
    3556             :             // Set SRS Units.
    3557             : 
    3558             :             // Check units for x and y.
    3559          28 :             if (oSRS.IsProjected())
    3560             :             {
    3561          25 :                 dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr);
    3562             : 
    3563             :                 // If the user doesn't ask to preserve the axis unit,
    3564             :                 // then normalize to metre
    3565          31 :                 if (dfLinearUnitsConvFactor != 1.0 &&
    3566           6 :                     !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS",
    3567             :                                   false))
    3568             :                 {
    3569           5 :                     oSRS.SetLinearUnits("metre", 1.0);
    3570           5 :                     oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001);
    3571             :                 }
    3572             :                 else
    3573             :                 {
    3574          20 :                     dfLinearUnitsConvFactor = 1.0;
    3575             :                 }
    3576             :             }
    3577             :         }
    3578             : 
    3579             :         // Set projection.
    3580          31 :         char *pszTempProjection = nullptr;
    3581          31 :         oSRS.exportToWkt(&pszTempProjection);
    3582          31 :         if (pszTempProjection)
    3583             :         {
    3584          31 :             CPLDebug("GDAL_netCDF", "setting WKT from CF");
    3585          31 :             if (returnProjStr != nullptr)
    3586             :             {
    3587           2 :                 (*returnProjStr) = std::string(pszTempProjection);
    3588             :             }
    3589             :             else
    3590             :             {
    3591          29 :                 m_bAddedProjectionVarsDefs = true;
    3592          29 :                 m_bAddedProjectionVarsData = true;
    3593          29 :                 SetSpatialRefNoUpdate(&oSRS);
    3594             :             }
    3595             :         }
    3596          31 :         CPLFree(pszTempProjection);
    3597             :     }
    3598             : 
    3599         511 :     if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 &&
    3600             :         ydim > 0)
    3601             :     {
    3602             :         double *pdfXCoord =
    3603         221 :             static_cast<double *>(CPLCalloc(xdim, sizeof(double)));
    3604             :         double *pdfYCoord =
    3605         221 :             static_cast<double *>(CPLCalloc(ydim, sizeof(double)));
    3606             : 
    3607         221 :         size_t start[2] = {0, 0};
    3608         221 :         size_t edge[2] = {xdim, 0};
    3609         221 :         int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge,
    3610             :                                         pdfXCoord);
    3611         221 :         NCDF_ERR(status);
    3612             : 
    3613         221 :         edge[0] = ydim;
    3614         221 :         status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge,
    3615             :                                     pdfYCoord);
    3616         221 :         NCDF_ERR(status);
    3617             : 
    3618         221 :         nc_type nc_var_dimx_datatype = NC_NAT;
    3619             :         status =
    3620         221 :             nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype);
    3621         221 :         NCDF_ERR(status);
    3622             : 
    3623         221 :         nc_type nc_var_dimy_datatype = NC_NAT;
    3624             :         status =
    3625         221 :             nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype);
    3626         221 :         NCDF_ERR(status);
    3627             : 
    3628         221 :         if (!poDS->bSwitchedXY)
    3629             :         {
    3630             :             // Convert ]180,540] longitude values to ]-180,0].
    3631         308 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3632          89 :                 CPLTestBool(
    3633             :                     CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")))
    3634             :             {
    3635             :                 // If minimum longitude is > 180, subtract 360 from all.
    3636             :                 // Add a check on the maximum X value too, since
    3637             :                 // NCDFIsVarLongitude() is not very specific by default (see
    3638             :                 // https://github.com/OSGeo/gdal/issues/1440)
    3639          96 :                 if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 &&
    3640           7 :                     std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540)
    3641             :                 {
    3642           0 :                     CPLDebug(
    3643             :                         "GDAL_netCDF",
    3644             :                         "Offsetting longitudes from ]180,540] to ]-180,180]. "
    3645             :                         "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO");
    3646           0 :                     for (size_t i = 0; i < xdim; i++)
    3647           0 :                         pdfXCoord[i] -= 360;
    3648             :                 }
    3649             :             }
    3650             :         }
    3651             : 
    3652             :         // Is pixel spacing uniform across the map?
    3653             : 
    3654             :         // Check Longitude.
    3655             : 
    3656         221 :         bool bLonSpacingOK = false;
    3657         221 :         if (xdim == 2)
    3658             :         {
    3659          28 :             bLonSpacingOK = true;
    3660             :         }
    3661             :         else
    3662             :         {
    3663         193 :             bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]);
    3664             : 
    3665             :             // fix longitudes if longitudes should increase from
    3666             :             // west to east, but west > east
    3667         272 :             if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) &&
    3668          79 :                 !bWestIsLeft)
    3669             :             {
    3670           2 :                 size_t ndecreases = 0;
    3671             : 
    3672             :                 // there is lon wrap if longitudes increase
    3673             :                 // with one single decrease
    3674         107 :                 for (size_t i = 1; i < xdim; i++)
    3675             :                 {
    3676         105 :                     if (pdfXCoord[i] < pdfXCoord[i - 1])
    3677           1 :                         ndecreases++;
    3678             :                 }
    3679             : 
    3680           2 :                 if (ndecreases == 1)
    3681             :                 {
    3682           1 :                     CPLDebug("GDAL_netCDF", "longitude wrap detected");
    3683           4 :                     for (size_t i = 0; i < xdim; i++)
    3684             :                     {
    3685           3 :                         if (pdfXCoord[i] > pdfXCoord[xdim - 1])
    3686           1 :                             pdfXCoord[i] -= 360;
    3687             :                     }
    3688             :                 }
    3689             :             }
    3690             : 
    3691         193 :             const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0];
    3692         193 :             const double dfSpacingMiddle =
    3693         193 :                 pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2];
    3694         193 :             const double dfSpacingLast =
    3695         193 :                 pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2];
    3696             : 
    3697         193 :             CPLDebug("GDAL_netCDF",
    3698             :                      "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3699             :                      "dfSpacingLast: %f",
    3700             :                      static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle,
    3701             :                      dfSpacingLast);
    3702             : #ifdef NCDF_DEBUG
    3703             :             CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0],
    3704             :                      pdfXCoord[1], pdfXCoord[xdim / 2],
    3705             :                      pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2],
    3706             :                      pdfXCoord[xdim - 1]);
    3707             : #endif
    3708             : 
    3709             :             // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc
    3710             :             // requires a 0.02% tolerance, so let's settle for 0.05%
    3711             : 
    3712             :             // For float variables, increase to 0.2% (as seen in
    3713             :             // https://github.com/OSGeo/gdal/issues/3663)
    3714         193 :             const double dfEpsRel =
    3715         193 :                 nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3716             : 
    3717             :             const double dfEps =
    3718             :                 dfEpsRel *
    3719         386 :                 std::max(fabs(dfSpacingBegin),
    3720         193 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3721         380 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3722         380 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3723         187 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3724             :             {
    3725         187 :                 bLonSpacingOK = true;
    3726             :             }
    3727           6 :             else if (CPLTestBool(CPLGetConfigOption(
    3728             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3729             :             {
    3730           0 :                 bLonSpacingOK = true;
    3731           0 :                 CPLDebug(
    3732             :                     "GDAL_netCDF",
    3733             :                     "Longitude/X is not equally spaced, but will be considered "
    3734             :                     "as such because of "
    3735             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3736             :             }
    3737             :         }
    3738             : 
    3739         221 :         if (bLonSpacingOK == false)
    3740             :         {
    3741           6 :             CPLDebug(
    3742             :                 "GDAL_netCDF", "%s",
    3743             :                 "Longitude/X is not equally spaced (with a 0.05% tolerance). "
    3744             :                 "You may set the "
    3745             :                 "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3746             :                 "option to YES to ignore this check");
    3747             :         }
    3748             : 
    3749             :         // Check Latitude.
    3750         221 :         bool bLatSpacingOK = false;
    3751             : 
    3752         221 :         if (ydim == 2)
    3753             :         {
    3754          48 :             bLatSpacingOK = true;
    3755             :         }
    3756             :         else
    3757             :         {
    3758         173 :             const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0];
    3759         173 :             const double dfSpacingMiddle =
    3760         173 :                 pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2];
    3761             : 
    3762         173 :             const double dfSpacingLast =
    3763         173 :                 pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2];
    3764             : 
    3765         173 :             CPLDebug("GDAL_netCDF",
    3766             :                      "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f "
    3767             :                      "dfSpacingLast: %f",
    3768             :                      (long)ydim, dfSpacingBegin, dfSpacingMiddle,
    3769             :                      dfSpacingLast);
    3770             : #ifdef NCDF_DEBUG
    3771             :             CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0],
    3772             :                      pdfYCoord[1], pdfYCoord[ydim / 2],
    3773             :                      pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2],
    3774             :                      pdfYCoord[ydim - 1]);
    3775             : #endif
    3776             : 
    3777         173 :             const double dfEpsRel =
    3778         173 :                 nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005;
    3779             : 
    3780             :             const double dfEps =
    3781             :                 dfEpsRel *
    3782         346 :                 std::max(fabs(dfSpacingBegin),
    3783         173 :                          std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast)));
    3784         344 :             if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) &&
    3785         344 :                 IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) &&
    3786         162 :                 IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps))
    3787             :             {
    3788         162 :                 bLatSpacingOK = true;
    3789             :             }
    3790          11 :             else if (CPLTestBool(CPLGetConfigOption(
    3791             :                          "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO")))
    3792             :             {
    3793           0 :                 bLatSpacingOK = true;
    3794           0 :                 CPLDebug(
    3795             :                     "GDAL_netCDF",
    3796             :                     "Latitude/Y is not equally spaced, but will be considered "
    3797             :                     "as such because of "
    3798             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK");
    3799             :             }
    3800          11 :             else if (!oSRS.IsProjected() &&
    3801          11 :                      fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 &&
    3802          30 :                      fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 &&
    3803           8 :                      fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1)
    3804             :             {
    3805           8 :                 bLatSpacingOK = true;
    3806           8 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3807             :                          "Latitude grid not spaced evenly.  "
    3808             :                          "Setting projection for grid spacing is "
    3809             :                          "within 0.1 degrees threshold.");
    3810             : 
    3811           8 :                 CPLDebug("GDAL_netCDF",
    3812             :                          "Latitude grid not spaced evenly, but within 0.1 "
    3813             :                          "degree threshold (probably a Gaussian grid).  "
    3814             :                          "Saving original latitude values in Y_VALUES "
    3815             :                          "geolocation metadata");
    3816           8 :                 Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y");
    3817             :             }
    3818             : 
    3819         173 :             if (bLatSpacingOK == false)
    3820             :             {
    3821           3 :                 CPLDebug(
    3822             :                     "GDAL_netCDF", "%s",
    3823             :                     "Latitude/Y is not equally spaced (with a 0.05% "
    3824             :                     "tolerance). "
    3825             :                     "You may set the "
    3826             :                     "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration "
    3827             :                     "option to YES to ignore this check");
    3828             :             }
    3829             :         }
    3830             : 
    3831         221 :         if (bLonSpacingOK && bLatSpacingOK)
    3832             :         {
    3833             :             // We have gridded data so we can set the Georeferencing info.
    3834             : 
    3835             :             // Enable GeoTransform.
    3836             : 
    3837             :             // In the following "actual_range" and "node_offset"
    3838             :             // are attributes used by netCDF files created by GMT.
    3839             :             // If we find them we know how to proceed. Else, use
    3840             :             // the original algorithm.
    3841         214 :             bGotCfGT = true;
    3842             : 
    3843         214 :             int node_offset = 0;
    3844         214 :             NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset);
    3845             : 
    3846         214 :             double adfActualRange[2] = {0.0, 0.0};
    3847         214 :             double xMinMax[2] = {0.0, 0.0};
    3848         214 :             double yMinMax[2] = {0.0, 0.0};
    3849             : 
    3850             :             const auto RoundMinMaxForFloatVals =
    3851          58 :                 [](double &dfMin, double &dfMax, int nIntervals)
    3852             :             {
    3853             :                 // Helps for a case where longitudes range from
    3854             :                 // -179.99 to 180.0 with a 0.01 degree spacing.
    3855             :                 // However as this is encoded in a float array,
    3856             :                 // -179.99 is actually read as -179.99000549316406 as
    3857             :                 // a double. Try to detect that and correct the rounding
    3858             : 
    3859          87 :                 const auto IsAlmostInteger = [](double dfVal)
    3860             :                 {
    3861          87 :                     constexpr double THRESHOLD_INTEGER = 1e-3;
    3862          87 :                     return std::fabs(dfVal - std::round(dfVal)) <=
    3863          87 :                            THRESHOLD_INTEGER;
    3864             :                 };
    3865             : 
    3866          58 :                 const double dfSpacing = (dfMax - dfMin) / nIntervals;
    3867          58 :                 if (dfSpacing > 0)
    3868             :                 {
    3869          47 :                     const double dfInvSpacing = 1.0 / dfSpacing;
    3870          47 :                     if (IsAlmostInteger(dfInvSpacing))
    3871             :                     {
    3872          20 :                         const double dfRoundedSpacing =
    3873          20 :                             1.0 / std::round(dfInvSpacing);
    3874          20 :                         const double dfMinDivRoundedSpacing =
    3875          20 :                             dfMin / dfRoundedSpacing;
    3876          20 :                         const double dfMaxDivRoundedSpacing =
    3877          20 :                             dfMax / dfRoundedSpacing;
    3878          40 :                         if (IsAlmostInteger(dfMinDivRoundedSpacing) &&
    3879          20 :                             IsAlmostInteger(dfMaxDivRoundedSpacing))
    3880             :                         {
    3881          20 :                             const double dfRoundedMin =
    3882          20 :                                 std::round(dfMinDivRoundedSpacing) *
    3883             :                                 dfRoundedSpacing;
    3884          20 :                             const double dfRoundedMax =
    3885          20 :                                 std::round(dfMaxDivRoundedSpacing) *
    3886             :                                 dfRoundedSpacing;
    3887          20 :                             if (static_cast<float>(dfMin) ==
    3888          20 :                                     static_cast<float>(dfRoundedMin) &&
    3889           8 :                                 static_cast<float>(dfMax) ==
    3890           8 :                                     static_cast<float>(dfRoundedMax))
    3891             :                             {
    3892           7 :                                 dfMin = dfRoundedMin;
    3893           7 :                                 dfMax = dfRoundedMax;
    3894             :                             }
    3895             :                         }
    3896             :                     }
    3897             :                 }
    3898          58 :             };
    3899             : 
    3900         214 :             if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range",
    3901             :                                    adfActualRange))
    3902             :             {
    3903           3 :                 xMinMax[0] = adfActualRange[0];
    3904           3 :                 xMinMax[1] = adfActualRange[1];
    3905             : 
    3906             :                 // Present xMinMax[] in the same order as padfXCoord
    3907           3 :                 if ((xMinMax[0] - xMinMax[1]) *
    3908           3 :                         (pdfXCoord[0] - pdfXCoord[xdim - 1]) <
    3909             :                     0)
    3910             :                 {
    3911           0 :                     std::swap(xMinMax[0], xMinMax[1]);
    3912             :                 }
    3913             :             }
    3914             :             else
    3915             :             {
    3916         211 :                 xMinMax[0] = pdfXCoord[0];
    3917         211 :                 xMinMax[1] = pdfXCoord[xdim - 1];
    3918         211 :                 node_offset = 0;
    3919             : 
    3920         211 :                 if (nc_var_dimx_datatype == NC_FLOAT)
    3921             :                 {
    3922          29 :                     RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1],
    3923          29 :                                             poDS->nRasterXSize - 1);
    3924             :                 }
    3925             :             }
    3926             : 
    3927         214 :             if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range",
    3928             :                                    adfActualRange))
    3929             :             {
    3930           3 :                 yMinMax[0] = adfActualRange[0];
    3931           3 :                 yMinMax[1] = adfActualRange[1];
    3932             : 
    3933             :                 // Present yMinMax[] in the same order as pdfYCoord
    3934           3 :                 if ((yMinMax[0] - yMinMax[1]) *
    3935           3 :                         (pdfYCoord[0] - pdfYCoord[ydim - 1]) <
    3936             :                     0)
    3937             :                 {
    3938           1 :                     std::swap(yMinMax[0], yMinMax[1]);
    3939             :                 }
    3940             :             }
    3941             :             else
    3942             :             {
    3943         211 :                 yMinMax[0] = pdfYCoord[0];
    3944         211 :                 yMinMax[1] = pdfYCoord[ydim - 1];
    3945         211 :                 node_offset = 0;
    3946             : 
    3947         211 :                 if (nc_var_dimy_datatype == NC_FLOAT)
    3948             :                 {
    3949          29 :                     RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1],
    3950          29 :                                             poDS->nRasterYSize - 1);
    3951             :                 }
    3952             :             }
    3953             : 
    3954         214 :             double dfCoordOffset = 0.0;
    3955         214 :             double dfCoordScale = 1.0;
    3956         214 :             if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET,
    3957         218 :                                    &dfCoordOffset) &&
    3958           4 :                 !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR,
    3959             :                                    &dfCoordScale))
    3960             :             {
    3961           4 :                 xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale;
    3962           4 :                 xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale;
    3963             :             }
    3964             : 
    3965         214 :             if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET,
    3966         218 :                                    &dfCoordOffset) &&
    3967           4 :                 !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR,
    3968             :                                    &dfCoordScale))
    3969             :             {
    3970           4 :                 yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale;
    3971           4 :                 yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale;
    3972             :             }
    3973             : 
    3974             :             // Check for reverse order of y-coordinate.
    3975         214 :             if (!bSwitchedXY)
    3976             :             {
    3977         212 :                 poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]);
    3978         212 :                 CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis",
    3979         212 :                          static_cast<int>(poDS->bBottomUp));
    3980         212 :                 if (!poDS->bBottomUp)
    3981             :                 {
    3982          32 :                     std::swap(yMinMax[0], yMinMax[1]);
    3983             :                 }
    3984             :             }
    3985             : 
    3986             :             // Geostationary satellites can specify units in (micro)radians
    3987             :             // So we check if they do, and if so convert to linear units
    3988             :             // (meters)
    3989         214 :             const char *pszProjName = oSRS.GetAttrValue("PROJECTION");
    3990         214 :             if (pszProjName != nullptr)
    3991             :             {
    3992          24 :                 if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE))
    3993             :                 {
    3994             :                     double satelliteHeight =
    3995           3 :                         oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0);
    3996           3 :                     size_t nAttlen = 0;
    3997             :                     char szUnits[NC_MAX_NAME + 1];
    3998           3 :                     szUnits[0] = '\0';
    3999           3 :                     nc_type nAttype = NC_NAT;
    4000           3 :                     nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype,
    4001             :                                &nAttlen);
    4002           6 :                     if (nAttlen < sizeof(szUnits) &&
    4003           3 :                         nc_get_att_text(nGroupId, nVarDimXID, "units",
    4004             :                                         szUnits) == NC_NOERR)
    4005             :                     {
    4006           3 :                         szUnits[nAttlen] = '\0';
    4007           3 :                         if (EQUAL(szUnits, "microradian"))
    4008             :                         {
    4009           1 :                             xMinMax[0] =
    4010           1 :                                 xMinMax[0] * satelliteHeight * 0.000001;
    4011           1 :                             xMinMax[1] =
    4012           1 :                                 xMinMax[1] * satelliteHeight * 0.000001;
    4013             :                         }
    4014           2 :                         else if (EQUAL(szUnits, "rad") ||
    4015           1 :                                  EQUAL(szUnits, "radian"))
    4016             :                         {
    4017           2 :                             xMinMax[0] = xMinMax[0] * satelliteHeight;
    4018           2 :                             xMinMax[1] = xMinMax[1] * satelliteHeight;
    4019             :                         }
    4020             :                     }
    4021           3 :                     szUnits[0] = '\0';
    4022           3 :                     nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype,
    4023             :                                &nAttlen);
    4024           6 :                     if (nAttlen < sizeof(szUnits) &&
    4025           3 :                         nc_get_att_text(nGroupId, nVarDimYID, "units",
    4026             :                                         szUnits) == NC_NOERR)
    4027             :                     {
    4028           3 :                         szUnits[nAttlen] = '\0';
    4029           3 :                         if (EQUAL(szUnits, "microradian"))
    4030             :                         {
    4031           1 :                             yMinMax[0] =
    4032           1 :                                 yMinMax[0] * satelliteHeight * 0.000001;
    4033           1 :                             yMinMax[1] =
    4034           1 :                                 yMinMax[1] * satelliteHeight * 0.000001;
    4035             :                         }
    4036           2 :                         else if (EQUAL(szUnits, "rad") ||
    4037           1 :                                  EQUAL(szUnits, "radian"))
    4038             :                         {
    4039           2 :                             yMinMax[0] = yMinMax[0] * satelliteHeight;
    4040           2 :                             yMinMax[1] = yMinMax[1] * satelliteHeight;
    4041             :                         }
    4042             :                     }
    4043             :                 }
    4044             :             }
    4045             : 
    4046         214 :             adfTempGeoTransform[0] = xMinMax[0];
    4047         214 :             adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) /
    4048         214 :                                      (poDS->nRasterXSize + (node_offset - 1));
    4049         214 :             adfTempGeoTransform[2] = 0;
    4050         214 :             if (bSwitchedXY)
    4051             :             {
    4052           2 :                 adfTempGeoTransform[3] = yMinMax[0];
    4053           2 :                 adfTempGeoTransform[4] = 0;
    4054           2 :                 adfTempGeoTransform[5] =
    4055           2 :                     (yMinMax[1] - yMinMax[0]) /
    4056           2 :                     (poDS->nRasterYSize + (node_offset - 1));
    4057             :             }
    4058             :             else
    4059             :             {
    4060         212 :                 adfTempGeoTransform[3] = yMinMax[1];
    4061         212 :                 adfTempGeoTransform[4] = 0;
    4062         212 :                 adfTempGeoTransform[5] =
    4063         212 :                     (yMinMax[0] - yMinMax[1]) /
    4064         212 :                     (poDS->nRasterYSize + (node_offset - 1));
    4065             :             }
    4066             : 
    4067             :             // Compute the center of the pixel.
    4068         214 :             if (!node_offset)
    4069             :             {
    4070             :                 // Otherwise its already the pixel center.
    4071         214 :                 adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2);
    4072         214 :                 adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2);
    4073             :             }
    4074             :         }
    4075             : 
    4076             :         const auto AreSRSEqualThroughProj4String =
    4077           2 :             [](const OGRSpatialReference &oSRS1,
    4078             :                const OGRSpatialReference &oSRS2)
    4079             :         {
    4080           2 :             char *pszProj4Str1 = nullptr;
    4081           2 :             oSRS1.exportToProj4(&pszProj4Str1);
    4082             : 
    4083           2 :             char *pszProj4Str2 = nullptr;
    4084           2 :             oSRS2.exportToProj4(&pszProj4Str2);
    4085             : 
    4086             :             {
    4087           2 :                 char *pszTmp = strstr(pszProj4Str1, "+datum=");
    4088           2 :                 if (pszTmp)
    4089           0 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4090             :             }
    4091             : 
    4092             :             {
    4093           2 :                 char *pszTmp = strstr(pszProj4Str2, "+datum=");
    4094           2 :                 if (pszTmp)
    4095           2 :                     memcpy(pszTmp, "+ellps=", strlen("+ellps="));
    4096             :             }
    4097             : 
    4098           2 :             bool bRet = false;
    4099           2 :             if (pszProj4Str1 && pszProj4Str2 &&
    4100           2 :                 EQUAL(pszProj4Str1, pszProj4Str2))
    4101             :             {
    4102           1 :                 bRet = true;
    4103             :             }
    4104             : 
    4105           2 :             CPLFree(pszProj4Str1);
    4106           2 :             CPLFree(pszProj4Str2);
    4107           2 :             return bRet;
    4108             :         };
    4109             : 
    4110         221 :         if (dfLinearUnitsConvFactor != 1.0)
    4111             :         {
    4112          35 :             for (int i = 0; i < 6; ++i)
    4113          30 :                 adfTempGeoTransform[i] *= dfLinearUnitsConvFactor;
    4114             : 
    4115           5 :             if (paosRemovedMDItems)
    4116             :             {
    4117             :                 char szVarNameX[NC_MAX_NAME + 1];
    4118           5 :                 CPL_IGNORE_RET_VAL(
    4119           5 :                     nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4120             : 
    4121             :                 char szVarNameY[NC_MAX_NAME + 1];
    4122           5 :                 CPL_IGNORE_RET_VAL(
    4123           5 :                     nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4124             : 
    4125           5 :                 paosRemovedMDItems->push_back(
    4126             :                     CPLSPrintf("%s#units", szVarNameX));
    4127           5 :                 paosRemovedMDItems->push_back(
    4128             :                     CPLSPrintf("%s#units", szVarNameY));
    4129             :             }
    4130             :         }
    4131             : 
    4132             :         // If there is a global "geospatial_bounds_crs" attribute, check that it
    4133             :         // is consistent with the SRS, and if so, use it as the SRS
    4134             :         const char *pszGBCRS =
    4135         221 :             FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs");
    4136         221 :         if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:"))
    4137             :         {
    4138           4 :             OGRSpatialReference oSRSFromGBCRS;
    4139           2 :             oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    4140           2 :             if (oSRSFromGBCRS.SetFromUserInput(
    4141             :                     pszGBCRS,
    4142             :                     OGRSpatialReference::
    4143           4 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE &&
    4144           2 :                 AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS))
    4145             :             {
    4146           1 :                 oSRS = std::move(oSRSFromGBCRS);
    4147           1 :                 SetSpatialRefNoUpdate(&oSRS);
    4148             :             }
    4149             :         }
    4150             : 
    4151         221 :         CPLFree(pdfXCoord);
    4152         221 :         CPLFree(pdfYCoord);
    4153             :     }  // end if(has dims)
    4154             : 
    4155             :     // Process custom GeoTransform GDAL value.
    4156         511 :     if (!EQUAL(pszGridMappingValue, "") && !bGotCfGT)
    4157             :     {
    4158             :         // TODO: Read the GT values and detect for conflict with CF.
    4159             :         // This could resolve the GT precision loss issue.
    4160             : 
    4161          94 :         if (pszGeoTransform != nullptr)
    4162             :         {
    4163             :             char **papszGeoTransform =
    4164          16 :                 CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS);
    4165          16 :             if (CSLCount(papszGeoTransform) == 6)
    4166             :             {
    4167          16 :                 bGotGdalGT = true;
    4168         112 :                 for (int i = 0; i < 6; i++)
    4169          96 :                     adfTempGeoTransform[i] = CPLAtof(papszGeoTransform[i]);
    4170             :             }
    4171          16 :             CSLDestroy(papszGeoTransform);
    4172             :         }
    4173             :         else
    4174             :         {
    4175             :             // Look for corner array values.
    4176             :             // CPLDebug("GDAL_netCDF",
    4177             :             //           "looking for geotransform corners");
    4178          78 :             bool bGotNN = false;
    4179          78 :             double dfNN = FetchCopyParam(pszGridMappingValue,
    4180             :                                          "Northernmost_Northing", 0, &bGotNN);
    4181             : 
    4182          78 :             bool bGotSN = false;
    4183          78 :             double dfSN = FetchCopyParam(pszGridMappingValue,
    4184             :                                          "Southernmost_Northing", 0, &bGotSN);
    4185             : 
    4186          78 :             bool bGotEE = false;
    4187          78 :             double dfEE = FetchCopyParam(pszGridMappingValue,
    4188             :                                          "Easternmost_Easting", 0, &bGotEE);
    4189             : 
    4190          78 :             bool bGotWE = false;
    4191          78 :             double dfWE = FetchCopyParam(pszGridMappingValue,
    4192             :                                          "Westernmost_Easting", 0, &bGotWE);
    4193             : 
    4194             :             // Only set the GeoTransform if we got all the values.
    4195          78 :             if (bGotNN && bGotSN && bGotEE && bGotWE)
    4196             :             {
    4197           0 :                 bGotGdalGT = true;
    4198             : 
    4199           0 :                 adfTempGeoTransform[0] = dfWE;
    4200           0 :                 adfTempGeoTransform[1] =
    4201           0 :                     (dfEE - dfWE) / (poDS->GetRasterXSize() - 1);
    4202           0 :                 adfTempGeoTransform[2] = 0.0;
    4203           0 :                 adfTempGeoTransform[3] = dfNN;
    4204           0 :                 adfTempGeoTransform[4] = 0.0;
    4205           0 :                 adfTempGeoTransform[5] =
    4206           0 :                     (dfSN - dfNN) / (poDS->GetRasterYSize() - 1);
    4207             :                 // Compute the center of the pixel.
    4208           0 :                 adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2);
    4209           0 :                 adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2);
    4210             :             }
    4211             :         }  // (pszGeoTransform != NULL)
    4212             : 
    4213          94 :         if (bGotGdalSRS && !bGotGdalGT)
    4214          72 :             CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!");
    4215             :     }
    4216             : 
    4217         511 :     if (!pszWKT && !bGotCfSRS)
    4218             :     {
    4219             :         // Some netCDF files have a srid attribute (#6613) like
    4220             :         // urn:ogc:def:crs:EPSG::6931
    4221         294 :         const char *pszSRID = FetchAttr(pszGridMappingValue, "srid");
    4222         294 :         if (pszSRID != nullptr)
    4223             :         {
    4224           0 :             oSRS.Clear();
    4225           0 :             if (oSRS.SetFromUserInput(
    4226             :                     pszSRID,
    4227             :                     OGRSpatialReference::
    4228           0 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    4229             :             {
    4230           0 :                 char *pszWKTExport = nullptr;
    4231           0 :                 CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID);
    4232           0 :                 oSRS.exportToWkt(&pszWKTExport);
    4233           0 :                 if (returnProjStr != nullptr)
    4234             :                 {
    4235           0 :                     (*returnProjStr) = std::string(pszWKTExport);
    4236             :                 }
    4237             :                 else
    4238             :                 {
    4239           0 :                     m_bAddedProjectionVarsDefs = true;
    4240           0 :                     m_bAddedProjectionVarsData = true;
    4241           0 :                     SetSpatialRefNoUpdate(&oSRS);
    4242             :                 }
    4243           0 :                 CPLFree(pszWKTExport);
    4244             :             }
    4245             :         }
    4246             :     }
    4247             : 
    4248         511 :     CPLFree(pszGridMappingValue);
    4249             : 
    4250         511 :     if (bReadSRSOnly)
    4251         166 :         return;
    4252             : 
    4253             :     // Determines the SRS to be used by the geolocation array, if any
    4254         690 :     std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG;
    4255         345 :     if (!m_oSRS.IsEmpty())
    4256             :     {
    4257         258 :         OGRSpatialReference oGeogCRS;
    4258         129 :         oGeogCRS.CopyGeogCSFrom(&m_oSRS);
    4259         129 :         char *pszWKTTmp = nullptr;
    4260         129 :         const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
    4261         129 :         if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE)
    4262             :         {
    4263         129 :             osGeolocWKT = pszWKTTmp;
    4264             :         }
    4265         129 :         CPLFree(pszWKTTmp);
    4266             :     }
    4267             : 
    4268             :     // Process geolocation arrays from CF "coordinates" attribute.
    4269         690 :     std::string osGeolocXName, osGeolocYName;
    4270         345 :     if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName,
    4271         345 :                              osGeolocYName))
    4272             :     {
    4273          52 :         bool bCanCancelGT = true;
    4274          52 :         if ((nVarDimXID != -1) && (nVarDimYID != -1))
    4275             :         {
    4276             :             char szVarNameX[NC_MAX_NAME + 1];
    4277          44 :             CPL_IGNORE_RET_VAL(
    4278          44 :                 nc_inq_varname(nGroupId, nVarDimXID, szVarNameX));
    4279             :             char szVarNameY[NC_MAX_NAME + 1];
    4280          44 :             CPL_IGNORE_RET_VAL(
    4281          44 :                 nc_inq_varname(nGroupId, nVarDimYID, szVarNameY));
    4282          44 :             bCanCancelGT =
    4283          44 :                 !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY);
    4284             :         }
    4285          86 :         if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() &&
    4286          34 :             !bSwitchedXY)
    4287             :         {
    4288          32 :             bGotCfGT = false;
    4289             :         }
    4290             :     }
    4291         119 :     else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) &&
    4292         415 :              (nVarDimYID != -1) && xdim > 0 && ydim > 0 &&
    4293           3 :              ((!bSwitchedXY &&
    4294           3 :                NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) &&
    4295           1 :                NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) ||
    4296           2 :               (bSwitchedXY &&
    4297           0 :                NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) &&
    4298           0 :                NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr))))
    4299             :     {
    4300             :         // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc
    4301             :         // which is indexed by lat, lon variables, but lat has irregular
    4302             :         // spacing.
    4303           1 :         const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID];
    4304           1 :         const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID];
    4305           1 :         if (bSwitchedXY)
    4306             :         {
    4307           0 :             std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4308           0 :             GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4309             :         }
    4310             : 
    4311           1 :         CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4312             :                  pszGeolocXFullName, pszGeolocYFullName);
    4313             : 
    4314           1 :         GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4315             :                                         "GEOLOCATION");
    4316             : 
    4317           2 :         CPLString osTMP;
    4318           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4319           1 :                      pszGeolocXFullName);
    4320             : 
    4321           1 :         GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4322           1 :         GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4323           1 :         osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4324           1 :                      pszGeolocYFullName);
    4325             : 
    4326           1 :         GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4327           1 :         GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4328             : 
    4329           1 :         GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4330           1 :         GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4331             : 
    4332           1 :         GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4333           1 :         GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4334             : 
    4335           1 :         GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4336             :                                         "PIXEL_CENTER", "GEOLOCATION");
    4337             :     }
    4338             : 
    4339             :     // Set GeoTransform if we got a complete one - after projection has been set
    4340         345 :     if (bGotCfGT || bGotGdalGT)
    4341             :     {
    4342         196 :         m_bAddedProjectionVarsDefs = true;
    4343         196 :         m_bAddedProjectionVarsData = true;
    4344         196 :         SetGeoTransformNoUpdate(adfTempGeoTransform);
    4345             :     }
    4346             : 
    4347             :     // Debugging reports.
    4348         345 :     CPLDebug("GDAL_netCDF",
    4349             :              "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d "
    4350             :              "bGotGdalSRS=%d bGotGdalGT=%d",
    4351             :              static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS),
    4352             :              static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS),
    4353             :              static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT));
    4354             : 
    4355         345 :     if (!bGotCfGT && !bGotGdalGT)
    4356         149 :         CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!");
    4357             : 
    4358         345 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS)
    4359         149 :         CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!");
    4360             : 
    4361             :     // wish of 6195
    4362             :     // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90
    4363         345 :     if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS)
    4364             :     {
    4365         216 :         if (bGotCfGT || bGotGdalGT)
    4366             :         {
    4367         134 :             bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef(
    4368          67 :                 papszOpenOptions, "ASSUME_LONGLAT",
    4369             :                 CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO")));
    4370             : 
    4371           2 :             if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 &&
    4372           2 :                 adfTempGeoTransform[0] < 360 &&
    4373           4 :                 (adfTempGeoTransform[0] +
    4374           2 :                  adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 &&
    4375          71 :                 adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 &&
    4376           4 :                 (adfTempGeoTransform[3] +
    4377           2 :                  adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90)
    4378             :             {
    4379             : 
    4380           2 :                 poDS->bIsGeographic = true;
    4381           2 :                 char *pszTempProjection = nullptr;
    4382             :                 // seems odd to use 4326 so OGC:CRS84
    4383           2 :                 oSRS.SetFromUserInput("OGC:CRS84");
    4384           2 :                 oSRS.exportToWkt(&pszTempProjection);
    4385           2 :                 if (returnProjStr != nullptr)
    4386             :                 {
    4387           0 :                     (*returnProjStr) = std::string(pszTempProjection);
    4388             :                 }
    4389             :                 else
    4390             :                 {
    4391           2 :                     m_bAddedProjectionVarsDefs = true;
    4392           2 :                     m_bAddedProjectionVarsData = true;
    4393           2 :                     SetSpatialRefNoUpdate(&oSRS);
    4394             :                 }
    4395           2 :                 CPLFree(pszTempProjection);
    4396             : 
    4397           2 :                 CPLDebug("netCDF",
    4398             :                          "Assumed Longitude Latitude CRS 'OGC:CRS84' because "
    4399             :                          "none otherwise available and geotransform within "
    4400             :                          "suitable bounds. "
    4401             :                          "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration "
    4402             :                          "option or "
    4403             :                          "    ASSUME_LONGLAT=NO as open option to bypass this "
    4404             :                          "assumption.");
    4405             :             }
    4406             :         }
    4407             :     }
    4408             : 
    4409             : // Search for Well-known GeogCS if got only CF WKT
    4410             : // Disabled for now, as a named datum also include control points
    4411             : // (see mailing list and bug#4281
    4412             : // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py
    4413             : 
    4414             : // Disabled for now, but could be set in a config option.
    4415             : #if 0
    4416             :     bool bLookForWellKnownGCS = false;  // This could be a Config Option.
    4417             : 
    4418             :     if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS )
    4419             :     {
    4420             :         // ET - Could use a more exhaustive method by scanning all EPSG codes in
    4421             :         // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help
    4422             :         // for comparing two WKT".
    4423             :         // This code could be contributed to a new function.
    4424             :         // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS(
    4425             :         //     const OGRSpatialReference *poOther) */
    4426             :         CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS");
    4427             :         const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" };
    4428             :         char *pszWKGCS = NULL;
    4429             :         oSRS.exportToPrettyWkt(&pszWKGCS);
    4430             :         for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ )
    4431             :         {
    4432             :             pszWKGCS = CPLStrdup(pszWKGCSList[i]);
    4433             :             OGRSpatialReference oSRSTmp;
    4434             :             oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]);
    4435             :             // Set datum to unknown, bug #4281.
    4436             :             if( oSRSTmp.GetAttrNode("DATUM" ) )
    4437             :                 oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown");
    4438             :             // Could use OGRSpatialReference::StripCTParms(), but let's keep
    4439             :             // TOWGS84.
    4440             :             oSRSTmp.GetRoot()->StripNodes("AXIS");
    4441             :             oSRSTmp.GetRoot()->StripNodes("AUTHORITY");
    4442             :             oSRSTmp.GetRoot()->StripNodes("EXTENSION");
    4443             : 
    4444             :             oSRSTmp.exportToPrettyWkt(&pszWKGCS);
    4445             :             if( oSRS.IsSameGeogCS(&oSRSTmp) )
    4446             :             {
    4447             :                 oSRS.SetWellKnownGeogCS(pszWKGCSList[i]);
    4448             :                 oSRS.exportToWkt(&(pszTempProjection));
    4449             :                 SetProjection(pszTempProjection);
    4450             :                 CPLFree(pszTempProjection);
    4451             :             }
    4452             :         }
    4453             :     }
    4454             : #endif
    4455             : }
    4456             : 
    4457         123 : void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId,
    4458             :                                          bool bReadSRSOnly)
    4459             : {
    4460         123 :     SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr,
    4461             :                          nullptr, nullptr);
    4462         123 : }
    4463             : 
    4464         282 : bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId)
    4465             : {
    4466             :     // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/
    4467             :     // and https://github.com/OSGeo/gdal/issues/7605
    4468             : 
    4469             :     // Check for a structure like:
    4470             :     /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT {
    4471             :         dimensions:
    4472             :             number_of_lines = 3248 ;
    4473             :             pixels_per_line = 3200 ;
    4474             :             [...]
    4475             :             pixel_control_points = 3200 ;
    4476             :         [...]
    4477             :         group: geophysical_data {
    4478             :           variables:
    4479             :             short aot_862(number_of_lines, pixels_per_line) ;  <-- nVarId
    4480             :                 [...]
    4481             :         }
    4482             :         group: navigation_data {
    4483             :           variables:
    4484             :             float longitude(number_of_lines, pixel_control_points) ;
    4485             :                 [...]
    4486             :             float latitude(number_of_lines, pixel_control_points) ;
    4487             :                 [...]
    4488             :         }
    4489             :     }
    4490             :     */
    4491             :     // Note that the longitude and latitude arrays are not indexed by the
    4492             :     // same dimensions. Handle only the case where
    4493             :     // pixel_control_points == pixels_per_line
    4494             :     // If there was a subsampling of the geolocation arrays, we'd need to
    4495             :     // add more logic.
    4496             : 
    4497         564 :     std::string osGroupName;
    4498         282 :     osGroupName.resize(NC_MAX_NAME);
    4499         282 :     NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0]));
    4500         282 :     osGroupName.resize(strlen(osGroupName.data()));
    4501         282 :     if (osGroupName != "geophysical_data")
    4502         281 :         return false;
    4503             : 
    4504           1 :     int nVarDims = 0;
    4505           1 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4506           1 :     if (nVarDims != 2)
    4507           0 :         return false;
    4508             : 
    4509           1 :     int nNavigationDataGrpId = 0;
    4510           1 :     if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) !=
    4511             :         NC_NOERR)
    4512           0 :         return false;
    4513             : 
    4514             :     std::array<int, 2> anVarDimIds;
    4515           1 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4516             : 
    4517           1 :     int nLongitudeId = 0;
    4518           1 :     int nLatitudeId = 0;
    4519           1 :     if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) !=
    4520           2 :             NC_NOERR ||
    4521           1 :         nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) !=
    4522             :             NC_NOERR)
    4523             :     {
    4524           0 :         return false;
    4525             :     }
    4526             : 
    4527           1 :     int nDimsLongitude = 0;
    4528           1 :     NCDF_ERR(
    4529             :         nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude));
    4530           1 :     int nDimsLatitude = 0;
    4531           1 :     NCDF_ERR(
    4532             :         nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude));
    4533           1 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4534             :     {
    4535           0 :         return false;
    4536             :     }
    4537             : 
    4538             :     std::array<int, 2> anDimLongitudeIds;
    4539           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId,
    4540             :                              anDimLongitudeIds.data()));
    4541             :     std::array<int, 2> anDimLatitudeIds;
    4542           1 :     NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId,
    4543             :                              anDimLatitudeIds.data()));
    4544           1 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4545             :     {
    4546           0 :         return false;
    4547             :     }
    4548             : 
    4549             :     std::array<size_t, 2> anSizeVarDimIds;
    4550             :     std::array<size_t, 2> anSizeLongLatIds;
    4551           2 :     if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) ==
    4552           1 :               NC_NOERR &&
    4553           1 :           nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) ==
    4554           1 :               NC_NOERR &&
    4555           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) ==
    4556           1 :               NC_NOERR &&
    4557           1 :           nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) ==
    4558             :               NC_NOERR &&
    4559           1 :           anSizeVarDimIds == anSizeLongLatIds))
    4560             :     {
    4561           0 :         return false;
    4562             :     }
    4563             : 
    4564           1 :     const char *pszGeolocXFullName = "/navigation_data/longitude";
    4565           1 :     const char *pszGeolocYFullName = "/navigation_data/latitude";
    4566             : 
    4567           1 :     if (bSwitchedXY)
    4568             :     {
    4569           0 :         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4570           0 :         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION");
    4571             :     }
    4572             : 
    4573           1 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4574             :              pszGeolocXFullName, pszGeolocYFullName);
    4575             : 
    4576           1 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4577             :                                     "GEOLOCATION");
    4578             : 
    4579           1 :     CPLString osTMP;
    4580           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4581             : 
    4582           1 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4583           1 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4584           1 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4585             : 
    4586           1 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4587           1 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4588             : 
    4589           1 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4590           1 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4591             : 
    4592           1 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4593           1 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4594             : 
    4595           1 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4596             :                                     "GEOLOCATION");
    4597           1 :     return true;
    4598             : }
    4599             : 
    4600         281 : bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId)
    4601             : {
    4602             :     // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/
    4603             : 
    4604             :     // Check for a structure like:
    4605             :     /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 {
    4606             :         dimensions:
    4607             :             downtrack = 1280 ;
    4608             :             crosstrack = 1242 ;
    4609             :             bands = 285 ;
    4610             :             [...]
    4611             : 
    4612             :         variables:
    4613             :             float reflectance(downtrack, crosstrack, bands) ;
    4614             : 
    4615             :         group: location {
    4616             :           variables:
    4617             :                 double lon(downtrack, crosstrack) ;
    4618             :                         lon:_FillValue = -9999. ;
    4619             :                         lon:long_name = "Longitude (WGS-84)" ;
    4620             :                         lon:units = "degrees east" ;
    4621             :                 double lat(downtrack, crosstrack) ;
    4622             :                         lat:_FillValue = -9999. ;
    4623             :                         lat:long_name = "Latitude (WGS-84)" ;
    4624             :                         lat:units = "degrees north" ;
    4625             :           } // group location
    4626             : 
    4627             :     }
    4628             :     or
    4629             :     netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 {
    4630             :         dimensions:
    4631             :                 downtrack = 1664 ;
    4632             :                 crosstrack = 1242 ;
    4633             :                 [...]
    4634             :         variables:
    4635             :                 float group_1_band_depth(downtrack, crosstrack) ;
    4636             :                         group_1_band_depth:_FillValue = -9999.f ;
    4637             :                         group_1_band_depth:long_name = "Group 1 Band Depth" ;
    4638             :                         group_1_band_depth:units = "unitless" ;
    4639             :                 [...]
    4640             :         group: location {
    4641             :           variables:
    4642             :                 double lon(downtrack, crosstrack) ;
    4643             :                         lon:_FillValue = -9999. ;
    4644             :                         lon:long_name = "Longitude (WGS-84)" ;
    4645             :                         lon:units = "degrees east" ;
    4646             :                 double lat(downtrack, crosstrack) ;
    4647             :                         lat:_FillValue = -9999. ;
    4648             :                         lat:long_name = "Latitude (WGS-84)" ;
    4649             :                         lat:units = "degrees north" ;
    4650             :         }
    4651             :     */
    4652             : 
    4653         281 :     int nVarDims = 0;
    4654         281 :     NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4655         281 :     if (nVarDims != 2 && nVarDims != 3)
    4656          14 :         return false;
    4657             : 
    4658         267 :     int nLocationGrpId = 0;
    4659         267 :     if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR)
    4660          57 :         return false;
    4661             : 
    4662             :     std::array<int, 3> anVarDimIds;
    4663         210 :     NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4664         210 :     if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1])
    4665          21 :         return false;
    4666             : 
    4667         189 :     int nLongitudeId = 0;
    4668         189 :     int nLatitudeId = 0;
    4669         226 :     if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR ||
    4670          37 :         nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR)
    4671             :     {
    4672         152 :         return false;
    4673             :     }
    4674             : 
    4675          37 :     int nDimsLongitude = 0;
    4676          37 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude));
    4677          37 :     int nDimsLatitude = 0;
    4678          37 :     NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude));
    4679          37 :     if (!(nDimsLongitude == 2 && nDimsLatitude == 2))
    4680             :     {
    4681          33 :         return false;
    4682             :     }
    4683             : 
    4684             :     std::array<int, 2> anDimLongitudeIds;
    4685           4 :     NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId,
    4686             :                              anDimLongitudeIds.data()));
    4687             :     std::array<int, 2> anDimLatitudeIds;
    4688           4 :     NCDF_ERR(
    4689             :         nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data()));
    4690           4 :     if (anDimLongitudeIds != anDimLatitudeIds)
    4691             :     {
    4692           0 :         return false;
    4693             :     }
    4694             : 
    4695           8 :     if (anDimLongitudeIds[0] != anVarDimIds[0] ||
    4696           4 :         anDimLongitudeIds[1] != anVarDimIds[1])
    4697             :     {
    4698           0 :         return false;
    4699             :     }
    4700             : 
    4701           4 :     const char *pszGeolocXFullName = "/location/lon";
    4702           4 :     const char *pszGeolocYFullName = "/location/lat";
    4703             : 
    4704           4 :     CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION",
    4705             :              pszGeolocXFullName, pszGeolocYFullName);
    4706             : 
    4707           4 :     GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    4708             :                                     "GEOLOCATION");
    4709             : 
    4710           4 :     CPLString osTMP;
    4711           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName);
    4712             : 
    4713           4 :     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION");
    4714           4 :     GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    4715           4 :     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName);
    4716             : 
    4717           4 :     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION");
    4718           4 :     GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    4719             : 
    4720           4 :     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION");
    4721           4 :     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION");
    4722             : 
    4723           4 :     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION");
    4724           4 :     GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION");
    4725             : 
    4726           4 :     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER",
    4727             :                                     "GEOLOCATION");
    4728           4 :     return true;
    4729             : }
    4730             : 
    4731         345 : int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId,
    4732             :                                         const std::string &osGeolocWKT,
    4733             :                                         std::string &osGeolocXNameOut,
    4734             :                                         std::string &osGeolocYNameOut)
    4735             : {
    4736         345 :     bool bAddGeoloc = false;
    4737         345 :     char *pszCoordinates = nullptr;
    4738             : 
    4739             :     // If there is no explicit "coordinates" attribute, check if there are
    4740             :     // "lon" and "lat" 2D variables whose dimensions are the last
    4741             :     // 2 ones of the variable of interest.
    4742         345 :     if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) !=
    4743             :         CE_None)
    4744             :     {
    4745         299 :         CPLFree(pszCoordinates);
    4746         299 :         pszCoordinates = nullptr;
    4747             : 
    4748         299 :         int nVarDims = 0;
    4749         299 :         NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims));
    4750         299 :         if (nVarDims >= 2)
    4751             :         {
    4752         598 :             std::vector<int> anVarDimIds(nVarDims);
    4753         299 :             NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data()));
    4754             : 
    4755         299 :             int nLongitudeId = 0;
    4756         299 :             int nLatitudeId = 0;
    4757         366 :             if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR &&
    4758          67 :                 nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR)
    4759             :             {
    4760          67 :                 int nDimsLongitude = 0;
    4761          67 :                 NCDF_ERR(
    4762             :                     nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude));
    4763          67 :                 int nDimsLatitude = 0;
    4764          67 :                 NCDF_ERR(
    4765             :                     nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude));
    4766          67 :                 if (nDimsLongitude == 2 && nDimsLatitude == 2)
    4767             :                 {
    4768          34 :                     std::vector<int> anDimLongitudeIds(2);
    4769          17 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId,
    4770             :                                              anDimLongitudeIds.data()));
    4771          34 :                     std::vector<int> anDimLatitudeIds(2);
    4772          17 :                     NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId,
    4773             :                                              anDimLatitudeIds.data()));
    4774          17 :                     if (anDimLongitudeIds == anDimLatitudeIds &&
    4775          34 :                         anVarDimIds[anVarDimIds.size() - 2] ==
    4776          51 :                             anDimLongitudeIds[0] &&
    4777          34 :                         anVarDimIds[anVarDimIds.size() - 1] ==
    4778          17 :                             anDimLongitudeIds[1])
    4779             :                     {
    4780          17 :                         pszCoordinates = CPLStrdup("lon lat");
    4781             :                     }
    4782             :                 }
    4783             :             }
    4784             :         }
    4785             :     }
    4786             : 
    4787         345 :     if (pszCoordinates)
    4788             :     {
    4789             :         // Get X and Y geolocation names from coordinates attribute.
    4790             :         const CPLStringList aosCoordinates(
    4791         126 :             NCDFTokenizeCoordinatesAttribute(pszCoordinates));
    4792          63 :         if (aosCoordinates.size() >= 2)
    4793             :         {
    4794             :             char szGeolocXName[NC_MAX_NAME + 1];
    4795             :             char szGeolocYName[NC_MAX_NAME + 1];
    4796          60 :             szGeolocXName[0] = '\0';
    4797          60 :             szGeolocYName[0] = '\0';
    4798             : 
    4799             :             // Test that each variable is longitude/latitude.
    4800         193 :             for (int i = 0; i < aosCoordinates.size(); i++)
    4801             :             {
    4802         133 :                 if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i]))
    4803             :                 {
    4804          49 :                     int nOtherGroupId = -1;
    4805          49 :                     int nOtherVarId = -1;
    4806             :                     // Check that the variable actually exists
    4807             :                     // Needed on Sentinel-3 products
    4808          49 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4809          49 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4810             :                     {
    4811          47 :                         snprintf(szGeolocXName, sizeof(szGeolocXName), "%s",
    4812             :                                  aosCoordinates[i]);
    4813             :                     }
    4814             :                 }
    4815          84 :                 else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i]))
    4816             :                 {
    4817          49 :                     int nOtherGroupId = -1;
    4818          49 :                     int nOtherVarId = -1;
    4819             :                     // Check that the variable actually exists
    4820             :                     // Needed on Sentinel-3 products
    4821          49 :                     if (NCDFResolveVar(nGroupId, aosCoordinates[i],
    4822          49 :                                        &nOtherGroupId, &nOtherVarId) == CE_None)
    4823             :                     {
    4824          47 :                         snprintf(szGeolocYName, sizeof(szGeolocYName), "%s",
    4825             :                                  aosCoordinates[i]);
    4826             :                     }
    4827             :                 }
    4828             :             }
    4829             :             // Add GEOLOCATION metadata.
    4830          60 :             if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, ""))
    4831             :             {
    4832          47 :                 osGeolocXNameOut = szGeolocXName;
    4833          47 :                 osGeolocYNameOut = szGeolocYName;
    4834             : 
    4835          47 :                 char *pszGeolocXFullName = nullptr;
    4836          47 :                 char *pszGeolocYFullName = nullptr;
    4837          47 :                 if (NCDFResolveVarFullName(nGroupId, szGeolocXName,
    4838          94 :                                            &pszGeolocXFullName) == CE_None &&
    4839          47 :                     NCDFResolveVarFullName(nGroupId, szGeolocYName,
    4840             :                                            &pszGeolocYFullName) == CE_None)
    4841             :                 {
    4842          47 :                     if (bSwitchedXY)
    4843             :                     {
    4844           2 :                         std::swap(pszGeolocXFullName, pszGeolocYFullName);
    4845           2 :                         GDALPamDataset::SetMetadataItem("SWAP_XY", "YES",
    4846             :                                                         "GEOLOCATION");
    4847             :                     }
    4848             : 
    4849          47 :                     bAddGeoloc = true;
    4850          47 :                     CPLDebug("GDAL_netCDF",
    4851             :                              "using variables %s and %s for GEOLOCATION",
    4852             :                              pszGeolocXFullName, pszGeolocYFullName);
    4853             : 
    4854          47 :                     GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(),
    4855             :                                                     "GEOLOCATION");
    4856             : 
    4857          94 :                     CPLString osTMP;
    4858          47 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4859          47 :                                  pszGeolocXFullName);
    4860             : 
    4861          47 :                     GDALPamDataset::SetMetadataItem("X_DATASET", osTMP,
    4862             :                                                     "GEOLOCATION");
    4863          47 :                     GDALPamDataset::SetMetadataItem("X_BAND", "1",
    4864             :                                                     "GEOLOCATION");
    4865          47 :                     osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(),
    4866          47 :                                  pszGeolocYFullName);
    4867             : 
    4868          47 :                     GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP,
    4869             :                                                     "GEOLOCATION");
    4870          47 :                     GDALPamDataset::SetMetadataItem("Y_BAND", "1",
    4871             :                                                     "GEOLOCATION");
    4872             : 
    4873          47 :                     GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0",
    4874             :                                                     "GEOLOCATION");
    4875          47 :                     GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1",
    4876             :                                                     "GEOLOCATION");
    4877             : 
    4878          47 :                     GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0",
    4879             :                                                     "GEOLOCATION");
    4880          47 :                     GDALPamDataset::SetMetadataItem("LINE_STEP", "1",
    4881             :                                                     "GEOLOCATION");
    4882             : 
    4883          47 :                     GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION",
    4884             :                                                     "PIXEL_CENTER",
    4885             :                                                     "GEOLOCATION");
    4886             :                 }
    4887             :                 else
    4888             :                 {
    4889           0 :                     CPLDebug("GDAL_netCDF",
    4890             :                              "cannot resolve location of "
    4891             :                              "lat/lon variables specified by the coordinates "
    4892             :                              "attribute [%s]",
    4893             :                              pszCoordinates);
    4894             :                 }
    4895          47 :                 CPLFree(pszGeolocXFullName);
    4896          47 :                 CPLFree(pszGeolocYFullName);
    4897             :             }
    4898             :             else
    4899             :             {
    4900          13 :                 CPLDebug("GDAL_netCDF",
    4901             :                          "coordinates attribute [%s] is unsupported",
    4902             :                          pszCoordinates);
    4903             :             }
    4904             :         }
    4905             :         else
    4906             :         {
    4907           3 :             CPLDebug("GDAL_netCDF",
    4908             :                      "coordinates attribute [%s] with %d element(s) is "
    4909             :                      "unsupported",
    4910             :                      pszCoordinates, aosCoordinates.size());
    4911             :         }
    4912             :     }
    4913             : 
    4914             :     else
    4915             :     {
    4916         282 :         bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId);
    4917             : 
    4918         282 :         if (!bAddGeoloc)
    4919         281 :             bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId);
    4920             :     }
    4921             : 
    4922         345 :     CPLFree(pszCoordinates);
    4923             : 
    4924         345 :     return bAddGeoloc;
    4925             : }
    4926             : 
    4927           8 : CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId,
    4928             :                                        const char *szDimName)
    4929             : {
    4930             :     // Get values.
    4931           8 :     char *pszVarValues = nullptr;
    4932           8 :     CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues);
    4933           8 :     if (eErr != CE_None)
    4934           0 :         return eErr;
    4935             : 
    4936             :     // Write metadata.
    4937           8 :     char szTemp[NC_MAX_NAME + 1 + 32] = {};
    4938           8 :     snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName);
    4939           8 :     GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2");
    4940             : 
    4941           8 :     CPLFree(pszVarValues);
    4942             : 
    4943           8 :     return CE_None;
    4944             : }
    4945             : 
    4946           0 : double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName,
    4947             :                                         int &nVarLen)
    4948             : {
    4949           0 :     nVarLen = 0;
    4950             : 
    4951             :     // Get Y_VALUES as tokens.
    4952             :     char **papszValues =
    4953           0 :         NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2"));
    4954           0 :     if (papszValues == nullptr)
    4955           0 :         return nullptr;
    4956             : 
    4957             :     // Initialize and fill array.
    4958           0 :     nVarLen = CSLCount(papszValues);
    4959             :     double *pdfVarValues =
    4960           0 :         static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
    4961             : 
    4962           0 :     for (int i = 0, j = 0; i < nVarLen; i++)
    4963             :     {
    4964           0 :         if (!bBottomUp)
    4965           0 :             j = nVarLen - 1 - i;
    4966             :         else
    4967           0 :             j = i;  // Invert latitude values.
    4968           0 :         char *pszTemp = nullptr;
    4969           0 :         pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp);
    4970             :     }
    4971           0 :     CSLDestroy(papszValues);
    4972             : 
    4973           0 :     return pdfVarValues;
    4974             : }
    4975             : 
    4976             : /************************************************************************/
    4977             : /*                        SetSpatialRefNoUpdate()                       */
    4978             : /************************************************************************/
    4979             : 
    4980         252 : void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS)
    4981             : {
    4982         252 :     m_oSRS.Clear();
    4983         252 :     if (poSRS)
    4984         245 :         m_oSRS = *poSRS;
    4985         252 :     m_bHasProjection = true;
    4986         252 : }
    4987             : 
    4988             : /************************************************************************/
    4989             : /*                          SetSpatialRef()                             */
    4990             : /************************************************************************/
    4991             : 
    4992          75 : CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    4993             : {
    4994         150 :     CPLMutexHolderD(&hNCMutex);
    4995             : 
    4996          75 :     if (GetAccess() != GA_Update || m_bHasProjection)
    4997             :     {
    4998           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4999             :                  "netCDFDataset::_SetProjection() should only be called once "
    5000             :                  "in update mode!");
    5001           0 :         return CE_Failure;
    5002             :     }
    5003             : 
    5004          75 :     if (m_bHasGeoTransform)
    5005             :     {
    5006          32 :         SetSpatialRefNoUpdate(poSRS);
    5007             : 
    5008             :         // For NC4/NC4C, writing both projection variables and data,
    5009             :         // followed by redefining nodata value, cancels the projection
    5010             :         // info from the Band variable, so for now only write the
    5011             :         // variable definitions, and write data at the end.
    5012             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5013          32 :         return AddProjectionVars(true, nullptr, nullptr);
    5014             :     }
    5015             : 
    5016          43 :     SetSpatialRefNoUpdate(poSRS);
    5017             : 
    5018          43 :     return CE_None;
    5019             : }
    5020             : 
    5021             : /************************************************************************/
    5022             : /*                     SetGeoTransformNoUpdate()                        */
    5023             : /************************************************************************/
    5024             : 
    5025         272 : void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform)
    5026             : {
    5027         272 :     memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6);
    5028         272 :     m_bHasGeoTransform = true;
    5029         272 : }
    5030             : 
    5031             : /************************************************************************/
    5032             : /*                          SetGeoTransform()                           */
    5033             : /************************************************************************/
    5034             : 
    5035          76 : CPLErr netCDFDataset::SetGeoTransform(double *padfTransform)
    5036             : {
    5037         152 :     CPLMutexHolderD(&hNCMutex);
    5038             : 
    5039          76 :     if (GetAccess() != GA_Update || m_bHasGeoTransform)
    5040             :     {
    5041           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    5042             :                  "netCDFDataset::SetGeoTransform() should only be called once "
    5043             :                  "in update mode!");
    5044           0 :         return CE_Failure;
    5045             :     }
    5046             : 
    5047          76 :     CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)",
    5048          76 :              padfTransform[0], padfTransform[1], padfTransform[2],
    5049          76 :              padfTransform[3], padfTransform[4], padfTransform[5]);
    5050             : 
    5051          76 :     if (m_bHasProjection)
    5052             :     {
    5053           3 :         SetGeoTransformNoUpdate(padfTransform);
    5054             : 
    5055             :         // For NC4/NC4C, writing both projection variables and data,
    5056             :         // followed by redefining nodata value, cancels the projection
    5057             :         // info from the Band variable, so for now only write the
    5058             :         // variable definitions, and write data at the end.
    5059             :         // See https://trac.osgeo.org/gdal/ticket/7245
    5060           3 :         return AddProjectionVars(true, nullptr, nullptr);
    5061             :     }
    5062             : 
    5063          73 :     SetGeoTransformNoUpdate(padfTransform);
    5064          73 :     return CE_None;
    5065             : }
    5066             : 
    5067             : /************************************************************************/
    5068             : /*                         NCDFWriteSRSVariable()                       */
    5069             : /************************************************************************/
    5070             : 
    5071         127 : int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS,
    5072             :                          char **ppszCFProjection, bool bWriteGDALTags,
    5073             :                          const std::string &srsVarName)
    5074             : {
    5075         127 :     char *pszCFProjection = nullptr;
    5076         127 :     char **papszKeyValues = nullptr;
    5077         127 :     poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr);
    5078             : 
    5079         127 :     if (bWriteGDALTags)
    5080             :     {
    5081         126 :         const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT);
    5082         126 :         if (pszWKT)
    5083             :         {
    5084             :             // SPATIAL_REF is deprecated. Will be removed in GDAL 4.
    5085         126 :             papszKeyValues =
    5086         126 :                 CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT);
    5087             :         }
    5088             :     }
    5089             : 
    5090         127 :     const int nValues = CSLCount(papszKeyValues);
    5091             : 
    5092             :     int NCDFVarID;
    5093         254 :     std::string varNameRadix(pszCFProjection);
    5094         127 :     int nCounter = 2;
    5095             :     while (true)
    5096             :     {
    5097         129 :         NCDFVarID = -1;
    5098         129 :         nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID);
    5099         129 :         if (NCDFVarID < 0)
    5100         124 :             break;
    5101             : 
    5102           5 :         int nbAttr = 0;
    5103           5 :         NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr));
    5104           5 :         bool bSame = nbAttr == nValues;
    5105          41 :         for (int i = 0; bSame && (i < nbAttr); i++)
    5106             :         {
    5107             :             char szAttrName[NC_MAX_NAME + 1];
    5108          38 :             szAttrName[0] = 0;
    5109          38 :             NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName));
    5110             : 
    5111             :             const char *pszValue =
    5112          38 :                 CSLFetchNameValue(papszKeyValues, szAttrName);
    5113          38 :             if (!pszValue)
    5114             :             {
    5115           0 :                 bSame = false;
    5116           2 :                 break;
    5117             :             }
    5118             : 
    5119          38 :             nc_type atttype = NC_NAT;
    5120          38 :             size_t attlen = 0;
    5121          38 :             NCDF_ERR(
    5122             :                 nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen));
    5123          38 :             if (atttype != NC_CHAR && atttype != NC_DOUBLE)
    5124             :             {
    5125           0 :                 bSame = false;
    5126           0 :                 break;
    5127             :             }
    5128          38 :             if (atttype == NC_CHAR)
    5129             :             {
    5130          15 :                 if (CPLGetValueType(pszValue) != CPL_VALUE_STRING)
    5131             :                 {
    5132           0 :                     bSame = false;
    5133           0 :                     break;
    5134             :                 }
    5135          15 :                 std::string val;
    5136          15 :                 val.resize(attlen);
    5137          15 :                 nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]);
    5138          15 :                 if (val != pszValue)
    5139             :                 {
    5140           0 :                     bSame = false;
    5141           0 :                     break;
    5142             :                 }
    5143             :             }
    5144             :             else
    5145             :             {
    5146             :                 const CPLStringList aosTokens(
    5147          23 :                     CSLTokenizeString2(pszValue, ",", 0));
    5148          23 :                 if (static_cast<size_t>(aosTokens.size()) != attlen)
    5149             :                 {
    5150           0 :                     bSame = false;
    5151           0 :                     break;
    5152             :                 }
    5153             :                 double vals[2];
    5154          23 :                 nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals);
    5155          44 :                 if (vals[0] != CPLAtof(aosTokens[0]) ||
    5156          21 :                     (attlen == 2 && vals[1] != CPLAtof(aosTokens[1])))
    5157             :                 {
    5158           2 :                     bSame = false;
    5159           2 :                     break;
    5160             :                 }
    5161             :             }
    5162             :         }
    5163           5 :         if (bSame)
    5164             :         {
    5165           3 :             *ppszCFProjection = pszCFProjection;
    5166           3 :             CSLDestroy(papszKeyValues);
    5167           3 :             return NCDFVarID;
    5168             :         }
    5169           2 :         CPLFree(pszCFProjection);
    5170           2 :         pszCFProjection =
    5171           2 :             CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter));
    5172           2 :         nCounter++;
    5173           2 :     }
    5174             : 
    5175         124 :     *ppszCFProjection = pszCFProjection;
    5176             : 
    5177             :     const char *pszVarName;
    5178             : 
    5179         124 :     if (srsVarName != "")
    5180             :     {
    5181          38 :         pszVarName = srsVarName.c_str();
    5182             :     }
    5183             :     else
    5184             :     {
    5185          86 :         pszVarName = pszCFProjection;
    5186             :     }
    5187             : 
    5188         124 :     int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID);
    5189         124 :     NCDF_ERR(status);
    5190        1191 :     for (int i = 0; i < nValues; ++i)
    5191             :     {
    5192        1067 :         char *pszKey = nullptr;
    5193        1067 :         const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey);
    5194        1067 :         if (pszKey && pszValue)
    5195             :         {
    5196        2134 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
    5197        1067 :             double adfValues[2] = {0, 0};
    5198        1067 :             const int nDoubleCount = std::min(2, aosTokens.size());
    5199        1067 :             if (!(aosTokens.size() == 2 &&
    5200        2133 :                   CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) &&
    5201        1066 :                 CPLGetValueType(pszValue) == CPL_VALUE_STRING)
    5202             :             {
    5203         495 :                 status = nc_put_att_text(cdfid, NCDFVarID, pszKey,
    5204             :                                          strlen(pszValue), pszValue);
    5205             :             }
    5206             :             else
    5207             :             {
    5208        1145 :                 for (int j = 0; j < nDoubleCount; ++j)
    5209         573 :                     adfValues[j] = CPLAtof(aosTokens[j]);
    5210         572 :                 status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE,
    5211             :                                            nDoubleCount, adfValues);
    5212             :             }
    5213        1067 :             NCDF_ERR(status);
    5214             :         }
    5215        1067 :         CPLFree(pszKey);
    5216             :     }
    5217             : 
    5218         124 :     CSLDestroy(papszKeyValues);
    5219         124 :     return NCDFVarID;
    5220             : }
    5221             : 
    5222             : /************************************************************************/
    5223             : /*                   NCDFWriteLonLatVarsAttributes()                    */
    5224             : /************************************************************************/
    5225             : 
    5226         100 : void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID,
    5227             :                                    int nVarLatID)
    5228             : {
    5229             : 
    5230             :     try
    5231             :     {
    5232         100 :         vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME);
    5233         100 :         vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME);
    5234         100 :         vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH);
    5235         100 :         vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME);
    5236         100 :         vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME);
    5237         100 :         vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST);
    5238             :     }
    5239           0 :     catch (nccfdriver::SG_Exception &e)
    5240             :     {
    5241           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5242             :     }
    5243         100 : }
    5244             : 
    5245             : /************************************************************************/
    5246             : /*                   NCDFWriteRLonRLatVarsAttributes()                    */
    5247             : /************************************************************************/
    5248             : 
    5249           0 : void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf,
    5250             :                                      int nVarRLonID, int nVarRLatID)
    5251             : {
    5252             :     try
    5253             :     {
    5254           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude");
    5255           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME,
    5256             :                               "latitude in rotated pole grid");
    5257           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees");
    5258           0 :         vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y");
    5259             : 
    5260           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude");
    5261           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME,
    5262             :                               "longitude in rotated pole grid");
    5263           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees");
    5264           0 :         vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X");
    5265             :     }
    5266           0 :     catch (nccfdriver::SG_Exception &e)
    5267             :     {
    5268           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5269             :     }
    5270           0 : }
    5271             : 
    5272             : /************************************************************************/
    5273             : /*                        NCDFGetProjectedCFUnit()                      */
    5274             : /************************************************************************/
    5275             : 
    5276          40 : std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS)
    5277             : {
    5278          40 :     char *pszUnitsToWrite = nullptr;
    5279          40 :     poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr);
    5280          40 :     const std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string();
    5281          40 :     CPLFree(pszUnitsToWrite);
    5282          80 :     return osRet;
    5283             : }
    5284             : 
    5285             : /************************************************************************/
    5286             : /*                     NCDFWriteXYVarsAttributes()                      */
    5287             : /************************************************************************/
    5288             : 
    5289          29 : void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID,
    5290             :                                int nVarYID, const OGRSpatialReference *poSRS)
    5291             : {
    5292          58 :     const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS);
    5293             : 
    5294             :     try
    5295             :     {
    5296          29 :         vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD);
    5297          29 :         vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME);
    5298          29 :         if (!osUnitsToWrite.empty())
    5299          29 :             vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str());
    5300          29 :         vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD);
    5301          29 :         vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME);
    5302          29 :         if (!osUnitsToWrite.empty())
    5303          29 :             vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str());
    5304             :     }
    5305           0 :     catch (nccfdriver::SG_Exception &e)
    5306             :     {
    5307           0 :         CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5308             :     }
    5309          29 : }
    5310             : 
    5311             : /************************************************************************/
    5312             : /*                          AddProjectionVars()                         */
    5313             : /************************************************************************/
    5314             : 
    5315         162 : CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly,
    5316             :                                         GDALProgressFunc pfnProgress,
    5317             :                                         void *pProgressData)
    5318             : {
    5319         162 :     if (nCFVersion >= 1.8)
    5320           0 :         return CE_None;  // do nothing
    5321             : 
    5322         162 :     bool bWriteGridMapping = false;
    5323         162 :     bool bWriteLonLat = false;
    5324         162 :     bool bHasGeoloc = false;
    5325         162 :     bool bWriteGDALTags = false;
    5326         162 :     bool bWriteGeoTransform = false;
    5327             : 
    5328             :     // For GEOLOCATION information.
    5329         162 :     GDALDatasetH hDS_X = nullptr;
    5330         162 :     GDALRasterBandH hBand_X = nullptr;
    5331         162 :     GDALDatasetH hDS_Y = nullptr;
    5332         162 :     GDALRasterBandH hBand_Y = nullptr;
    5333             : 
    5334         324 :     OGRSpatialReference oSRS(m_oSRS);
    5335         162 :     if (!m_oSRS.IsEmpty())
    5336             :     {
    5337         136 :         if (oSRS.IsProjected())
    5338          50 :             bIsProjected = true;
    5339          86 :         else if (oSRS.IsGeographic())
    5340          86 :             bIsGeographic = true;
    5341             :     }
    5342             : 
    5343         162 :     if (bDefsOnly)
    5344             :     {
    5345          81 :         char *pszProjection = nullptr;
    5346          81 :         m_oSRS.exportToWkt(&pszProjection);
    5347          81 :         CPLDebug("GDAL_netCDF",
    5348             :                  "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d",
    5349          81 :                  pszProjection ? pszProjection : "(null)",
    5350          81 :                  static_cast<int>(bIsProjected),
    5351          81 :                  static_cast<int>(bIsGeographic));
    5352          81 :         CPLFree(pszProjection);
    5353             : 
    5354          81 :         if (!m_bHasGeoTransform)
    5355           5 :             CPLDebug("GDAL_netCDF",
    5356             :                      "netCDFDataset::AddProjectionVars() called, "
    5357             :                      "but GeoTransform has not yet been defined!");
    5358             : 
    5359          81 :         if (!m_bHasProjection)
    5360           6 :             CPLDebug("GDAL_netCDF",
    5361             :                      "netCDFDataset::AddProjectionVars() called, "
    5362             :                      "but Projection has not yet been defined!");
    5363             :     }
    5364             : 
    5365             :     // Check GEOLOCATION information.
    5366         162 :     char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION");
    5367         162 :     if (papszGeolocationInfo != nullptr)
    5368             :     {
    5369             :         // Look for geolocation datasets.
    5370             :         const char *pszDSName =
    5371          10 :             CSLFetchNameValue(papszGeolocationInfo, "X_DATASET");
    5372          10 :         if (pszDSName != nullptr)
    5373          10 :             hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly);
    5374          10 :         pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET");
    5375          10 :         if (pszDSName != nullptr)
    5376          10 :             hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly);
    5377             : 
    5378          10 :         if (hDS_X != nullptr && hDS_Y != nullptr)
    5379             :         {
    5380          10 :             int nBand = std::max(1, atoi(CSLFetchNameValueDef(
    5381          10 :                                         papszGeolocationInfo, "X_BAND", "0")));
    5382          10 :             hBand_X = GDALGetRasterBand(hDS_X, nBand);
    5383          10 :             nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo,
    5384          10 :                                                           "Y_BAND", "0")));
    5385          10 :             hBand_Y = GDALGetRasterBand(hDS_Y, nBand);
    5386             : 
    5387             :             // If geoloc bands are found, do basic validation based on their
    5388             :             // dimensions.
    5389          10 :             if (hBand_X != nullptr && hBand_Y != nullptr)
    5390             :             {
    5391          10 :                 int nXSize_XBand = GDALGetRasterXSize(hDS_X);
    5392          10 :                 int nYSize_XBand = GDALGetRasterYSize(hDS_X);
    5393          10 :                 int nXSize_YBand = GDALGetRasterXSize(hDS_Y);
    5394          10 :                 int nYSize_YBand = GDALGetRasterYSize(hDS_Y);
    5395             : 
    5396             :                 // TODO 1D geolocation arrays not implemented.
    5397          10 :                 if (nYSize_XBand == 1 && nYSize_YBand == 1)
    5398             :                 {
    5399           0 :                     bHasGeoloc = false;
    5400           0 :                     CPLDebug("GDAL_netCDF",
    5401             :                              "1D GEOLOCATION arrays not supported yet");
    5402             :                 }
    5403             :                 // 2D bands must have same sizes as the raster bands.
    5404          10 :                 else if (nXSize_XBand != nRasterXSize ||
    5405          10 :                          nYSize_XBand != nRasterYSize ||
    5406          10 :                          nXSize_YBand != nRasterXSize ||
    5407          10 :                          nYSize_YBand != nRasterYSize)
    5408             :                 {
    5409           0 :                     bHasGeoloc = false;
    5410           0 :                     CPLDebug("GDAL_netCDF",
    5411             :                              "GEOLOCATION array sizes (%dx%d %dx%d) differ "
    5412             :                              "from raster (%dx%d), not supported",
    5413             :                              nXSize_XBand, nYSize_XBand, nXSize_YBand,
    5414             :                              nYSize_YBand, nRasterXSize, nRasterYSize);
    5415             :                 }
    5416             :                 else
    5417             :                 {
    5418          10 :                     bHasGeoloc = true;
    5419          10 :                     CPLDebug("GDAL_netCDF",
    5420             :                              "dataset has GEOLOCATION information, will try to "
    5421             :                              "write it");
    5422             :                 }
    5423             :             }
    5424             :         }
    5425             :     }
    5426             : 
    5427             :     // Process projection options.
    5428         162 :     if (bIsProjected)
    5429             :     {
    5430             :         bool bIsCfProjection =
    5431          50 :             oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE;
    5432          50 :         bWriteGridMapping = true;
    5433          50 :         bWriteGDALTags = CPL_TO_BOOL(
    5434          50 :             CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE));
    5435             :         // Force WRITE_GDAL_TAGS if is not a CF projection.
    5436          50 :         if (!bWriteGDALTags && !bIsCfProjection)
    5437           0 :             bWriteGDALTags = true;
    5438          50 :         if (bWriteGDALTags)
    5439          50 :             bWriteGeoTransform = true;
    5440             : 
    5441             :         // Write lon/lat: default is NO, except if has geolocation.
    5442             :         // With IF_NEEDED: write if has geoloc or is not CF projection.
    5443             :         const char *pszValue =
    5444          50 :             CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT");
    5445          50 :         if (pszValue)
    5446             :         {
    5447           2 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5448             :             {
    5449           0 :                 bWriteLonLat = bHasGeoloc || !bIsCfProjection;
    5450             :             }
    5451             :             else
    5452             :             {
    5453           2 :                 bWriteLonLat = CPLTestBool(pszValue);
    5454             :             }
    5455             :         }
    5456             :         else
    5457             :         {
    5458          48 :             bWriteLonLat = bHasGeoloc;
    5459             :         }
    5460             : 
    5461             :         // Save value of pszCFCoordinates for later.
    5462          50 :         if (bWriteLonLat)
    5463             :         {
    5464           4 :             pszCFCoordinates = NCDF_LONLAT;
    5465             :         }
    5466             :     }
    5467             :     else
    5468             :     {
    5469             :         // Files without a Datum will not have a grid_mapping variable and
    5470             :         // geographic information.
    5471         112 :         bWriteGridMapping = bIsGeographic;
    5472             : 
    5473         112 :         if (bHasGeoloc)
    5474             :         {
    5475           8 :             bWriteLonLat = true;
    5476             :         }
    5477             :         else
    5478             :         {
    5479         104 :             bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean(
    5480         104 :                 papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping));
    5481         104 :             if (bWriteGDALTags)
    5482          86 :                 bWriteGeoTransform = true;
    5483             : 
    5484         104 :             const char *pszValue = CSLFetchNameValueDef(papszCreationOptions,
    5485             :                                                         "WRITE_LONLAT", "YES");
    5486         104 :             if (EQUAL(pszValue, "IF_NEEDED"))
    5487           0 :                 bWriteLonLat = true;
    5488             :             else
    5489         104 :                 bWriteLonLat = CPLTestBool(pszValue);
    5490             :             //  Don't write lon/lat if no source geotransform.
    5491         104 :             if (!m_bHasGeoTransform)
    5492           0 :                 bWriteLonLat = false;
    5493             :             // If we don't write lon/lat, set dimnames to X/Y and write gdal
    5494             :             // tags.
    5495         104 :             if (!bWriteLonLat)
    5496             :             {
    5497           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5498             :                          "creating geographic file without lon/lat values!");
    5499           0 :                 if (m_bHasGeoTransform)
    5500             :                 {
    5501           0 :                     bWriteGDALTags = true;  // Not desirable if no geotransform.
    5502           0 :                     bWriteGeoTransform = true;
    5503             :                 }
    5504             :             }
    5505             :         }
    5506             :     }
    5507             : 
    5508             :     // Make sure we write grid_mapping if we need to write GDAL tags.
    5509         162 :     if (bWriteGDALTags)
    5510         136 :         bWriteGridMapping = true;
    5511             : 
    5512             :     // bottom-up value: new driver is bottom-up by default.
    5513             :     // Override with WRITE_BOTTOMUP.
    5514         162 :     bBottomUp = CPL_TO_BOOL(
    5515         162 :         CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE));
    5516             : 
    5517         162 :     if (bDefsOnly)
    5518             :     {
    5519          81 :         CPLDebug(
    5520             :             "GDAL_netCDF",
    5521             :             "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d "
    5522             :             "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d",
    5523          81 :             static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic),
    5524             :             static_cast<int>(bWriteGridMapping),
    5525             :             static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat),
    5526          81 :             static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc));
    5527             :     }
    5528             : 
    5529             :     // Exit if nothing to do.
    5530         162 :     if (!bIsProjected && !bWriteLonLat)
    5531           0 :         return CE_None;
    5532             : 
    5533             :     // Define dimension names.
    5534             : 
    5535         162 :     constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole";
    5536             : 
    5537         162 :     if (bDefsOnly)
    5538             :     {
    5539          81 :         int nVarLonID = -1;
    5540          81 :         int nVarLatID = -1;
    5541          81 :         int nVarXID = -1;
    5542          81 :         int nVarYID = -1;
    5543             : 
    5544          81 :         m_bAddedProjectionVarsDefs = true;
    5545             : 
    5546             :         // Make sure we are in define mode.
    5547          81 :         SetDefineMode(true);
    5548             : 
    5549             :         // Write projection attributes.
    5550          81 :         if (bWriteGridMapping)
    5551             :         {
    5552          68 :             const int NCDFVarID = NCDFWriteSRSVariable(
    5553             :                 cdfid, &oSRS, &pszCFProjection, bWriteGDALTags);
    5554          68 :             if (NCDFVarID < 0)
    5555           0 :                 return CE_Failure;
    5556             : 
    5557             :             // Optional GDAL custom projection tags.
    5558          68 :             if (bWriteGDALTags)
    5559             :             {
    5560         136 :                 CPLString osGeoTransform;
    5561         476 :                 for (int i = 0; i < 6; i++)
    5562             :                 {
    5563             :                     osGeoTransform +=
    5564         408 :                         CPLSPrintf("%.16g ", m_adfGeoTransform[i]);
    5565             :                 }
    5566          68 :                 CPLDebug("GDAL_netCDF", "szGeoTransform = %s",
    5567             :                          osGeoTransform.c_str());
    5568             : 
    5569             :                 // if( strlen(pszProj4Defn) > 0 ) {
    5570             :                 //     nc_put_att_text(cdfid, NCDFVarID, "proj4",
    5571             :                 //                      strlen(pszProj4Defn), pszProj4Defn);
    5572             :                 // }
    5573             : 
    5574             :                 // For now, write the geotransform for back-compat or else
    5575             :                 // the old (1.8.1) driver overrides the CF geotransform with
    5576             :                 // empty values from dfNN, dfSN, dfEE, dfWE;
    5577             : 
    5578             :                 // TODO: fix this in 1.8 branch, and then remove this here.
    5579          68 :                 if (bWriteGeoTransform && m_bHasGeoTransform)
    5580             :                 {
    5581             :                     {
    5582          67 :                         const int status = nc_put_att_text(
    5583             :                             cdfid, NCDFVarID, NCDF_GEOTRANSFORM,
    5584             :                             osGeoTransform.size(), osGeoTransform.c_str());
    5585          67 :                         NCDF_ERR(status);
    5586             :                     }
    5587             :                 }
    5588             :             }
    5589             : 
    5590             :             // Write projection variable to band variable.
    5591             :             // Need to call later if there are no bands.
    5592          68 :             AddGridMappingRef();
    5593             :         }  // end if( bWriteGridMapping )
    5594             : 
    5595             :         // Write CF Projection vars.
    5596             : 
    5597          81 :         const bool bIsRotatedPole =
    5598         149 :             pszCFProjection != nullptr &&
    5599          68 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5600          81 :         if (bIsRotatedPole)
    5601             :         {
    5602             :             // Rename dims to rlat/rlon.
    5603             :             papszDimName
    5604           0 :                 .Clear();  // If we add other dims one day, this has to change
    5605           0 :             papszDimName.AddString(NCDF_DIMNAME_RLAT);
    5606           0 :             papszDimName.AddString(NCDF_DIMNAME_RLON);
    5607             : 
    5608           0 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT);
    5609           0 :             NCDF_ERR(status);
    5610           0 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON);
    5611           0 :             NCDF_ERR(status);
    5612             :         }
    5613             :         // Rename dimensions if lon/lat.
    5614          81 :         else if (!bIsProjected && !bHasGeoloc)
    5615             :         {
    5616             :             // Rename dims to lat/lon.
    5617             :             papszDimName
    5618          52 :                 .Clear();  // If we add other dims one day, this has to change
    5619          52 :             papszDimName.AddString(NCDF_DIMNAME_LAT);
    5620          52 :             papszDimName.AddString(NCDF_DIMNAME_LON);
    5621             : 
    5622          52 :             int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT);
    5623          52 :             NCDF_ERR(status);
    5624          52 :             status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON);
    5625          52 :             NCDF_ERR(status);
    5626             :         }
    5627             : 
    5628             :         // Write X/Y attributes.
    5629             :         else /* if( bIsProjected || bHasGeoloc ) */
    5630             :         {
    5631             :             // X
    5632             :             int anXDims[1];
    5633          29 :             anXDims[0] = nXDimID;
    5634          29 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5635             :                      CF_PROJ_X_VAR_NAME, NC_DOUBLE);
    5636          29 :             int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1,
    5637             :                                     anXDims, &nVarXID);
    5638          29 :             NCDF_ERR(status);
    5639             : 
    5640             :             // Y
    5641             :             int anYDims[1];
    5642          29 :             anYDims[0] = nYDimID;
    5643          29 :             CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid,
    5644             :                      CF_PROJ_Y_VAR_NAME, NC_DOUBLE);
    5645          29 :             status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1,
    5646             :                                 anYDims, &nVarYID);
    5647          29 :             NCDF_ERR(status);
    5648             : 
    5649          29 :             if (bIsProjected)
    5650             :             {
    5651          25 :                 NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS);
    5652             :             }
    5653             :             else
    5654             :             {
    5655           4 :                 CPLAssert(bHasGeoloc);
    5656             :                 try
    5657             :                 {
    5658           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS);
    5659           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME,
    5660             :                                           "x-coordinate in Cartesian system");
    5661           4 :                     vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m");
    5662           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS);
    5663           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME,
    5664             :                                           "y-coordinate in Cartesian system");
    5665           4 :                     vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m");
    5666             : 
    5667           4 :                     pszCFCoordinates = NCDF_LONLAT;
    5668             :                 }
    5669           0 :                 catch (nccfdriver::SG_Exception &e)
    5670             :                 {
    5671           0 :                     CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg());
    5672           0 :                     return CE_Failure;
    5673             :                 }
    5674             :             }
    5675             :         }
    5676             : 
    5677             :         // Write lat/lon attributes if needed.
    5678          81 :         if (bWriteLonLat)
    5679             :         {
    5680          58 :             int *panLatDims = nullptr;
    5681          58 :             int *panLonDims = nullptr;
    5682          58 :             int nLatDims = -1;
    5683          58 :             int nLonDims = -1;
    5684             : 
    5685             :             // Get information.
    5686          58 :             if (bHasGeoloc)
    5687             :             {
    5688             :                 // Geoloc
    5689           5 :                 nLatDims = 2;
    5690             :                 panLatDims =
    5691           5 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5692           5 :                 panLatDims[0] = nYDimID;
    5693           5 :                 panLatDims[1] = nXDimID;
    5694           5 :                 nLonDims = 2;
    5695             :                 panLonDims =
    5696           5 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5697           5 :                 panLonDims[0] = nYDimID;
    5698           5 :                 panLonDims[1] = nXDimID;
    5699             :             }
    5700          53 :             else if (bIsProjected)
    5701             :             {
    5702             :                 // Projected
    5703           1 :                 nLatDims = 2;
    5704             :                 panLatDims =
    5705           1 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5706           1 :                 panLatDims[0] = nYDimID;
    5707           1 :                 panLatDims[1] = nXDimID;
    5708           1 :                 nLonDims = 2;
    5709             :                 panLonDims =
    5710           1 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5711           1 :                 panLonDims[0] = nYDimID;
    5712           1 :                 panLonDims[1] = nXDimID;
    5713             :             }
    5714             :             else
    5715             :             {
    5716             :                 // Geographic
    5717          52 :                 nLatDims = 1;
    5718             :                 panLatDims =
    5719          52 :                     static_cast<int *>(CPLCalloc(nLatDims, sizeof(int)));
    5720          52 :                 panLatDims[0] = nYDimID;
    5721          52 :                 nLonDims = 1;
    5722             :                 panLonDims =
    5723          52 :                     static_cast<int *>(CPLCalloc(nLonDims, sizeof(int)));
    5724          52 :                 panLonDims[0] = nXDimID;
    5725             :             }
    5726             : 
    5727          58 :             nc_type eLonLatType = NC_NAT;
    5728          58 :             if (bIsProjected)
    5729             :             {
    5730           2 :                 eLonLatType = NC_FLOAT;
    5731           4 :                 const char *pszValue = CSLFetchNameValueDef(
    5732           2 :                     papszCreationOptions, "TYPE_LONLAT", "FLOAT");
    5733           2 :                 if (EQUAL(pszValue, "DOUBLE"))
    5734           0 :                     eLonLatType = NC_DOUBLE;
    5735             :             }
    5736             :             else
    5737             :             {
    5738          56 :                 eLonLatType = NC_DOUBLE;
    5739         112 :                 const char *pszValue = CSLFetchNameValueDef(
    5740          56 :                     papszCreationOptions, "TYPE_LONLAT", "DOUBLE");
    5741          56 :                 if (EQUAL(pszValue, "FLOAT"))
    5742           0 :                     eLonLatType = NC_FLOAT;
    5743             :             }
    5744             : 
    5745             :             // Def vars and attributes.
    5746             :             {
    5747          58 :                 const char *pszVarName =
    5748          58 :                     bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME;
    5749          58 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5750             :                                         nLatDims, panLatDims, &nVarLatID);
    5751          58 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5752             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID);
    5753          58 :                 NCDF_ERR(status);
    5754          58 :                 DefVarDeflate(nVarLatID, false);  // Don't set chunking.
    5755             :             }
    5756             : 
    5757             :             {
    5758          58 :                 const char *pszVarName =
    5759          58 :                     bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME;
    5760          58 :                 int status = nc_def_var(cdfid, pszVarName, eLonLatType,
    5761             :                                         nLonDims, panLonDims, &nVarLonID);
    5762          58 :                 CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d",
    5763             :                          cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID);
    5764          58 :                 NCDF_ERR(status);
    5765          58 :                 DefVarDeflate(nVarLonID, false);  // Don't set chunking.
    5766             :             }
    5767             : 
    5768          58 :             if (bIsRotatedPole)
    5769           0 :                 NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID,
    5770             :                                                 nVarLatID);
    5771             :             else
    5772          58 :                 NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID);
    5773             : 
    5774          58 :             CPLFree(panLatDims);
    5775          58 :             CPLFree(panLonDims);
    5776             :         }
    5777             :     }
    5778             : 
    5779         162 :     if (!bDefsOnly)
    5780             :     {
    5781          81 :         m_bAddedProjectionVarsData = true;
    5782             : 
    5783          81 :         int nVarXID = -1;
    5784          81 :         int nVarYID = -1;
    5785             : 
    5786          81 :         nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID);
    5787          81 :         nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID);
    5788             : 
    5789          81 :         int nVarLonID = -1;
    5790          81 :         int nVarLatID = -1;
    5791             : 
    5792          81 :         const bool bIsRotatedPole =
    5793         149 :             pszCFProjection != nullptr &&
    5794          68 :             EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME);
    5795          81 :         nc_inq_varid(cdfid,
    5796             :                      bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME,
    5797             :                      &nVarLonID);
    5798          81 :         nc_inq_varid(cdfid,
    5799             :                      bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME,
    5800             :                      &nVarLatID);
    5801             : 
    5802             :         // Get projection values.
    5803             : 
    5804          81 :         double *padLonVal = nullptr;
    5805          81 :         double *padLatVal = nullptr;
    5806             : 
    5807          81 :         if (bIsProjected)
    5808             :         {
    5809          25 :             OGRSpatialReference *poLatLonSRS = nullptr;
    5810          25 :             OGRCoordinateTransformation *poTransform = nullptr;
    5811             : 
    5812             :             size_t startX[1];
    5813             :             size_t countX[1];
    5814             :             size_t startY[1];
    5815             :             size_t countY[1];
    5816             : 
    5817          25 :             CPLDebug("GDAL_netCDF", "Getting (X,Y) values");
    5818             : 
    5819             :             double *padXVal =
    5820          25 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    5821             :             double *padYVal =
    5822          25 :                 static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double)));
    5823             : 
    5824             :             // Get Y values.
    5825          25 :             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
    5826             :                                              // Invert latitude values.
    5827          25 :                                     m_adfGeoTransform[3] +
    5828          25 :                                         (m_adfGeoTransform[5] * nRasterYSize);
    5829          25 :             const double dfDY = m_adfGeoTransform[5];
    5830             : 
    5831        1456 :             for (int j = 0; j < nRasterYSize; j++)
    5832             :             {
    5833             :                 // The data point is centered inside the pixel.
    5834        1431 :                 if (!bBottomUp)
    5835           0 :                     padYVal[j] = dfY0 + (j + 0.5) * dfDY;
    5836             :                 else  // Invert latitude values.
    5837        1431 :                     padYVal[j] = dfY0 - (j + 0.5) * dfDY;
    5838             :             }
    5839          25 :             startX[0] = 0;
    5840          25 :             countX[0] = nRasterXSize;
    5841             : 
    5842             :             // Get X values.
    5843          25 :             const double dfX0 = m_adfGeoTransform[0];
    5844          25 :             const double dfDX = m_adfGeoTransform[1];
    5845             : 
    5846        1477 :             for (int i = 0; i < nRasterXSize; i++)
    5847             :             {
    5848             :                 // The data point is centered inside the pixel.
    5849        1452 :                 padXVal[i] = dfX0 + (i + 0.5) * dfDX;
    5850             :             }
    5851          25 :             startY[0] = 0;
    5852          25 :             countY[0] = nRasterYSize;
    5853             : 
    5854             :             // Write X/Y values.
    5855             : 
    5856             :             // Make sure we are in data mode.
    5857          25 :             SetDefineMode(false);
    5858             : 
    5859          25 :             CPLDebug("GDAL_netCDF", "Writing X values");
    5860             :             int status =
    5861          25 :                 nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal);
    5862          25 :             NCDF_ERR(status);
    5863             : 
    5864          25 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    5865             :             status =
    5866          25 :                 nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal);
    5867          25 :             NCDF_ERR(status);
    5868             : 
    5869          25 :             if (pfnProgress)
    5870          21 :                 pfnProgress(0.20, nullptr, pProgressData);
    5871             : 
    5872             :             // Write lon/lat arrays (CF coordinates) if requested.
    5873             : 
    5874             :             // Get OGR transform if GEOLOCATION is not available.
    5875          25 :             if (bWriteLonLat && !bHasGeoloc)
    5876             :             {
    5877           1 :                 poLatLonSRS = m_oSRS.CloneGeogCS();
    5878           1 :                 if (poLatLonSRS != nullptr)
    5879             :                 {
    5880           1 :                     poLatLonSRS->SetAxisMappingStrategy(
    5881             :                         OAMS_TRADITIONAL_GIS_ORDER);
    5882             :                     poTransform =
    5883           1 :                         OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS);
    5884             :                 }
    5885             :                 // If no OGR transform, then don't write CF lon/lat.
    5886           1 :                 if (poTransform == nullptr)
    5887             :                 {
    5888           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5889             :                              "Unable to get Coordinate Transform");
    5890           0 :                     bWriteLonLat = false;
    5891             :                 }
    5892             :             }
    5893             : 
    5894          25 :             if (bWriteLonLat)
    5895             :             {
    5896           2 :                 if (!bHasGeoloc)
    5897           1 :                     CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)");
    5898             :                 else
    5899           1 :                     CPLDebug("GDAL_netCDF",
    5900             :                              "Writing (lon,lat) from GEOLOCATION arrays");
    5901             : 
    5902           2 :                 bool bOK = true;
    5903           2 :                 double dfProgress = 0.2;
    5904             : 
    5905           2 :                 size_t start[] = {0, 0};
    5906           2 :                 size_t count[] = {1, (size_t)nRasterXSize};
    5907             :                 padLatVal = static_cast<double *>(
    5908           2 :                     CPLMalloc(nRasterXSize * sizeof(double)));
    5909             :                 padLonVal = static_cast<double *>(
    5910           2 :                     CPLMalloc(nRasterXSize * sizeof(double)));
    5911             : 
    5912          61 :                 for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR;
    5913             :                      j++)
    5914             :                 {
    5915          59 :                     start[0] = j;
    5916             : 
    5917             :                     // Get values from geotransform.
    5918          59 :                     if (!bHasGeoloc)
    5919             :                     {
    5920             :                         // Fill values to transform.
    5921         420 :                         for (int i = 0; i < nRasterXSize; i++)
    5922             :                         {
    5923         400 :                             padLatVal[i] = padYVal[j];
    5924         400 :                             padLonVal[i] = padXVal[i];
    5925             :                         }
    5926             : 
    5927             :                         // Do the transform.
    5928          20 :                         bOK = CPL_TO_BOOL(poTransform->Transform(
    5929          20 :                             nRasterXSize, padLonVal, padLatVal, nullptr));
    5930          20 :                         if (!bOK)
    5931             :                         {
    5932           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5933             :                                      "Unable to Transform (X,Y) to (lon,lat).");
    5934             :                         }
    5935             :                     }
    5936             :                     // Get values from geoloc arrays.
    5937             :                     else
    5938             :                     {
    5939          39 :                         CPLErr eErr = GDALRasterIO(
    5940             :                             hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal,
    5941             :                             nRasterXSize, 1, GDT_Float64, 0, 0);
    5942          39 :                         if (eErr == CE_None)
    5943             :                         {
    5944          39 :                             eErr = GDALRasterIO(
    5945             :                                 hBand_X, GF_Read, 0, j, nRasterXSize, 1,
    5946             :                                 padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0);
    5947             :                         }
    5948             : 
    5949          39 :                         if (eErr == CE_None)
    5950             :                         {
    5951          39 :                             bOK = true;
    5952             :                         }
    5953             :                         else
    5954             :                         {
    5955           0 :                             bOK = false;
    5956           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    5957             :                                      "Unable to get scanline %d", j);
    5958             :                         }
    5959             :                     }
    5960             : 
    5961             :                     // Write data.
    5962          59 :                     if (bOK)
    5963             :                     {
    5964          59 :                         status = nc_put_vara_double(cdfid, nVarLatID, start,
    5965             :                                                     count, padLatVal);
    5966          59 :                         NCDF_ERR(status);
    5967          59 :                         status = nc_put_vara_double(cdfid, nVarLonID, start,
    5968             :                                                     count, padLonVal);
    5969          59 :                         NCDF_ERR(status);
    5970             :                     }
    5971             : 
    5972          59 :                     if (pfnProgress && (nRasterYSize / 10) > 0 &&
    5973          59 :                         (j % (nRasterYSize / 10) == 0))
    5974             :                     {
    5975          23 :                         dfProgress += 0.08;
    5976          23 :                         pfnProgress(dfProgress, nullptr, pProgressData);
    5977             :                     }
    5978             :                 }
    5979             :             }
    5980             : 
    5981          25 :             if (poLatLonSRS != nullptr)
    5982           1 :                 delete poLatLonSRS;
    5983          25 :             if (poTransform != nullptr)
    5984           1 :                 delete poTransform;
    5985             : 
    5986          25 :             CPLFree(padXVal);
    5987          25 :             CPLFree(padYVal);
    5988             :         }  // Projected
    5989             : 
    5990             :         // If not projected/geographic and has geoloc
    5991          56 :         else if (!bIsGeographic && bHasGeoloc)
    5992             :         {
    5993             :             // Use
    5994             :             // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables
    5995             : 
    5996           4 :             bool bOK = true;
    5997           4 :             double dfProgress = 0.2;
    5998             : 
    5999             :             // Make sure we are in data mode.
    6000           4 :             SetDefineMode(false);
    6001             : 
    6002             :             size_t startX[1];
    6003             :             size_t countX[1];
    6004             :             size_t startY[1];
    6005             :             size_t countY[1];
    6006           4 :             startX[0] = 0;
    6007           4 :             countX[0] = nRasterXSize;
    6008             : 
    6009           4 :             startY[0] = 0;
    6010           4 :             countY[0] = nRasterYSize;
    6011             : 
    6012           8 :             std::vector<double> adfXVal(nRasterXSize);
    6013          16 :             for (int i = 0; i < nRasterXSize; i++)
    6014          12 :                 adfXVal[i] = i;
    6015             : 
    6016           8 :             std::vector<double> adfYVal(nRasterYSize);
    6017          12 :             for (int i = 0; i < nRasterYSize; i++)
    6018           8 :                 adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i;
    6019             : 
    6020           4 :             CPLDebug("GDAL_netCDF", "Writing X values");
    6021           4 :             int status = nc_put_vara_double(cdfid, nVarXID, startX, countX,
    6022           4 :                                             adfXVal.data());
    6023           4 :             NCDF_ERR(status);
    6024             : 
    6025           4 :             CPLDebug("GDAL_netCDF", "Writing Y values");
    6026           4 :             status = nc_put_vara_double(cdfid, nVarYID, startY, countY,
    6027           4 :                                         adfYVal.data());
    6028           4 :             NCDF_ERR(status);
    6029             : 
    6030           4 :             if (pfnProgress)
    6031           0 :                 pfnProgress(0.20, nullptr, pProgressData);
    6032             : 
    6033           4 :             size_t start[] = {0, 0};
    6034           4 :             size_t count[] = {1, (size_t)nRasterXSize};
    6035             :             padLatVal =
    6036           4 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    6037             :             padLonVal =
    6038           4 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    6039             : 
    6040          12 :             for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++)
    6041             :             {
    6042           8 :                 start[0] = j;
    6043             : 
    6044           8 :                 CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0,
    6045           8 :                                            bBottomUp ? nRasterYSize - 1 - j : j,
    6046             :                                            nRasterXSize, 1, padLatVal,
    6047             :                                            nRasterXSize, 1, GDT_Float64, 0, 0);
    6048           8 :                 if (eErr == CE_None)
    6049             :                 {
    6050           8 :                     eErr = GDALRasterIO(hBand_X, GF_Read, 0,
    6051           8 :                                         bBottomUp ? nRasterYSize - 1 - j : j,
    6052             :                                         nRasterXSize, 1, padLonVal,
    6053             :                                         nRasterXSize, 1, GDT_Float64, 0, 0);
    6054             :                 }
    6055             : 
    6056           8 :                 if (eErr == CE_None)
    6057             :                 {
    6058           8 :                     bOK = true;
    6059             :                 }
    6060             :                 else
    6061             :                 {
    6062           0 :                     bOK = false;
    6063           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    6064             :                              "Unable to get scanline %d", j);
    6065             :                 }
    6066             : 
    6067             :                 // Write data.
    6068           8 :                 if (bOK)
    6069             :                 {
    6070           8 :                     status = nc_put_vara_double(cdfid, nVarLatID, start, count,
    6071             :                                                 padLatVal);
    6072           8 :                     NCDF_ERR(status);
    6073           8 :                     status = nc_put_vara_double(cdfid, nVarLonID, start, count,
    6074             :                                                 padLonVal);
    6075           8 :                     NCDF_ERR(status);
    6076             :                 }
    6077             : 
    6078           8 :                 if (pfnProgress && (nRasterYSize / 10) > 0 &&
    6079           0 :                     (j % (nRasterYSize / 10) == 0))
    6080             :                 {
    6081           0 :                     dfProgress += 0.08;
    6082           0 :                     pfnProgress(dfProgress, nullptr, pProgressData);
    6083             :                 }
    6084           4 :             }
    6085             :         }
    6086             : 
    6087             :         // If not projected, assume geographic to catch grids without Datum.
    6088          52 :         else if (bWriteLonLat)
    6089             :         {
    6090             :             // Get latitude values.
    6091          52 :             const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] :
    6092             :                                              // Invert latitude values.
    6093          52 :                                     m_adfGeoTransform[3] +
    6094          52 :                                         (m_adfGeoTransform[5] * nRasterYSize);
    6095          52 :             const double dfDY = m_adfGeoTransform[5];
    6096             : 
    6097             :             // Override lat values with the ones in GEOLOCATION/Y_VALUES.
    6098          52 :             if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") !=
    6099             :                 nullptr)
    6100             :             {
    6101           0 :                 int nTemp = 0;
    6102           0 :                 padLatVal = Get1DGeolocation("Y_VALUES", nTemp);
    6103             :                 // Make sure we got the correct amount, if not fallback to GT */
    6104             :                 // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1))
    6105           0 :                 if (nTemp == nRasterYSize)
    6106             :                 {
    6107           0 :                     CPLDebug(
    6108             :                         "GDAL_netCDF",
    6109             :                         "Using Y_VALUES geolocation metadata for lat values");
    6110             :                 }
    6111             :                 else
    6112             :                 {
    6113           0 :                     CPLDebug("GDAL_netCDF",
    6114             :                              "Got %d elements from Y_VALUES geolocation "
    6115             :                              "metadata, need %d",
    6116             :                              nTemp, nRasterYSize);
    6117           0 :                     if (padLatVal)
    6118             :                     {
    6119           0 :                         CPLFree(padLatVal);
    6120           0 :                         padLatVal = nullptr;
    6121             :                     }
    6122             :                 }
    6123             :             }
    6124             : 
    6125          52 :             if (padLatVal == nullptr)
    6126             :             {
    6127             :                 padLatVal = static_cast<double *>(
    6128          52 :                     CPLMalloc(nRasterYSize * sizeof(double)));
    6129        3504 :                 for (int i = 0; i < nRasterYSize; i++)
    6130             :                 {
    6131             :                     // The data point is centered inside the pixel.
    6132        3452 :                     if (!bBottomUp)
    6133           0 :                         padLatVal[i] = dfY0 + (i + 0.5) * dfDY;
    6134             :                     else  // Invert latitude values.
    6135        3452 :                         padLatVal[i] = dfY0 - (i + 0.5) * dfDY;
    6136             :                 }
    6137             :             }
    6138             : 
    6139          52 :             size_t startLat[1] = {0};
    6140          52 :             size_t countLat[1] = {static_cast<size_t>(nRasterYSize)};
    6141             : 
    6142             :             // Get longitude values.
    6143          52 :             const double dfX0 = m_adfGeoTransform[0];
    6144          52 :             const double dfDX = m_adfGeoTransform[1];
    6145             : 
    6146             :             padLonVal =
    6147          52 :                 static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double)));
    6148        3556 :             for (int i = 0; i < nRasterXSize; i++)
    6149             :             {
    6150             :                 // The data point is centered inside the pixel.
    6151        3504 :                 padLonVal[i] = dfX0 + (i + 0.5) * dfDX;
    6152             :             }
    6153             : 
    6154          52 :             size_t startLon[1] = {0};
    6155          52 :             size_t countLon[1] = {static_cast<size_t>(nRasterXSize)};
    6156             : 
    6157             :             // Write latitude and longitude values.
    6158             : 
    6159             :             // Make sure we are in data mode.
    6160          52 :             SetDefineMode(false);
    6161             : 
    6162             :             // Write values.
    6163          52 :             CPLDebug("GDAL_netCDF", "Writing lat values");
    6164             : 
    6165          52 :             int status = nc_put_vara_double(cdfid, nVarLatID, startLat,
    6166             :                                             countLat, padLatVal);
    6167          52 :             NCDF_ERR(status);
    6168             : 
    6169          52 :             CPLDebug("GDAL_netCDF", "Writing lon values");
    6170          52 :             status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon,
    6171             :                                         padLonVal);
    6172          52 :             NCDF_ERR(status);
    6173             : 
    6174             :         }  // Not projected.
    6175             : 
    6176          81 :         CPLFree(padLatVal);
    6177          81 :         CPLFree(padLonVal);
    6178             : 
    6179          81 :         if (pfnProgress)
    6180          40 :             pfnProgress(1.00, nullptr, pProgressData);
    6181             :     }
    6182             : 
    6183         162 :     if (hDS_X != nullptr)
    6184             :     {
    6185          10 :         GDALClose(hDS_X);
    6186             :     }
    6187         162 :     if (hDS_Y != nullptr)
    6188             :     {
    6189          10 :         GDALClose(hDS_Y);
    6190             :     }
    6191             : 
    6192         162 :     return CE_None;
    6193             : }
    6194             : 
    6195             : // Write Projection variable to band variable.
    6196             : // Moved from AddProjectionVars() for cases when bands are added after
    6197             : // projection.
    6198         372 : bool netCDFDataset::AddGridMappingRef()
    6199             : {
    6200         372 :     bool bRet = true;
    6201         372 :     bool bOldDefineMode = bDefineMode;
    6202             : 
    6203         561 :     if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) &&
    6204         189 :         ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) ||
    6205         183 :          (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))))
    6206             :     {
    6207          72 :         bAddedGridMappingRef = true;
    6208             : 
    6209             :         // Make sure we are in define mode.
    6210          72 :         SetDefineMode(true);
    6211             : 
    6212         190 :         for (int i = 1; i <= nBands; i++)
    6213             :         {
    6214             :             const int nVarId =
    6215         118 :                 static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId;
    6216             : 
    6217         118 :             if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, ""))
    6218             :             {
    6219             :                 int status =
    6220         228 :                     nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING,
    6221         114 :                                     strlen(pszCFProjection), pszCFProjection);
    6222         114 :                 NCDF_ERR(status);
    6223         114 :                 if (status != NC_NOERR)
    6224           0 :                     bRet = false;
    6225             :             }
    6226         118 :             if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, ""))
    6227             :             {
    6228             :                 int status =
    6229           6 :                     nc_put_att_text(cdfid, nVarId, CF_COORDINATES,
    6230             :                                     strlen(pszCFCoordinates), pszCFCoordinates);
    6231           6 :                 NCDF_ERR(status);
    6232           6 :                 if (status != NC_NOERR)
    6233           0 :                     bRet = false;
    6234             :             }
    6235             :         }
    6236             : 
    6237             :         // Go back to previous define mode.
    6238          72 :         SetDefineMode(bOldDefineMode);
    6239             :     }
    6240         372 :     return bRet;
    6241             : }
    6242             : 
    6243             : /************************************************************************/
    6244             : /*                          GetGeoTransform()                           */
    6245             : /************************************************************************/
    6246             : 
    6247         107 : CPLErr netCDFDataset::GetGeoTransform(double *padfTransform)
    6248             : 
    6249             : {
    6250         107 :     memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6);
    6251         107 :     if (m_bHasGeoTransform)
    6252          77 :         return CE_None;
    6253             : 
    6254          30 :     return GDALPamDataset::GetGeoTransform(padfTransform);
    6255             : }
    6256             : 
    6257             : /************************************************************************/
    6258             : /*                                rint()                                */
    6259             : /************************************************************************/
    6260             : 
    6261           0 : double netCDFDataset::rint(double dfX)
    6262             : {
    6263           0 :     return std::round(dfX);
    6264             : }
    6265             : 
    6266             : /************************************************************************/
    6267             : /*                          NCDFReadIsoMetadata()                       */
    6268             : /************************************************************************/
    6269             : 
    6270          16 : static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj)
    6271             : {
    6272          16 :     int nbAttr = 0;
    6273          16 :     NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr));
    6274             : 
    6275          32 :     std::map<std::string, CPLJSONArray> oMapNameToArray;
    6276          40 :     for (int l = 0; l < nbAttr; l++)
    6277             :     {
    6278             :         char szAttrName[NC_MAX_NAME + 1];
    6279          24 :         szAttrName[0] = 0;
    6280          24 :         NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName));
    6281             : 
    6282          24 :         char *pszMetaValue = nullptr;
    6283          24 :         if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None)
    6284             :         {
    6285          24 :             nc_type nAttrType = NC_NAT;
    6286          24 :             size_t nAttrLen = 0;
    6287             : 
    6288          24 :             NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType,
    6289             :                                 &nAttrLen));
    6290             : 
    6291          24 :             std::string osAttrName(szAttrName);
    6292          24 :             const auto sharpPos = osAttrName.find('#');
    6293          24 :             if (sharpPos == std::string::npos)
    6294             :             {
    6295          16 :                 if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT)
    6296           4 :                     obj.Add(osAttrName, CPLAtof(pszMetaValue));
    6297             :                 else
    6298          12 :                     obj.Add(osAttrName, pszMetaValue);
    6299             :             }
    6300             :             else
    6301             :             {
    6302           8 :                 osAttrName.resize(sharpPos);
    6303           8 :                 auto iter = oMapNameToArray.find(osAttrName);
    6304           8 :                 if (iter == oMapNameToArray.end())
    6305             :                 {
    6306           8 :                     CPLJSONArray array;
    6307           4 :                     obj.Add(osAttrName, array);
    6308           4 :                     oMapNameToArray[osAttrName] = array;
    6309           4 :                     array.Add(pszMetaValue);
    6310             :                 }
    6311             :                 else
    6312             :                 {
    6313           4 :                     iter->second.Add(pszMetaValue);
    6314             :                 }
    6315             :             }
    6316          24 :             CPLFree(pszMetaValue);
    6317          24 :             pszMetaValue = nullptr;
    6318             :         }
    6319             :     }
    6320             : 
    6321          16 :     int nSubGroups = 0;
    6322          16 :     int *panSubGroupIds = nullptr;
    6323          16 :     NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds);
    6324          16 :     oMapNameToArray.clear();
    6325          28 :     for (int i = 0; i < nSubGroups; i++)
    6326             :     {
    6327          24 :         CPLJSONObject subObj;
    6328          12 :         NCDFReadMetadataAsJson(panSubGroupIds[i], subObj);
    6329             : 
    6330          24 :         std::string osGroupName;
    6331          12 :         osGroupName.resize(NC_MAX_NAME);
    6332          12 :         NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0]));
    6333          12 :         osGroupName.resize(strlen(osGroupName.data()));
    6334          12 :         const auto sharpPos = osGroupName.find('#');
    6335          12 :         if (sharpPos == std::string::npos)
    6336             :         {
    6337           4 :             obj.Add(osGroupName, subObj);
    6338             :         }
    6339             :         else
    6340             :         {
    6341           8 :             osGroupName.resize(sharpPos);
    6342           8 :             auto iter = oMapNameToArray.find(osGroupName);
    6343           8 :             if (iter == oMapNameToArray.end())
    6344             :             {
    6345           8 :                 CPLJSONArray array;
    6346           4 :                 obj.Add(osGroupName, array);
    6347           4 :                 oMapNameToArray[osGroupName] = array;
    6348           4 :                 array.Add(subObj);
    6349             :             }
    6350             :             else
    6351             :             {
    6352           4 :                 iter->second.Add(subObj);
    6353             :             }
    6354             :         }
    6355             :     }
    6356          16 :     CPLFree(panSubGroupIds);
    6357          16 : }
    6358             : 
    6359           4 : std::string NCDFReadMetadataAsJson(int cdfid)
    6360             : {
    6361           8 :     CPLJSONDocument oDoc;
    6362           8 :     CPLJSONObject oRoot = oDoc.GetRoot();
    6363           4 :     NCDFReadMetadataAsJson(cdfid, oRoot);
    6364           8 :     return oDoc.SaveAsString();
    6365             : }
    6366             : 
    6367             : /************************************************************************/
    6368             : /*                        ReadAttributes()                              */
    6369             : /************************************************************************/
    6370        1780 : CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var)
    6371             : 
    6372             : {
    6373        1780 :     char *pszVarFullName = nullptr;
    6374        1780 :     ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName));
    6375             : 
    6376             :     // For metadata in Sentinel 5
    6377        1780 :     if (STARTS_WITH(pszVarFullName, "/METADATA/"))
    6378             :     {
    6379           6 :         for (const char *key :
    6380             :              {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS",
    6381           8 :               "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"})
    6382             :         {
    6383          14 :             if (var == NC_GLOBAL &&
    6384           7 :                 strcmp(pszVarFullName,
    6385             :                        CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0)
    6386             :             {
    6387           1 :                 CPLFree(pszVarFullName);
    6388           1 :                 CPLStringList aosList;
    6389           2 :                 aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn))
    6390           1 :                                       .replaceAll("\\/", '/'));
    6391           1 :                 m_oMapDomainToJSon[key] = std::move(aosList);
    6392           1 :                 return CE_None;
    6393             :             }
    6394             :         }
    6395             :     }
    6396        1779 :     if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/"))
    6397             :     {
    6398           0 :         CPLFree(pszVarFullName);
    6399           0 :         CPLStringList aosList;
    6400             :         aosList.AddString(
    6401           0 :             CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/'));
    6402           0 :         m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList);
    6403           0 :         return CE_None;
    6404             :     }
    6405             : 
    6406        1779 :     size_t nMetaNameSize =
    6407        1779 :         sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1);
    6408        1779 :     char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize));
    6409             : 
    6410        1779 :     int nbAttr = 0;
    6411        1779 :     NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr));
    6412             : 
    6413        8934 :     for (int l = 0; l < nbAttr; l++)
    6414             :     {
    6415             :         char szAttrName[NC_MAX_NAME + 1];
    6416        7155 :         szAttrName[0] = 0;
    6417        7155 :         NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName));
    6418        7155 :         snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName,
    6419             :                  szAttrName);
    6420             : 
    6421        7155 :         char *pszMetaTemp = nullptr;
    6422        7155 :         if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None)
    6423             :         {
    6424        7154 :             papszMetadata =
    6425        7154 :                 CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp);
    6426        7154 :             CPLFree(pszMetaTemp);
    6427        7154 :             pszMetaTemp = nullptr;
    6428             :         }
    6429             :         else
    6430             :         {
    6431           1 :             CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName);
    6432             :         }
    6433             :     }
    6434             : 
    6435        1779 :     CPLFree(pszVarFullName);
    6436        1779 :     CPLFree(pszMetaName);
    6437             : 
    6438        1779 :     if (var == NC_GLOBAL)
    6439             :     {
    6440             :         // Recurse on sub-groups.
    6441         518 :         int nSubGroups = 0;
    6442         518 :         int *panSubGroupIds = nullptr;
    6443         518 :         NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds);
    6444         547 :         for (int i = 0; i < nSubGroups; i++)
    6445             :         {
    6446          29 :             ReadAttributes(panSubGroupIds[i], var);
    6447             :         }
    6448         518 :         CPLFree(panSubGroupIds);
    6449             :     }
    6450             : 
    6451        1779 :     return CE_None;
    6452             : }
    6453             : 
    6454             : /************************************************************************/
    6455             : /*                netCDFDataset::CreateSubDatasetList()                 */
    6456             : /************************************************************************/
    6457          51 : void netCDFDataset::CreateSubDatasetList(int nGroupId)
    6458             : {
    6459             :     char szVarStdName[NC_MAX_NAME + 1];
    6460          51 :     int *ponDimIds = nullptr;
    6461             :     nc_type nAttype;
    6462             :     size_t nAttlen;
    6463             : 
    6464          51 :     netCDFDataset *poDS = this;
    6465             : 
    6466             :     int nVarCount;
    6467          51 :     nc_inq_nvars(nGroupId, &nVarCount);
    6468             : 
    6469          51 :     const bool bListAllArrays = CPLTestBool(
    6470          51 :         CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    6471             : 
    6472         329 :     for (int nVar = 0; nVar < nVarCount; nVar++)
    6473             :     {
    6474             : 
    6475             :         int nDims;
    6476         278 :         nc_inq_varndims(nGroupId, nVar, &nDims);
    6477             : 
    6478         278 :         if ((bListAllArrays && nDims > 0) || nDims >= 2)
    6479             :         {
    6480         158 :             ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int)));
    6481         158 :             nc_inq_vardimid(nGroupId, nVar, ponDimIds);
    6482             : 
    6483             :             // Create Sub dataset list.
    6484         158 :             CPLString osDim;
    6485         487 :             for (int i = 0; i < nDims; i++)
    6486             :             {
    6487             :                 size_t nDimLen;
    6488         329 :                 nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen);
    6489         329 :                 if (!osDim.empty())
    6490         171 :                     osDim += 'x';
    6491         329 :                 osDim += CPLSPrintf("%d", (int)nDimLen);
    6492             :             }
    6493         158 :             CPLFree(ponDimIds);
    6494             : 
    6495             :             nc_type nVarType;
    6496         158 :             nc_inq_vartype(nGroupId, nVar, &nVarType);
    6497         158 :             const char *pszType = "";
    6498         158 :             switch (nVarType)
    6499             :             {
    6500          38 :                 case NC_BYTE:
    6501          38 :                     pszType = "8-bit integer";
    6502          38 :                     break;
    6503           2 :                 case NC_CHAR:
    6504           2 :                     pszType = "8-bit character";
    6505           2 :                     break;
    6506           6 :                 case NC_SHORT:
    6507           6 :                     pszType = "16-bit integer";
    6508           6 :                     break;
    6509          10 :                 case NC_INT:
    6510          10 :                     pszType = "32-bit integer";
    6511          10 :                     break;
    6512          51 :                 case NC_FLOAT:
    6513          51 :                     pszType = "32-bit floating-point";
    6514          51 :                     break;
    6515          33 :                 case NC_DOUBLE:
    6516          33 :                     pszType = "64-bit floating-point";
    6517          33 :                     break;
    6518           4 :                 case NC_UBYTE:
    6519           4 :                     pszType = "8-bit unsigned integer";
    6520           4 :                     break;
    6521           1 :                 case NC_USHORT:
    6522           1 :                     pszType = "16-bit unsigned integer";
    6523           1 :                     break;
    6524           1 :                 case NC_UINT:
    6525           1 :                     pszType = "32-bit unsigned integer";
    6526           1 :                     break;
    6527           1 :                 case NC_INT64:
    6528           1 :                     pszType = "64-bit integer";
    6529           1 :                     break;
    6530           1 :                 case NC_UINT64:
    6531           1 :                     pszType = "64-bit unsigned integer";
    6532           1 :                     break;
    6533          10 :                 default:
    6534          10 :                     break;
    6535             :             }
    6536             : 
    6537         158 :             char *pszName = nullptr;
    6538         158 :             if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None)
    6539           0 :                 continue;
    6540             : 
    6541         158 :             nSubDatasets++;
    6542             : 
    6543         158 :             nAttlen = 0;
    6544         158 :             nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen);
    6545         316 :             if (nAttlen < sizeof(szVarStdName) &&
    6546         158 :                 nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) ==
    6547             :                     NC_NOERR)
    6548             :             {
    6549          53 :                 szVarStdName[nAttlen] = '\0';
    6550             :             }
    6551             :             else
    6552             :             {
    6553         105 :                 snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName);
    6554             :             }
    6555             : 
    6556             :             char szTemp[NC_MAX_NAME + 1];
    6557         158 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME",
    6558             :                      nSubDatasets);
    6559             : 
    6560         158 :             if (strchr(pszName, ' ') || strchr(pszName, ':'))
    6561             :             {
    6562           1 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6563             :                     poDS->papszSubDatasets, szTemp,
    6564             :                     CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(),
    6565             :                                pszName));
    6566             :             }
    6567             :             else
    6568             :             {
    6569         157 :                 poDS->papszSubDatasets = CSLSetNameValue(
    6570             :                     poDS->papszSubDatasets, szTemp,
    6571             :                     CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(),
    6572             :                                pszName));
    6573             :             }
    6574             : 
    6575         158 :             CPLFree(pszName);
    6576             : 
    6577         158 :             snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC",
    6578             :                      nSubDatasets);
    6579             : 
    6580         158 :             poDS->papszSubDatasets =
    6581         158 :                 CSLSetNameValue(poDS->papszSubDatasets, szTemp,
    6582             :                                 CPLSPrintf("[%s] %s (%s)", osDim.c_str(),
    6583             :                                            szVarStdName, pszType));
    6584             :         }
    6585             :     }
    6586             : 
    6587             :     // Recurse on sub groups.
    6588          51 :     int nSubGroups = 0;
    6589          51 :     int *panSubGroupIds = nullptr;
    6590          51 :     NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds);
    6591          56 :     for (int i = 0; i < nSubGroups; i++)
    6592             :     {
    6593           5 :         CreateSubDatasetList(panSubGroupIds[i]);
    6594             :     }
    6595          51 :     CPLFree(panSubGroupIds);
    6596          51 : }
    6597             : 
    6598             : /************************************************************************/
    6599             : /*                            TestCapability()                          */
    6600             : /************************************************************************/
    6601             : 
    6602         248 : int netCDFDataset::TestCapability(const char *pszCap)
    6603             : {
    6604         248 :     if (EQUAL(pszCap, ODsCCreateLayer))
    6605             :     {
    6606         223 :         return eAccess == GA_Update && nBands == 0 &&
    6607         218 :                (eMultipleLayerBehavior != SINGLE_LAYER ||
    6608         229 :                 this->GetLayerCount() == 0 || bSGSupport);
    6609             :     }
    6610         136 :     else if (EQUAL(pszCap, ODsCZGeometries))
    6611           2 :         return true;
    6612             : 
    6613         134 :     return false;
    6614             : }
    6615             : 
    6616             : /************************************************************************/
    6617             : /*                            GetLayer()                                */
    6618             : /************************************************************************/
    6619             : 
    6620         384 : OGRLayer *netCDFDataset::GetLayer(int nIdx)
    6621             : {
    6622         384 :     if (nIdx < 0 || nIdx >= this->GetLayerCount())
    6623           2 :         return nullptr;
    6624         382 :     return papoLayers[nIdx].get();
    6625             : }
    6626             : 
    6627             : /************************************************************************/
    6628             : /*                            ICreateLayer()                            */
    6629             : /************************************************************************/
    6630             : 
    6631          59 : OGRLayer *netCDFDataset::ICreateLayer(const char *pszName,
    6632             :                                       const OGRGeomFieldDefn *poGeomFieldDefn,
    6633             :                                       CSLConstList papszOptions)
    6634             : {
    6635          59 :     int nLayerCDFId = cdfid;
    6636          59 :     if (!TestCapability(ODsCCreateLayer))
    6637           0 :         return nullptr;
    6638             : 
    6639          59 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    6640             :     const auto poSpatialRef =
    6641          59 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6642             : 
    6643         118 :     CPLString osNetCDFLayerName(pszName);
    6644          59 :     const netCDFWriterConfigLayer *poLayerConfig = nullptr;
    6645          59 :     if (oWriterConfig.m_bIsValid)
    6646             :     {
    6647             :         std::map<CPLString, netCDFWriterConfigLayer>::const_iterator
    6648           2 :             oLayerIter = oWriterConfig.m_oLayers.find(pszName);
    6649           2 :         if (oLayerIter != oWriterConfig.m_oLayers.end())
    6650             :         {
    6651           1 :             poLayerConfig = &(oLayerIter->second);
    6652           1 :             osNetCDFLayerName = poLayerConfig->m_osNetCDFName;
    6653             :         }
    6654             :     }
    6655             : 
    6656          59 :     netCDFDataset *poLayerDataset = nullptr;
    6657          59 :     if (eMultipleLayerBehavior == SEPARATE_FILES)
    6658             :     {
    6659           2 :         char **papszDatasetOptions = nullptr;
    6660           2 :         papszDatasetOptions = CSLSetNameValue(
    6661             :             papszDatasetOptions, "CONFIG_FILE",
    6662           2 :             CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"));
    6663             :         papszDatasetOptions =
    6664           2 :             CSLSetNameValue(papszDatasetOptions, "FORMAT",
    6665           2 :                             CSLFetchNameValue(papszCreationOptions, "FORMAT"));
    6666           2 :         papszDatasetOptions = CSLSetNameValue(
    6667             :             papszDatasetOptions, "WRITE_GDAL_TAGS",
    6668           2 :             CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS"));
    6669             :         const CPLString osLayerFilename(
    6670           2 :             CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc"));
    6671           2 :         CPLAcquireMutex(hNCMutex, 1000.0);
    6672           2 :         poLayerDataset =
    6673           2 :             CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions);
    6674           2 :         CPLReleaseMutex(hNCMutex);
    6675           2 :         CSLDestroy(papszDatasetOptions);
    6676           2 :         if (poLayerDataset == nullptr)
    6677           0 :             return nullptr;
    6678             : 
    6679           2 :         nLayerCDFId = poLayerDataset->cdfid;
    6680           2 :         NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion,
    6681           2 :                            bWriteGDALHistory, "", "Create",
    6682             :                            NCDF_CONVENTIONS_CF_V1_6);
    6683             :     }
    6684          57 :     else if (eMultipleLayerBehavior == SEPARATE_GROUPS)
    6685             :     {
    6686           2 :         SetDefineMode(true);
    6687             : 
    6688           2 :         nLayerCDFId = -1;
    6689           2 :         int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId);
    6690           2 :         NCDF_ERR(status);
    6691           2 :         if (status != NC_NOERR)
    6692           0 :             return nullptr;
    6693             : 
    6694           2 :         NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion,
    6695           2 :                            bWriteGDALHistory, "", "Create",
    6696             :                            NCDF_CONVENTIONS_CF_V1_6);
    6697             :     }
    6698             : 
    6699             :     // Make a clone to workaround a bug in released MapServer versions
    6700             :     // that destroys the passed SRS instead of releasing it .
    6701          59 :     OGRSpatialReference *poSRS = nullptr;
    6702          59 :     if (poSpatialRef)
    6703             :     {
    6704          43 :         poSRS = poSpatialRef->Clone();
    6705          43 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6706             :     }
    6707             :     std::shared_ptr<netCDFLayer> poLayer(
    6708          59 :         new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId,
    6709         118 :                         osNetCDFLayerName, eGType, poSRS));
    6710          59 :     if (poSRS != nullptr)
    6711          43 :         poSRS->Release();
    6712             : 
    6713             :     // Fetch layer creation options coming from config file
    6714          59 :     char **papszNewOptions = CSLDuplicate(papszOptions);
    6715          59 :     if (oWriterConfig.m_bIsValid)
    6716             :     {
    6717           2 :         std::map<CPLString, CPLString>::const_iterator oIter;
    6718           3 :         for (oIter = oWriterConfig.m_oLayerCreationOptions.begin();
    6719           3 :              oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter)
    6720             :         {
    6721             :             papszNewOptions =
    6722           1 :                 CSLSetNameValue(papszNewOptions, oIter->first, oIter->second);
    6723             :         }
    6724           2 :         if (poLayerConfig != nullptr)
    6725             :         {
    6726           3 :             for (oIter = poLayerConfig->m_oLayerCreationOptions.begin();
    6727           3 :                  oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter)
    6728             :             {
    6729           2 :                 papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first,
    6730           2 :                                                   oIter->second);
    6731             :             }
    6732             :         }
    6733             :     }
    6734             : 
    6735          59 :     const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig);
    6736          59 :     CSLDestroy(papszNewOptions);
    6737             : 
    6738          59 :     if (!bRet)
    6739             :     {
    6740           0 :         return nullptr;
    6741             :     }
    6742             : 
    6743          59 :     if (poLayerDataset != nullptr)
    6744           2 :         apoVectorDatasets.push_back(poLayerDataset);
    6745             : 
    6746          59 :     papoLayers.push_back(poLayer);
    6747          59 :     return poLayer.get();
    6748             : }
    6749             : 
    6750             : /************************************************************************/
    6751             : /*                           CloneAttributes()                          */
    6752             : /************************************************************************/
    6753             : 
    6754         137 : bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId,
    6755             :                                     int nDstVarId)
    6756             : {
    6757         137 :     int nAttCount = -1;
    6758         137 :     int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount);
    6759         137 :     NCDF_ERR(status);
    6760             : 
    6761         693 :     for (int i = 0; i < nAttCount; i++)
    6762             :     {
    6763             :         char szName[NC_MAX_NAME + 1];
    6764         556 :         szName[0] = 0;
    6765         556 :         status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName);
    6766         556 :         NCDF_ERR(status);
    6767             : 
    6768             :         status =
    6769         556 :             nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId);
    6770         556 :         NCDF_ERR(status);
    6771         556 :         if (status != NC_NOERR)
    6772           0 :             return false;
    6773             :     }
    6774             : 
    6775         137 :     return true;
    6776             : }
    6777             : 
    6778             : /************************************************************************/
    6779             : /*                          CloneVariableContent()                      */
    6780             : /************************************************************************/
    6781             : 
    6782         121 : bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid,
    6783             :                                          int nSrcVarId, int nDstVarId)
    6784             : {
    6785         121 :     int nVarDimCount = -1;
    6786         121 :     int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount);
    6787         121 :     NCDF_ERR(status);
    6788         121 :     int anDimIds[] = {-1, 1};
    6789         121 :     status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds);
    6790         121 :     NCDF_ERR(status);
    6791         121 :     nc_type nc_datatype = NC_NAT;
    6792         121 :     status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype);
    6793         121 :     NCDF_ERR(status);
    6794         121 :     size_t nTypeSize = 0;
    6795         121 :     switch (nc_datatype)
    6796             :     {
    6797          35 :         case NC_BYTE:
    6798             :         case NC_CHAR:
    6799          35 :             nTypeSize = 1;
    6800          35 :             break;
    6801           4 :         case NC_SHORT:
    6802           4 :             nTypeSize = 2;
    6803           4 :             break;
    6804          24 :         case NC_INT:
    6805          24 :             nTypeSize = 4;
    6806          24 :             break;
    6807           4 :         case NC_FLOAT:
    6808           4 :             nTypeSize = 4;
    6809           4 :             break;
    6810          43 :         case NC_DOUBLE:
    6811          43 :             nTypeSize = 8;
    6812          43 :             break;
    6813           2 :         case NC_UBYTE:
    6814           2 :             nTypeSize = 1;
    6815           2 :             break;
    6816           2 :         case NC_USHORT:
    6817           2 :             nTypeSize = 2;
    6818           2 :             break;
    6819           2 :         case NC_UINT:
    6820           2 :             nTypeSize = 4;
    6821           2 :             break;
    6822           4 :         case NC_INT64:
    6823             :         case NC_UINT64:
    6824           4 :             nTypeSize = 8;
    6825           4 :             break;
    6826           1 :         case NC_STRING:
    6827           1 :             nTypeSize = sizeof(char *);
    6828           1 :             break;
    6829           0 :         default:
    6830             :         {
    6831           0 :             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d",
    6832             :                      nc_datatype);
    6833           0 :             return false;
    6834             :         }
    6835             :     }
    6836             : 
    6837         121 :     size_t nElems = 1;
    6838             :     size_t anStart[NC_MAX_DIMS];
    6839             :     size_t anCount[NC_MAX_DIMS];
    6840         121 :     size_t nRecords = 1;
    6841         261 :     for (int i = 0; i < nVarDimCount; i++)
    6842             :     {
    6843         140 :         anStart[i] = 0;
    6844         140 :         if (i == 0)
    6845             :         {
    6846         116 :             anCount[i] = 1;
    6847         116 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords);
    6848         116 :             NCDF_ERR(status);
    6849             :         }
    6850             :         else
    6851             :         {
    6852          24 :             anCount[i] = 0;
    6853          24 :             status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]);
    6854          24 :             NCDF_ERR(status);
    6855          24 :             nElems *= anCount[i];
    6856             :         }
    6857             :     }
    6858             : 
    6859             :     /* Workaround in some cases a netCDF bug:
    6860             :      * https://github.com/Unidata/netcdf-c/pull/1442 */
    6861         121 :     if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize))
    6862             :     {
    6863         119 :         nElems *= nRecords;
    6864         119 :         anCount[0] = nRecords;
    6865         119 :         nRecords = 1;
    6866             :     }
    6867             : 
    6868         121 :     void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize);
    6869         121 :     if (pBuffer == nullptr)
    6870           0 :         return false;
    6871             : 
    6872         240 :     for (size_t iRecord = 0; iRecord < nRecords; iRecord++)
    6873             :     {
    6874         119 :         anStart[0] = iRecord;
    6875             : 
    6876         119 :         switch (nc_datatype)
    6877             :         {
    6878           5 :             case NC_BYTE:
    6879             :                 status =
    6880           5 :                     nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount,
    6881             :                                       static_cast<signed char *>(pBuffer));
    6882           5 :                 if (!status)
    6883           5 :                     status = nc_put_vara_schar(
    6884             :                         new_cdfid, nDstVarId, anStart, anCount,
    6885             :                         static_cast<signed char *>(pBuffer));
    6886           5 :                 break;
    6887          28 :             case NC_CHAR:
    6888             :                 status =
    6889          28 :                     nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount,
    6890             :                                      static_cast<char *>(pBuffer));
    6891          28 :                 if (!status)
    6892             :                     status =
    6893          28 :                         nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount,
    6894             :                                          static_cast<char *>(pBuffer));
    6895          28 :                 break;
    6896           4 :             case NC_SHORT:
    6897             :                 status =
    6898           4 :                     nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount,
    6899             :                                       static_cast<short *>(pBuffer));
    6900           4 :                 if (!status)
    6901           4 :                     status = nc_put_vara_short(new_cdfid, nDstVarId, anStart,
    6902             :                                                anCount,
    6903             :                                                static_cast<short *>(pBuffer));
    6904           4 :                 break;
    6905          24 :             case NC_INT:
    6906          24 :                 status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount,
    6907             :                                          static_cast<int *>(pBuffer));
    6908          24 :                 if (!status)
    6909             :                     status =
    6910          24 :                         nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount,
    6911             :                                         static_cast<int *>(pBuffer));
    6912          24 :                 break;
    6913           4 :             case NC_FLOAT:
    6914             :                 status =
    6915           4 :                     nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount,
    6916             :                                       static_cast<float *>(pBuffer));
    6917           4 :                 if (!status)
    6918           4 :                     status = nc_put_vara_float(new_cdfid, nDstVarId, anStart,
    6919             :                                                anCount,
    6920             :                                                static_cast<float *>(pBuffer));
    6921           4 :                 break;
    6922          43 :             case NC_DOUBLE:
    6923             :                 status =
    6924          43 :                     nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount,
    6925             :                                        static_cast<double *>(pBuffer));
    6926          43 :                 if (!status)
    6927          43 :                     status = nc_put_vara_double(new_cdfid, nDstVarId, anStart,
    6928             :                                                 anCount,
    6929             :                                                 static_cast<double *>(pBuffer));
    6930          43 :                 break;
    6931           1 :             case NC_STRING:
    6932             :                 status =
    6933           1 :                     nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount,
    6934             :                                        static_cast<char **>(pBuffer));
    6935           1 :                 if (!status)
    6936             :                 {
    6937           1 :                     status = nc_put_vara_string(
    6938             :                         new_cdfid, nDstVarId, anStart, anCount,
    6939             :                         static_cast<const char **>(pBuffer));
    6940           1 :                     nc_free_string(nElems, static_cast<char **>(pBuffer));
    6941             :                 }
    6942           1 :                 break;
    6943             : 
    6944           2 :             case NC_UBYTE:
    6945             :                 status =
    6946           2 :                     nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount,
    6947             :                                       static_cast<unsigned char *>(pBuffer));
    6948           2 :                 if (!status)
    6949           2 :                     status = nc_put_vara_uchar(
    6950             :                         new_cdfid, nDstVarId, anStart, anCount,
    6951             :                         static_cast<unsigned char *>(pBuffer));
    6952           2 :                 break;
    6953           2 :             case NC_USHORT:
    6954             :                 status =
    6955           2 :                     nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount,
    6956             :                                        static_cast<unsigned short *>(pBuffer));
    6957           2 :                 if (!status)
    6958           2 :                     status = nc_put_vara_ushort(
    6959             :                         new_cdfid, nDstVarId, anStart, anCount,
    6960             :                         static_cast<unsigned short *>(pBuffer));
    6961           2 :                 break;
    6962           2 :             case NC_UINT:
    6963             :                 status =
    6964           2 :                     nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount,
    6965             :                                      static_cast<unsigned int *>(pBuffer));
    6966           2 :                 if (!status)
    6967             :                     status =
    6968           2 :                         nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount,
    6969             :                                          static_cast<unsigned int *>(pBuffer));
    6970           2 :                 break;
    6971           2 :             case NC_INT64:
    6972             :                 status =
    6973           2 :                     nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount,
    6974             :                                          static_cast<long long *>(pBuffer));
    6975           2 :                 if (!status)
    6976           2 :                     status = nc_put_vara_longlong(
    6977             :                         new_cdfid, nDstVarId, anStart, anCount,
    6978             :                         static_cast<long long *>(pBuffer));
    6979           2 :                 break;
    6980           2 :             case NC_UINT64:
    6981           2 :                 status = nc_get_vara_ulonglong(
    6982             :                     old_cdfid, nSrcVarId, anStart, anCount,
    6983             :                     static_cast<unsigned long long *>(pBuffer));
    6984           2 :                 if (!status)
    6985           2 :                     status = nc_put_vara_ulonglong(
    6986             :                         new_cdfid, nDstVarId, anStart, anCount,
    6987             :                         static_cast<unsigned long long *>(pBuffer));
    6988           2 :                 break;
    6989           0 :             default:
    6990           0 :                 status = NC_EBADTYPE;
    6991             :         }
    6992             : 
    6993         119 :         NCDF_ERR(status);
    6994         119 :         if (status != NC_NOERR)
    6995             :         {
    6996           0 :             VSIFree(pBuffer);
    6997           0 :             return false;
    6998             :         }
    6999             :     }
    7000             : 
    7001         121 :     VSIFree(pBuffer);
    7002         121 :     return true;
    7003             : }
    7004             : 
    7005             : /************************************************************************/
    7006             : /*                         NCDFIsUnlimitedDim()                         */
    7007             : /************************************************************************/
    7008             : 
    7009          58 : bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId)
    7010             : {
    7011          58 :     if (bIsNC4)
    7012             :     {
    7013          16 :         int nUnlimitedDims = 0;
    7014          16 :         nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr);
    7015          16 :         bool bFound = false;
    7016          16 :         if (nUnlimitedDims)
    7017             :         {
    7018             :             int *panUnlimitedDimIds =
    7019          16 :                 static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims));
    7020          16 :             nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds);
    7021          30 :             for (int i = 0; i < nUnlimitedDims; i++)
    7022             :             {
    7023          22 :                 if (panUnlimitedDimIds[i] == nDimId)
    7024             :                 {
    7025           8 :                     bFound = true;
    7026           8 :                     break;
    7027             :                 }
    7028             :             }
    7029          16 :             CPLFree(panUnlimitedDimIds);
    7030             :         }
    7031          16 :         return bFound;
    7032             :     }
    7033             :     else
    7034             :     {
    7035          42 :         int nUnlimitedDimId = -1;
    7036          42 :         nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId);
    7037          42 :         return nDimId == nUnlimitedDimId;
    7038             :     }
    7039             : }
    7040             : 
    7041             : /************************************************************************/
    7042             : /*                              CloneGrp()                              */
    7043             : /************************************************************************/
    7044             : 
    7045          16 : bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4,
    7046             :                              int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7047             : {
    7048             :     // Clone dimensions
    7049          16 :     int nDimCount = -1;
    7050          16 :     int status = nc_inq_ndims(nOldGrpId, &nDimCount);
    7051          16 :     NCDF_ERR(status);
    7052          16 :     if (nDimCount < 0 || nDimCount > NC_MAX_DIMS)
    7053           0 :         return false;
    7054             :     int anDimIds[NC_MAX_DIMS];
    7055          16 :     int nUnlimiDimID = -1;
    7056          16 :     status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID);
    7057          16 :     NCDF_ERR(status);
    7058          16 :     if (bIsNC4)
    7059             :     {
    7060             :         // In NC4, the dimension ids of a group are not necessarily in
    7061             :         // [0,nDimCount-1] range
    7062           8 :         int nDimCount2 = -1;
    7063           8 :         status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE);
    7064           8 :         NCDF_ERR(status);
    7065           8 :         CPLAssert(nDimCount == nDimCount2);
    7066             :     }
    7067             :     else
    7068             :     {
    7069          36 :         for (int i = 0; i < nDimCount; i++)
    7070          28 :             anDimIds[i] = i;
    7071             :     }
    7072          60 :     for (int i = 0; i < nDimCount; i++)
    7073             :     {
    7074             :         char szDimName[NC_MAX_NAME + 1];
    7075          44 :         szDimName[0] = 0;
    7076          44 :         size_t nLen = 0;
    7077          44 :         const int nDimId = anDimIds[i];
    7078          44 :         status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen);
    7079          44 :         NCDF_ERR(status);
    7080          44 :         if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId))
    7081          16 :             nLen = NC_UNLIMITED;
    7082          28 :         else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId)
    7083          13 :             nLen = nNewSize;
    7084          44 :         int nNewDimId = -1;
    7085          44 :         status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId);
    7086          44 :         NCDF_ERR(status);
    7087          44 :         CPLAssert(nDimId == nNewDimId);
    7088          44 :         if (status != NC_NOERR)
    7089             :         {
    7090           0 :             return false;
    7091             :         }
    7092             :     }
    7093             : 
    7094             :     // Clone main attributes
    7095          16 :     if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL))
    7096             :     {
    7097           0 :         return false;
    7098             :     }
    7099             : 
    7100             :     // Clone variable definitions
    7101          16 :     int nVarCount = -1;
    7102          16 :     status = nc_inq_nvars(nOldGrpId, &nVarCount);
    7103          16 :     NCDF_ERR(status);
    7104             : 
    7105         137 :     for (int i = 0; i < nVarCount; i++)
    7106             :     {
    7107             :         char szVarName[NC_MAX_NAME + 1];
    7108         121 :         szVarName[0] = 0;
    7109         121 :         status = nc_inq_varname(nOldGrpId, i, szVarName);
    7110         121 :         NCDF_ERR(status);
    7111         121 :         nc_type nc_datatype = NC_NAT;
    7112         121 :         status = nc_inq_vartype(nOldGrpId, i, &nc_datatype);
    7113         121 :         NCDF_ERR(status);
    7114         121 :         int nVarDimCount = -1;
    7115         121 :         status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount);
    7116         121 :         NCDF_ERR(status);
    7117         121 :         status = nc_inq_vardimid(nOldGrpId, i, anDimIds);
    7118         121 :         NCDF_ERR(status);
    7119         121 :         int nNewVarId = -1;
    7120         121 :         status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount,
    7121             :                             anDimIds, &nNewVarId);
    7122         121 :         NCDF_ERR(status);
    7123         121 :         CPLAssert(i == nNewVarId);
    7124         121 :         if (status != NC_NOERR)
    7125             :         {
    7126           0 :             return false;
    7127             :         }
    7128             : 
    7129         121 :         if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i))
    7130             :         {
    7131           0 :             return false;
    7132             :         }
    7133             :     }
    7134             : 
    7135          16 :     status = nc_enddef(nNewGrpId);
    7136          16 :     NCDF_ERR(status);
    7137          16 :     if (status != NC_NOERR)
    7138             :     {
    7139           0 :         return false;
    7140             :     }
    7141             : 
    7142             :     // Clone variable content
    7143         137 :     for (int i = 0; i < nVarCount; i++)
    7144             :     {
    7145         121 :         if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i))
    7146             :         {
    7147           0 :             return false;
    7148             :         }
    7149             :     }
    7150             : 
    7151          16 :     return true;
    7152             : }
    7153             : 
    7154             : /************************************************************************/
    7155             : /*                              GrowDim()                               */
    7156             : /************************************************************************/
    7157             : 
    7158          13 : bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize)
    7159             : {
    7160             :     int nCreationMode;
    7161             :     // Set nCreationMode based on eFormat.
    7162          13 :     switch (eFormat)
    7163             :     {
    7164             : #ifdef NETCDF_HAS_NC2
    7165           0 :         case NCDF_FORMAT_NC2:
    7166           0 :             nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET;
    7167           0 :             break;
    7168             : #endif
    7169           5 :         case NCDF_FORMAT_NC4:
    7170           5 :             nCreationMode = NC_CLOBBER | NC_NETCDF4;
    7171           5 :             break;
    7172           0 :         case NCDF_FORMAT_NC4C:
    7173           0 :             nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
    7174           0 :             break;
    7175           8 :         case NCDF_FORMAT_NC:
    7176             :         default:
    7177           8 :             nCreationMode = NC_CLOBBER;
    7178           8 :             break;
    7179             :     }
    7180             : 
    7181          13 :     int new_cdfid = -1;
    7182          26 :     CPLString osTmpFilename(osFilename + ".tmp");
    7183          26 :     CPLString osFilenameForNCCreate(osTmpFilename);
    7184             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7185             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7186             :     {
    7187             :         char *pszTemp =
    7188             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    7189             :         osFilenameForNCCreate = pszTemp;
    7190             :         CPLFree(pszTemp);
    7191             :     }
    7192             : #endif
    7193          13 :     int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid);
    7194          13 :     NCDF_ERR(status);
    7195          13 :     if (status != NC_NOERR)
    7196           0 :         return false;
    7197             : 
    7198          13 :     if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId,
    7199             :                   nDimIdToGrow, nNewSize))
    7200             :     {
    7201           0 :         GDAL_nc_close(new_cdfid);
    7202           0 :         return false;
    7203             :     }
    7204             : 
    7205          13 :     int nGroupCount = 0;
    7206          26 :     std::vector<CPLString> oListGrpName;
    7207          31 :     if (eFormat == NCDF_FORMAT_NC4 &&
    7208          18 :         nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR &&
    7209           5 :         nGroupCount > 0)
    7210             :     {
    7211             :         int *panGroupIds =
    7212           2 :             static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount));
    7213           2 :         status = nc_inq_grps(cdfid, nullptr, panGroupIds);
    7214           2 :         NCDF_ERR(status);
    7215           5 :         for (int i = 0; i < nGroupCount; i++)
    7216             :         {
    7217             :             char szGroupName[NC_MAX_NAME + 1];
    7218           3 :             szGroupName[0] = 0;
    7219           3 :             NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName));
    7220           3 :             int nNewGrpId = -1;
    7221           3 :             status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId);
    7222           3 :             NCDF_ERR(status);
    7223           3 :             if (status != NC_NOERR)
    7224             :             {
    7225           0 :                 CPLFree(panGroupIds);
    7226           0 :                 GDAL_nc_close(new_cdfid);
    7227           0 :                 return false;
    7228             :             }
    7229           3 :             if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId,
    7230             :                           nDimIdToGrow, nNewSize))
    7231             :             {
    7232           0 :                 CPLFree(panGroupIds);
    7233           0 :                 GDAL_nc_close(new_cdfid);
    7234           0 :                 return false;
    7235             :             }
    7236             :         }
    7237           2 :         CPLFree(panGroupIds);
    7238             : 
    7239           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7240             :         {
    7241           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7242           3 :             if (poLayer)
    7243             :             {
    7244             :                 char szGroupName[NC_MAX_NAME + 1];
    7245           3 :                 szGroupName[0] = 0;
    7246           3 :                 status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName);
    7247           3 :                 NCDF_ERR(status);
    7248           3 :                 oListGrpName.push_back(szGroupName);
    7249             :             }
    7250             :         }
    7251             :     }
    7252             : 
    7253          13 :     GDAL_nc_close(cdfid);
    7254          13 :     cdfid = -1;
    7255          13 :     GDAL_nc_close(new_cdfid);
    7256             : 
    7257          26 :     CPLString osOriFilename(osFilename + ".ori");
    7258          26 :     if (VSIRename(osFilename, osOriFilename) != 0 ||
    7259          13 :         VSIRename(osTmpFilename, osFilename) != 0)
    7260             :     {
    7261           0 :         CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed");
    7262           0 :         return false;
    7263             :     }
    7264          13 :     VSIUnlink(osOriFilename);
    7265             : 
    7266          26 :     CPLString osFilenameForNCOpen(osFilename);
    7267             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    7268             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    7269             :     {
    7270             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    7271             :         osFilenameForNCOpen = pszTemp;
    7272             :         CPLFree(pszTemp);
    7273             :     }
    7274             : #endif
    7275          13 :     status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid);
    7276          13 :     NCDF_ERR(status);
    7277          13 :     if (status != NC_NOERR)
    7278           0 :         return false;
    7279          13 :     bDefineMode = false;
    7280             : 
    7281          13 :     if (!oListGrpName.empty())
    7282             :     {
    7283           5 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7284             :         {
    7285           3 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7286           3 :             if (poLayer)
    7287             :             {
    7288           3 :                 int nNewLayerCDFID = -1;
    7289           3 :                 status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(),
    7290             :                                      &nNewLayerCDFID);
    7291           3 :                 NCDF_ERR(status);
    7292           3 :                 poLayer->SetCDFID(nNewLayerCDFID);
    7293             :             }
    7294             :         }
    7295             :     }
    7296             :     else
    7297             :     {
    7298          22 :         for (int i = 0; i < this->GetLayerCount(); i++)
    7299             :         {
    7300          11 :             auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get());
    7301          11 :             if (poLayer)
    7302          11 :                 poLayer->SetCDFID(cdfid);
    7303             :         }
    7304             :     }
    7305             : 
    7306          13 :     return true;
    7307             : }
    7308             : 
    7309             : #ifdef ENABLE_NCDUMP
    7310             : 
    7311             : /************************************************************************/
    7312             : /*                      netCDFDatasetCreateTempFile()                   */
    7313             : /************************************************************************/
    7314             : 
    7315             : /* Create a netCDF file from a text dump (format of ncdump) */
    7316             : /* Mostly to easy fuzzing of the driver, while still generating valid */
    7317             : /* netCDF files. */
    7318             : /* Note: not all data types are supported ! */
    7319           4 : bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat,
    7320             :                                  const char *pszTmpFilename, VSILFILE *fpSrc)
    7321             : {
    7322           4 :     CPL_IGNORE_RET_VAL(eFormat);
    7323           4 :     int nCreateMode = NC_CLOBBER;
    7324           4 :     if (eFormat == NCDF_FORMAT_NC4)
    7325           1 :         nCreateMode |= NC_NETCDF4;
    7326           3 :     else if (eFormat == NCDF_FORMAT_NC4C)
    7327           0 :         nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
    7328           4 :     int nCdfId = -1;
    7329           4 :     int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId);
    7330           4 :     if (status != NC_NOERR)
    7331             :     {
    7332           0 :         return false;
    7333             :     }
    7334           4 :     VSIFSeekL(fpSrc, 0, SEEK_SET);
    7335             :     const char *pszLine;
    7336           4 :     constexpr int SECTION_NONE = 0;
    7337           4 :     constexpr int SECTION_DIMENSIONS = 1;
    7338           4 :     constexpr int SECTION_VARIABLES = 2;
    7339           4 :     constexpr int SECTION_DATA = 3;
    7340           4 :     int nActiveSection = SECTION_NONE;
    7341           8 :     std::map<CPLString, int> oMapDimToId;
    7342           8 :     std::map<int, int> oMapDimIdToDimLen;
    7343           8 :     std::map<CPLString, int> oMapVarToId;
    7344           8 :     std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId;
    7345           8 :     std::map<int, int> oMapVarIdToType;
    7346           4 :     std::set<CPLString> oSetAttrDefined;
    7347           4 :     oMapVarToId[""] = -1;
    7348           4 :     size_t nTotalVarSize = 0;
    7349         208 :     while ((pszLine = CPLReadLineL(fpSrc)) != nullptr)
    7350             :     {
    7351         204 :         if (STARTS_WITH(pszLine, "dimensions:") &&
    7352             :             nActiveSection == SECTION_NONE)
    7353             :         {
    7354           4 :             nActiveSection = SECTION_DIMENSIONS;
    7355             :         }
    7356         200 :         else if (STARTS_WITH(pszLine, "variables:") &&
    7357             :                  nActiveSection == SECTION_DIMENSIONS)
    7358             :         {
    7359           4 :             nActiveSection = SECTION_VARIABLES;
    7360             :         }
    7361         196 :         else if (STARTS_WITH(pszLine, "data:") &&
    7362             :                  nActiveSection == SECTION_VARIABLES)
    7363             :         {
    7364           4 :             nActiveSection = SECTION_DATA;
    7365           4 :             status = nc_enddef(nCdfId);
    7366           4 :             if (status != NC_NOERR)
    7367             :             {
    7368           0 :                 CPLDebug("netCDF", "nc_enddef() failed: %s",
    7369             :                          nc_strerror(status));
    7370             :             }
    7371             :         }
    7372         192 :         else if (nActiveSection == SECTION_DIMENSIONS)
    7373             :         {
    7374           9 :             char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0);
    7375           9 :             if (CSLCount(papszTokens) == 2)
    7376             :             {
    7377           9 :                 const char *pszDimName = papszTokens[0];
    7378           9 :                 bool bValidName = true;
    7379           9 :                 if (STARTS_WITH(pszDimName, "_nc4_non_coord_"))
    7380             :                 {
    7381             :                     // This is an internal netcdf prefix. Using it may
    7382             :                     // cause memory leaks.
    7383           0 :                     bValidName = false;
    7384             :                 }
    7385           9 :                 if (!bValidName)
    7386             :                 {
    7387           0 :                     CPLDebug("netCDF",
    7388             :                              "nc_def_dim(%s) failed: invalid name found",
    7389             :                              pszDimName);
    7390           0 :                     CSLDestroy(papszTokens);
    7391           0 :                     continue;
    7392             :                 }
    7393             : 
    7394             :                 const bool bIsASCII =
    7395           9 :                     CPLIsASCII(pszDimName, static_cast<size_t>(-1));
    7396           9 :                 if (!bIsASCII)
    7397             :                 {
    7398             :                     // Workaround https://github.com/Unidata/netcdf-c/pull/450
    7399           0 :                     CPLDebug("netCDF",
    7400             :                              "nc_def_dim(%s) failed: rejected because "
    7401             :                              "of non-ASCII characters",
    7402             :                              pszDimName);
    7403           0 :                     CSLDestroy(papszTokens);
    7404           0 :                     continue;
    7405             :                 }
    7406           9 :                 int nDimSize = EQUAL(papszTokens[1], "UNLIMITED")
    7407             :                                    ? NC_UNLIMITED
    7408           9 :                                    : atoi(papszTokens[1]);
    7409           9 :                 if (nDimSize >= 1000)
    7410           1 :                     nDimSize = 1000;  // to avoid very long processing
    7411           9 :                 if (nDimSize >= 0)
    7412             :                 {
    7413           9 :                     int nDimId = -1;
    7414           9 :                     status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId);
    7415           9 :                     if (status != NC_NOERR)
    7416             :                     {
    7417           0 :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s",
    7418             :                                  pszDimName, nDimSize, nc_strerror(status));
    7419             :                     }
    7420             :                     else
    7421             :                     {
    7422             : #ifdef DEBUG_VERBOSE
    7423             :                         CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded",
    7424             :                                  pszDimName, nDimSize, pszLine);
    7425             : #endif
    7426           9 :                         oMapDimToId[pszDimName] = nDimId;
    7427           9 :                         oMapDimIdToDimLen[nDimId] = nDimSize;
    7428             :                     }
    7429             :                 }
    7430             :             }
    7431           9 :             CSLDestroy(papszTokens);
    7432             :         }
    7433         183 :         else if (nActiveSection == SECTION_VARIABLES)
    7434             :         {
    7435         390 :             while (*pszLine == ' ' || *pszLine == '\t')
    7436         249 :                 pszLine++;
    7437         141 :             const char *pszColumn = strchr(pszLine, ':');
    7438         141 :             const char *pszEqual = strchr(pszLine, '=');
    7439         141 :             if (pszColumn == nullptr)
    7440             :             {
    7441          21 :                 char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0);
    7442          21 :                 if (CSLCount(papszTokens) >= 2)
    7443             :                 {
    7444          17 :                     const char *pszVarName = papszTokens[1];
    7445          17 :                     bool bValidName = true;
    7446          17 :                     if (STARTS_WITH(pszVarName, "_nc4_non_coord_"))
    7447             :                     {
    7448             :                         // This is an internal netcdf prefix. Using it may
    7449             :                         // cause memory leaks.
    7450           0 :                         bValidName = false;
    7451             :                     }
    7452         138 :                     for (int i = 0; pszVarName[i]; i++)
    7453             :                     {
    7454         121 :                         if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') ||
    7455          28 :                               (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') ||
    7456           9 :                               (pszVarName[i] >= '0' && pszVarName[i] <= '9') ||
    7457           6 :                               pszVarName[i] == '_'))
    7458             :                         {
    7459           0 :                             bValidName = false;
    7460             :                         }
    7461             :                     }
    7462          17 :                     if (!bValidName)
    7463             :                     {
    7464           0 :                         CPLDebug(
    7465             :                             "netCDF",
    7466             :                             "nc_def_var(%s) failed: illegal character found",
    7467             :                             pszVarName);
    7468           0 :                         CSLDestroy(papszTokens);
    7469           0 :                         continue;
    7470             :                     }
    7471          17 :                     if (oMapVarToId.find(pszVarName) != oMapVarToId.end())
    7472             :                     {
    7473           0 :                         CPLDebug("netCDF",
    7474             :                                  "nc_def_var(%s) failed: already defined",
    7475             :                                  pszVarName);
    7476           0 :                         CSLDestroy(papszTokens);
    7477           0 :                         continue;
    7478             :                     }
    7479          17 :                     const char *pszVarType = papszTokens[0];
    7480          17 :                     int nc_datatype = NC_BYTE;
    7481          17 :                     size_t nDataTypeSize = 1;
    7482          17 :                     if (EQUAL(pszVarType, "char"))
    7483             :                     {
    7484           6 :                         nc_datatype = NC_CHAR;
    7485           6 :                         nDataTypeSize = 1;
    7486             :                     }
    7487          11 :                     else if (EQUAL(pszVarType, "byte"))
    7488             :                     {
    7489           3 :                         nc_datatype = NC_BYTE;
    7490           3 :                         nDataTypeSize = 1;
    7491             :                     }
    7492           8 :                     else if (EQUAL(pszVarType, "short"))
    7493             :                     {
    7494           0 :                         nc_datatype = NC_SHORT;
    7495           0 :                         nDataTypeSize = 2;
    7496             :                     }
    7497           8 :                     else if (EQUAL(pszVarType, "int"))
    7498             :                     {
    7499           0 :                         nc_datatype = NC_INT;
    7500           0 :                         nDataTypeSize = 4;
    7501             :                     }
    7502           8 :                     else if (EQUAL(pszVarType, "float"))
    7503             :                     {
    7504           0 :                         nc_datatype = NC_FLOAT;
    7505           0 :                         nDataTypeSize = 4;
    7506             :                     }
    7507           8 :                     else if (EQUAL(pszVarType, "double"))
    7508             :                     {
    7509           8 :                         nc_datatype = NC_DOUBLE;
    7510           8 :                         nDataTypeSize = 8;
    7511             :                     }
    7512           0 :                     else if (EQUAL(pszVarType, "ubyte"))
    7513             :                     {
    7514           0 :                         nc_datatype = NC_UBYTE;
    7515           0 :                         nDataTypeSize = 1;
    7516             :                     }
    7517           0 :                     else if (EQUAL(pszVarType, "ushort"))
    7518             :                     {
    7519           0 :                         nc_datatype = NC_USHORT;
    7520           0 :                         nDataTypeSize = 2;
    7521             :                     }
    7522           0 :                     else if (EQUAL(pszVarType, "uint"))
    7523             :                     {
    7524           0 :                         nc_datatype = NC_UINT;
    7525           0 :                         nDataTypeSize = 4;
    7526             :                     }
    7527           0 :                     else if (EQUAL(pszVarType, "int64"))
    7528             :                     {
    7529           0 :                         nc_datatype = NC_INT64;
    7530           0 :                         nDataTypeSize = 8;
    7531             :                     }
    7532           0 :                     else if (EQUAL(pszVarType, "uint64"))
    7533             :                     {
    7534           0 :                         nc_datatype = NC_UINT64;
    7535           0 :                         nDataTypeSize = 8;
    7536             :                     }
    7537             : 
    7538          17 :                     int nDims = CSLCount(papszTokens) - 2;
    7539          17 :                     if (nDims >= 32)
    7540             :                     {
    7541             :                         // The number of dimensions in a netCDFv4 file is
    7542             :                         // limited by #define H5S_MAX_RANK    32
    7543             :                         // but libnetcdf doesn't check that...
    7544           0 :                         CPLDebug("netCDF",
    7545             :                                  "nc_def_var(%s) failed: too many dimensions",
    7546             :                                  pszVarName);
    7547           0 :                         CSLDestroy(papszTokens);
    7548           0 :                         continue;
    7549             :                     }
    7550          17 :                     std::vector<int> aoDimIds;
    7551          17 :                     bool bFailed = false;
    7552          17 :                     size_t nSize = 1;
    7553          35 :                     for (int i = 0; i < nDims; i++)
    7554             :                     {
    7555          18 :                         const char *pszDimName = papszTokens[2 + i];
    7556          18 :                         if (oMapDimToId.find(pszDimName) == oMapDimToId.end())
    7557             :                         {
    7558           0 :                             bFailed = true;
    7559           0 :                             break;
    7560             :                         }
    7561          18 :                         const int nDimId = oMapDimToId[pszDimName];
    7562          18 :                         aoDimIds.push_back(nDimId);
    7563             : 
    7564          18 :                         const size_t nDimSize = oMapDimIdToDimLen[nDimId];
    7565          18 :                         if (nDimSize != 0)
    7566             :                         {
    7567          18 :                             if (nSize >
    7568          18 :                                 std::numeric_limits<size_t>::max() / nDimSize)
    7569             :                             {
    7570           0 :                                 bFailed = true;
    7571           0 :                                 break;
    7572             :                             }
    7573             :                             else
    7574             :                             {
    7575          18 :                                 nSize *= nDimSize;
    7576             :                             }
    7577             :                         }
    7578             :                     }
    7579          17 :                     if (bFailed)
    7580             :                     {
    7581           0 :                         CPLDebug("netCDF",
    7582             :                                  "nc_def_var(%s) failed: unknown dimension(s)",
    7583             :                                  pszVarName);
    7584           0 :                         CSLDestroy(papszTokens);
    7585           0 :                         continue;
    7586             :                     }
    7587          17 :                     if (nSize > 100U * 1024 * 1024 / nDataTypeSize)
    7588             :                     {
    7589           0 :                         CPLDebug("netCDF",
    7590             :                                  "nc_def_var(%s) failed: too large data",
    7591             :                                  pszVarName);
    7592           0 :                         CSLDestroy(papszTokens);
    7593           0 :                         continue;
    7594             :                     }
    7595          17 :                     if (nTotalVarSize >
    7596          34 :                             std::numeric_limits<size_t>::max() - nSize ||
    7597          17 :                         nTotalVarSize + nSize > 100 * 1024 * 1024)
    7598             :                     {
    7599           0 :                         CPLDebug("netCDF",
    7600             :                                  "nc_def_var(%s) failed: too large data",
    7601             :                                  pszVarName);
    7602           0 :                         CSLDestroy(papszTokens);
    7603           0 :                         continue;
    7604             :                     }
    7605          17 :                     nTotalVarSize += nSize;
    7606             : 
    7607          17 :                     int nVarId = -1;
    7608             :                     status =
    7609          30 :                         nc_def_var(nCdfId, pszVarName, nc_datatype, nDims,
    7610          13 :                                    (nDims) ? &aoDimIds[0] : nullptr, &nVarId);
    7611          17 :                     if (status != NC_NOERR)
    7612             :                     {
    7613           0 :                         CPLDebug("netCDF", "nc_def_var(%s) failed: %s",
    7614             :                                  pszVarName, nc_strerror(status));
    7615             :                     }
    7616             :                     else
    7617             :                     {
    7618             : #ifdef DEBUG_VERBOSE
    7619             :                         CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded",
    7620             :                                  pszVarName, pszLine);
    7621             : #endif
    7622          17 :                         oMapVarToId[pszVarName] = nVarId;
    7623          17 :                         oMapVarIdToType[nVarId] = nc_datatype;
    7624          17 :                         oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds);
    7625             :                     }
    7626             :                 }
    7627          21 :                 CSLDestroy(papszTokens);
    7628             :             }
    7629         120 :             else if (pszEqual != nullptr && pszEqual - pszColumn > 0)
    7630             :             {
    7631         116 :                 CPLString osVarName(pszLine, pszColumn - pszLine);
    7632         116 :                 CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1);
    7633         116 :                 osAttrName.Trim();
    7634         116 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7635             :                 {
    7636           0 :                     CPLDebug("netCDF",
    7637             :                              "nc_put_att(%s:%s) failed: "
    7638             :                              "no corresponding variable",
    7639             :                              osVarName.c_str(), osAttrName.c_str());
    7640           0 :                     continue;
    7641             :                 }
    7642         116 :                 bool bValidName = true;
    7643        1743 :                 for (size_t i = 0; i < osAttrName.size(); i++)
    7644             :                 {
    7645        1865 :                     if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') ||
    7646         238 :                           (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') ||
    7647         158 :                           (osAttrName[i] >= '0' && osAttrName[i] <= '9') ||
    7648         158 :                           osAttrName[i] == '_'))
    7649             :                     {
    7650           0 :                         bValidName = false;
    7651             :                     }
    7652             :                 }
    7653         116 :                 if (!bValidName)
    7654             :                 {
    7655           0 :                     CPLDebug(
    7656             :                         "netCDF",
    7657             :                         "nc_put_att(%s:%s) failed: illegal character found",
    7658             :                         osVarName.c_str(), osAttrName.c_str());
    7659           0 :                     continue;
    7660             :                 }
    7661         116 :                 if (oSetAttrDefined.find(osVarName + ":" + osAttrName) !=
    7662         232 :                     oSetAttrDefined.end())
    7663             :                 {
    7664           0 :                     CPLDebug("netCDF",
    7665             :                              "nc_put_att(%s:%s) failed: already defined",
    7666             :                              osVarName.c_str(), osAttrName.c_str());
    7667           0 :                     continue;
    7668             :                 }
    7669             : 
    7670         116 :                 const int nVarId = oMapVarToId[osVarName];
    7671         116 :                 const char *pszValue = pszEqual + 1;
    7672         232 :                 while (*pszValue == ' ')
    7673         116 :                     pszValue++;
    7674             : 
    7675         116 :                 status = NC_EBADTYPE;
    7676         116 :                 if (*pszValue == '"')
    7677             :                 {
    7678             :                     // For _FillValue, the attribute type should match
    7679             :                     // the variable type. Leaks memory with NC4 otherwise
    7680          74 :                     if (osAttrName == "_FillValue")
    7681             :                     {
    7682           0 :                         CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7683             :                                  osVarName.c_str(), osAttrName.c_str(),
    7684             :                                  nc_strerror(status));
    7685           0 :                         continue;
    7686             :                     }
    7687             : 
    7688             :                     // Unquote and unescape string value
    7689          74 :                     CPLString osVal(pszValue + 1);
    7690         222 :                     while (!osVal.empty())
    7691             :                     {
    7692         222 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7693             :                         {
    7694         148 :                             osVal.pop_back();
    7695             :                         }
    7696          74 :                         else if (osVal.back() == '"')
    7697             :                         {
    7698          74 :                             osVal.pop_back();
    7699          74 :                             break;
    7700             :                         }
    7701             :                         else
    7702             :                         {
    7703           0 :                             break;
    7704             :                         }
    7705             :                     }
    7706          74 :                     osVal.replaceAll("\\\"", '"');
    7707          74 :                     status = nc_put_att_text(nCdfId, nVarId, osAttrName,
    7708             :                                              osVal.size(), osVal.c_str());
    7709             :                 }
    7710             :                 else
    7711             :                 {
    7712          84 :                     CPLString osVal(pszValue);
    7713         126 :                     while (!osVal.empty())
    7714             :                     {
    7715         126 :                         if (osVal.back() == ';' || osVal.back() == ' ')
    7716             :                         {
    7717          84 :                             osVal.pop_back();
    7718             :                         }
    7719             :                         else
    7720             :                         {
    7721          42 :                             break;
    7722             :                         }
    7723             :                     }
    7724          42 :                     int nc_datatype = -1;
    7725          42 :                     if (!osVal.empty() && osVal.back() == 'b')
    7726             :                     {
    7727           3 :                         nc_datatype = NC_BYTE;
    7728           3 :                         osVal.pop_back();
    7729             :                     }
    7730          39 :                     else if (!osVal.empty() && osVal.back() == 's')
    7731             :                     {
    7732           3 :                         nc_datatype = NC_SHORT;
    7733           3 :                         osVal.pop_back();
    7734             :                     }
    7735          42 :                     if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER)
    7736             :                     {
    7737           7 :                         if (nc_datatype < 0)
    7738           4 :                             nc_datatype = NC_INT;
    7739             :                     }
    7740          35 :                     else if (CPLGetValueType(osVal) == CPL_VALUE_REAL)
    7741             :                     {
    7742          32 :                         nc_datatype = NC_DOUBLE;
    7743             :                     }
    7744             :                     else
    7745             :                     {
    7746           3 :                         nc_datatype = -1;
    7747             :                     }
    7748             : 
    7749             :                     // For _FillValue, check that the attribute type matches
    7750             :                     // the variable type. Leaks memory with NC4 otherwise
    7751          42 :                     if (osAttrName == "_FillValue")
    7752             :                     {
    7753           6 :                         if (nVarId < 0 ||
    7754           3 :                             nc_datatype != oMapVarIdToType[nVarId])
    7755             :                         {
    7756           0 :                             nc_datatype = -1;
    7757             :                         }
    7758             :                     }
    7759             : 
    7760          42 :                     if (nc_datatype == NC_BYTE)
    7761             :                     {
    7762             :                         signed char chVal =
    7763           3 :                             static_cast<signed char>(atoi(osVal));
    7764           3 :                         status = nc_put_att_schar(nCdfId, nVarId, osAttrName,
    7765             :                                                   NC_BYTE, 1, &chVal);
    7766             :                     }
    7767          39 :                     else if (nc_datatype == NC_SHORT)
    7768             :                     {
    7769           0 :                         short nVal = static_cast<short>(atoi(osVal));
    7770           0 :                         status = nc_put_att_short(nCdfId, nVarId, osAttrName,
    7771             :                                                   NC_SHORT, 1, &nVal);
    7772             :                     }
    7773          39 :                     else if (nc_datatype == NC_INT)
    7774             :                     {
    7775           4 :                         int nVal = static_cast<int>(atoi(osVal));
    7776           4 :                         status = nc_put_att_int(nCdfId, nVarId, osAttrName,
    7777             :                                                 NC_INT, 1, &nVal);
    7778             :                     }
    7779          35 :                     else if (nc_datatype == NC_DOUBLE)
    7780             :                     {
    7781          32 :                         double dfVal = CPLAtof(osVal);
    7782          32 :                         status = nc_put_att_double(nCdfId, nVarId, osAttrName,
    7783             :                                                    NC_DOUBLE, 1, &dfVal);
    7784             :                     }
    7785             :                 }
    7786         116 :                 if (status != NC_NOERR)
    7787             :                 {
    7788           3 :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s",
    7789             :                              osVarName.c_str(), osAttrName.c_str(),
    7790             :                              nc_strerror(status));
    7791             :                 }
    7792             :                 else
    7793             :                 {
    7794         113 :                     oSetAttrDefined.insert(osVarName + ":" + osAttrName);
    7795             : #ifdef DEBUG_VERBOSE
    7796             :                     CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded",
    7797             :                              osVarName.c_str(), osAttrName.c_str(), pszLine);
    7798             : #endif
    7799             :                 }
    7800             :             }
    7801             :         }
    7802          42 :         else if (nActiveSection == SECTION_DATA)
    7803             :         {
    7804          55 :             while (*pszLine == ' ' || *pszLine == '\t')
    7805          17 :                 pszLine++;
    7806          38 :             const char *pszEqual = strchr(pszLine, '=');
    7807          38 :             if (pszEqual)
    7808             :             {
    7809          17 :                 CPLString osVarName(pszLine, pszEqual - pszLine);
    7810          17 :                 osVarName.Trim();
    7811          17 :                 if (oMapVarToId.find(osVarName) == oMapVarToId.end())
    7812           0 :                     continue;
    7813          17 :                 const int nVarId = oMapVarToId[osVarName];
    7814          17 :                 CPLString osAccVal(pszEqual + 1);
    7815          17 :                 osAccVal.Trim();
    7816         153 :                 while (osAccVal.empty() || osAccVal.back() != ';')
    7817             :                 {
    7818         136 :                     pszLine = CPLReadLineL(fpSrc);
    7819         136 :                     if (pszLine == nullptr)
    7820           0 :                         break;
    7821         272 :                     CPLString osVal(pszLine);
    7822         136 :                     osVal.Trim();
    7823         136 :                     osAccVal += osVal;
    7824             :                 }
    7825          17 :                 if (pszLine == nullptr)
    7826           0 :                     break;
    7827          17 :                 osAccVal.pop_back();
    7828             : 
    7829             :                 const std::vector<int> aoDimIds =
    7830          34 :                     oMapVarIdToVectorOfDimId[nVarId];
    7831          17 :                 size_t nSize = 1;
    7832          34 :                 std::vector<size_t> aoStart, aoEdge;
    7833          17 :                 aoStart.resize(aoDimIds.size());
    7834          17 :                 aoEdge.resize(aoDimIds.size());
    7835          35 :                 for (size_t i = 0; i < aoDimIds.size(); ++i)
    7836             :                 {
    7837          18 :                     const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]];
    7838          36 :                     if (nDimSize != 0 &&
    7839          18 :                         nSize > std::numeric_limits<size_t>::max() / nDimSize)
    7840             :                     {
    7841           0 :                         nSize = 0;
    7842             :                     }
    7843             :                     else
    7844             :                     {
    7845          18 :                         nSize *= nDimSize;
    7846             :                     }
    7847          18 :                     aoStart[i] = 0;
    7848          18 :                     aoEdge[i] = nDimSize;
    7849             :                 }
    7850             : 
    7851          17 :                 status = NC_EBADTYPE;
    7852          17 :                 if (nSize == 0)
    7853             :                 {
    7854             :                     // Might happen with a unlimited dimension
    7855             :                 }
    7856          17 :                 else if (oMapVarIdToType[nVarId] == NC_DOUBLE)
    7857             :                 {
    7858           8 :                     if (!aoStart.empty())
    7859             :                     {
    7860             :                         char **papszTokens =
    7861           8 :                             CSLTokenizeString2(osAccVal, " ,;", 0);
    7862           8 :                         size_t nTokens = CSLCount(papszTokens);
    7863           8 :                         if (nTokens >= nSize)
    7864             :                         {
    7865             :                             double *padfVals = static_cast<double *>(
    7866           8 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(double)));
    7867           8 :                             if (padfVals)
    7868             :                             {
    7869         132 :                                 for (size_t i = 0; i < nSize; i++)
    7870             :                                 {
    7871         124 :                                     padfVals[i] = CPLAtof(papszTokens[i]);
    7872             :                                 }
    7873           8 :                                 status = nc_put_vara_double(
    7874           8 :                                     nCdfId, nVarId, &aoStart[0], &aoEdge[0],
    7875             :                                     padfVals);
    7876           8 :                                 VSIFree(padfVals);
    7877             :                             }
    7878             :                         }
    7879           8 :                         CSLDestroy(papszTokens);
    7880             :                     }
    7881             :                 }
    7882           9 :                 else if (oMapVarIdToType[nVarId] == NC_BYTE)
    7883             :                 {
    7884           3 :                     if (!aoStart.empty())
    7885             :                     {
    7886             :                         char **papszTokens =
    7887           3 :                             CSLTokenizeString2(osAccVal, " ,;", 0);
    7888           3 :                         size_t nTokens = CSLCount(papszTokens);
    7889           3 :                         if (nTokens >= nSize)
    7890             :                         {
    7891             :                             signed char *panVals = static_cast<signed char *>(
    7892           3 :                                 VSI_CALLOC_VERBOSE(nSize, sizeof(signed char)));
    7893           3 :                             if (panVals)
    7894             :                             {
    7895        1203 :                                 for (size_t i = 0; i < nSize; i++)
    7896             :                                 {
    7897        1200 :                                     panVals[i] = static_cast<signed char>(
    7898        1200 :                                         atoi(papszTokens[i]));
    7899             :                                 }
    7900           3 :                                 status = nc_put_vara_schar(nCdfId, nVarId,
    7901           3 :                                                            &aoStart[0],
    7902           3 :                                                            &aoEdge[0], panVals);
    7903           3 :                                 VSIFree(panVals);
    7904             :                             }
    7905             :                         }
    7906           3 :                         CSLDestroy(papszTokens);
    7907             :                     }
    7908             :                 }
    7909           6 :                 else if (oMapVarIdToType[nVarId] == NC_CHAR)
    7910             :                 {
    7911           6 :                     if (aoStart.size() == 2)
    7912             :                     {
    7913           4 :                         std::vector<CPLString> aoStrings;
    7914           2 :                         bool bInString = false;
    7915           4 :                         CPLString osCurString;
    7916         935 :                         for (size_t i = 0; i < osAccVal.size();)
    7917             :                         {
    7918         933 :                             if (!bInString)
    7919             :                             {
    7920           8 :                                 if (osAccVal[i] == '"')
    7921             :                                 {
    7922           4 :                                     bInString = true;
    7923           4 :                                     osCurString.clear();
    7924             :                                 }
    7925           8 :                                 i++;
    7926             :                             }
    7927         926 :                             else if (osAccVal[i] == '\\' &&
    7928         926 :                                      i + 1 < osAccVal.size() &&
    7929           1 :                                      osAccVal[i + 1] == '"')
    7930             :                             {
    7931           1 :                                 osCurString += '"';
    7932           1 :                                 i += 2;
    7933             :                             }
    7934         924 :                             else if (osAccVal[i] == '"')
    7935             :                             {
    7936           4 :                                 aoStrings.push_back(osCurString);
    7937           4 :                                 osCurString.clear();
    7938           4 :                                 bInString = false;
    7939           4 :                                 i++;
    7940             :                             }
    7941             :                             else
    7942             :                             {
    7943         920 :                                 osCurString += osAccVal[i];
    7944         920 :                                 i++;
    7945             :                             }
    7946             :                         }
    7947           2 :                         const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]];
    7948           2 :                         const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]];
    7949           2 :                         size_t nIters = aoStrings.size();
    7950           2 :                         if (nIters > nRecords)
    7951           0 :                             nIters = nRecords;
    7952           6 :                         for (size_t i = 0; i < nIters; i++)
    7953             :                         {
    7954             :                             size_t anIndex[2];
    7955           4 :                             anIndex[0] = i;
    7956           4 :                             anIndex[1] = 0;
    7957             :                             size_t anCount[2];
    7958           4 :                             anCount[0] = 1;
    7959           4 :                             anCount[1] = aoStrings[i].size();
    7960           4 :                             if (anCount[1] > nWidth)
    7961           0 :                                 anCount[1] = nWidth;
    7962             :                             status =
    7963           4 :                                 nc_put_vara_text(nCdfId, nVarId, anIndex,
    7964           4 :                                                  anCount, aoStrings[i].c_str());
    7965           4 :                             if (status != NC_NOERR)
    7966           0 :                                 break;
    7967             :                         }
    7968             :                     }
    7969             :                 }
    7970          17 :                 if (status != NC_NOERR)
    7971             :                 {
    7972           4 :                     CPLDebug("netCDF", "nc_put_var_(%s) failed: %s",
    7973             :                              osVarName.c_str(), nc_strerror(status));
    7974             :                 }
    7975             :             }
    7976             :         }
    7977             :     }
    7978             : 
    7979           4 :     GDAL_nc_close(nCdfId);
    7980           4 :     return true;
    7981             : }
    7982             : 
    7983             : #endif  // ENABLE_NCDUMP
    7984             : 
    7985             : /************************************************************************/
    7986             : /*                                Open()                                */
    7987             : /************************************************************************/
    7988             : 
    7989         674 : GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo)
    7990             : 
    7991             : {
    7992             : #ifdef NCDF_DEBUG
    7993             :     CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]",
    7994             :              poOpenInfo->pszFilename);
    7995             : #endif
    7996             : 
    7997             :     // Does this appear to be a netcdf file?
    7998         674 :     NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE;
    7999         674 :     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8000             :     {
    8001         615 :         eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true);
    8002             : #ifdef NCDF_DEBUG
    8003             :         CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat);
    8004             : #endif
    8005             :         // Note: not calling Identify() directly, because we want the file type.
    8006             :         // Only support NCDF_FORMAT* formats.
    8007         615 :         if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat ||
    8008           2 :             NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)
    8009             :         {
    8010             :             // ok
    8011             :         }
    8012           2 :         else if (eTmpFormat == NCDF_FORMAT_HDF4 &&
    8013           0 :                  poOpenInfo->IsSingleAllowedDriver("netCDF"))
    8014             :         {
    8015             :             // ok
    8016             :         }
    8017             :         else
    8018             :         {
    8019           2 :             return nullptr;
    8020             :         }
    8021             :     }
    8022             :     else
    8023             :     {
    8024             : #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    8025             :         // We don't necessarily want to catch bugs in libnetcdf ...
    8026             :         if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr))
    8027             :         {
    8028             :             return nullptr;
    8029             :         }
    8030             : #endif
    8031             :     }
    8032             : 
    8033         672 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
    8034             :     {
    8035         182 :         return OpenMultiDim(poOpenInfo);
    8036             :     }
    8037             : 
    8038         980 :     CPLMutexHolderD(&hNCMutex);
    8039             : 
    8040         490 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    8041             :         // GDALDataset own mutex.
    8042         490 :     netCDFDataset *poDS = new netCDFDataset();
    8043         490 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    8044         490 :     CPLAcquireMutex(hNCMutex, 1000.0);
    8045             : 
    8046         490 :     poDS->SetDescription(poOpenInfo->pszFilename);
    8047             : 
    8048             :     // Check if filename start with NETCDF: tag.
    8049         490 :     bool bTreatAsSubdataset = false;
    8050         980 :     CPLString osSubdatasetName;
    8051             : 
    8052             : #ifdef ENABLE_NCDUMP
    8053         490 :     const char *pszHeader =
    8054             :         reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    8055         490 :     if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") &&
    8056           3 :         strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:"))
    8057             :     {
    8058             :         // By default create a temporary file that will be destroyed,
    8059             :         // unless NETCDF_TMP_FILE is defined. Can be useful to see which
    8060             :         // netCDF file has been generated from a potential fuzzed input.
    8061           3 :         poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", "");
    8062           3 :         if (poDS->osFilename.empty())
    8063             :         {
    8064           3 :             poDS->bFileToDestroyAtClosing = true;
    8065           3 :             poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp");
    8066             :         }
    8067           3 :         if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename,
    8068             :                                          poOpenInfo->fpL))
    8069             :         {
    8070           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8071             :                 // deadlock with GDALDataset own mutex.
    8072           0 :             delete poDS;
    8073           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8074           0 :             return nullptr;
    8075             :         }
    8076           3 :         bTreatAsSubdataset = false;
    8077           3 :         poDS->eFormat = eTmpFormat;
    8078             :     }
    8079             :     else
    8080             : #endif
    8081             : 
    8082         487 :         if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:"))
    8083             :     {
    8084             :         char **papszName =
    8085          59 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    8086             :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
    8087             : 
    8088         118 :         if (CSLCount(papszName) >= 3 &&
    8089          59 :             ((strlen(papszName[1]) == 1 && /* D:\\bla */
    8090           0 :               (papszName[2][0] == '/' || papszName[2][0] == '\\')) ||
    8091          59 :              EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") ||
    8092          59 :              EQUAL(papszName[1], "/vsicurl/http") ||
    8093          59 :              EQUAL(papszName[1], "/vsicurl/https") ||
    8094          59 :              EQUAL(papszName[1], "/vsicurl_streaming/http") ||
    8095          59 :              EQUAL(papszName[1], "/vsicurl_streaming/https")))
    8096             :         {
    8097           0 :             const int nCountBefore = CSLCount(papszName);
    8098           0 :             CPLString osTmp = papszName[1];
    8099           0 :             osTmp += ':';
    8100           0 :             osTmp += papszName[2];
    8101           0 :             CPLFree(papszName[1]);
    8102           0 :             CPLFree(papszName[2]);
    8103           0 :             papszName[1] = CPLStrdup(osTmp);
    8104           0 :             memmove(papszName + 2, papszName + 3,
    8105           0 :                     (nCountBefore - 2) * sizeof(char *));
    8106             :         }
    8107             : 
    8108          59 :         if (CSLCount(papszName) == 3)
    8109             :         {
    8110          59 :             poDS->osFilename = papszName[1];
    8111          59 :             osSubdatasetName = papszName[2];
    8112          59 :             bTreatAsSubdataset = true;
    8113          59 :             CSLDestroy(papszName);
    8114             :         }
    8115           0 :         else if (CSLCount(papszName) == 2)
    8116             :         {
    8117           0 :             poDS->osFilename = papszName[1];
    8118           0 :             osSubdatasetName = "";
    8119           0 :             bTreatAsSubdataset = false;
    8120           0 :             CSLDestroy(papszName);
    8121             :         }
    8122             :         else
    8123             :         {
    8124           0 :             CSLDestroy(papszName);
    8125           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8126             :                 // deadlock with GDALDataset own mutex.
    8127           0 :             delete poDS;
    8128           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8129           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8130             :                      "Failed to parse NETCDF: prefix string into expected 2, 3 "
    8131             :                      "or 4 fields.");
    8132           0 :             return nullptr;
    8133             :         }
    8134             : 
    8135         118 :         if (!STARTS_WITH(poDS->osFilename, "http://") &&
    8136          59 :             !STARTS_WITH(poDS->osFilename, "https://"))
    8137             :         {
    8138             :             // Identify Format from real file, with bCheckExt=FALSE.
    8139             :             GDALOpenInfo *poOpenInfo2 =
    8140          59 :                 new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly);
    8141          59 :             poDS->eFormat =
    8142          59 :                 netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false);
    8143          59 :             delete poOpenInfo2;
    8144          59 :             if (NCDF_FORMAT_NONE == poDS->eFormat ||
    8145          59 :                 NCDF_FORMAT_UNKNOWN == poDS->eFormat)
    8146             :             {
    8147           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8148             :                     // deadlock with GDALDataset own mutex.
    8149           0 :                 delete poDS;
    8150           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    8151           0 :                 return nullptr;
    8152             :             }
    8153             :         }
    8154             :     }
    8155             :     else
    8156             :     {
    8157         428 :         poDS->osFilename = poOpenInfo->pszFilename;
    8158         428 :         bTreatAsSubdataset = false;
    8159         428 :         poDS->eFormat = eTmpFormat;
    8160             :     }
    8161             : 
    8162             : // Try opening the dataset.
    8163             : #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD)
    8164             :     CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)",
    8165             :              poDS->osFilename.c_str());
    8166             : #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD)
    8167             :     CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str());
    8168             : #endif
    8169         490 :     int cdfid = -1;
    8170         490 :     const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
    8171             :                           ? NC_WRITE
    8172             :                           : NC_NOWRITE;
    8173         980 :     CPLString osFilenameForNCOpen(poDS->osFilename);
    8174             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    8175             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    8176             :     {
    8177             :         char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP");
    8178             :         osFilenameForNCOpen = pszTemp;
    8179             :         CPLFree(pszTemp);
    8180             :     }
    8181             : #endif
    8182         490 :     int status2 = -1;
    8183             : 
    8184             : #ifdef ENABLE_UFFD
    8185         490 :     cpl_uffd_context *pCtx = nullptr;
    8186             : #endif
    8187             : 
    8188         505 :     if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") &&
    8189          15 :         poOpenInfo->eAccess == GA_ReadOnly)
    8190             :     {
    8191          15 :         vsi_l_offset nLength = 0;
    8192          15 :         poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb");
    8193          15 :         if (poDS->fpVSIMEM)
    8194             :         {
    8195             :             // We assume that the file will not be modified. If it is, then
    8196             :             // pabyBuffer might become invalid.
    8197             :             GByte *pabyBuffer =
    8198          15 :                 VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false);
    8199          15 :             if (pabyBuffer)
    8200             :             {
    8201          15 :                 status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen),
    8202             :                                       nMode, static_cast<size_t>(nLength),
    8203             :                                       pabyBuffer, &cdfid);
    8204             :             }
    8205             :         }
    8206             :     }
    8207             :     else
    8208             :     {
    8209             :         const bool bVsiFile =
    8210         475 :             !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi"));
    8211             : #ifdef ENABLE_UFFD
    8212         475 :         bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly);
    8213         475 :         void *pVma = nullptr;
    8214         475 :         uint64_t nVmaSize = 0;
    8215             : 
    8216         475 :         if (bVsiFile)
    8217             :         {
    8218           2 :             if (bReadOnly)
    8219             :             {
    8220           2 :                 if (CPLIsUserFaultMappingSupported())
    8221             :                 {
    8222           2 :                     pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma,
    8223             :                                                      &nVmaSize);
    8224             :                 }
    8225             :                 else
    8226             :                 {
    8227           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    8228             :                              "Opening a /vsi file with the netCDF driver "
    8229             :                              "requires Linux userfaultfd to be available. "
    8230             :                              "If running from Docker, "
    8231             :                              "--security-opt seccomp=unconfined might be "
    8232             :                              "needed.%s",
    8233           0 :                              ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8234           0 :                                poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8235           0 :                               GDALGetDriverByName("HDF5"))
    8236             :                                  ? " Or you may set the GDAL_SKIP=netCDF "
    8237             :                                    "configuration option to force the use of "
    8238             :                                    "the HDF5 driver."
    8239             :                                  : "");
    8240             :                 }
    8241             :             }
    8242             :             else
    8243             :             {
    8244           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    8245             :                          "Opening a /vsi file with the netCDF driver is only "
    8246             :                          "supported in read-only mode");
    8247             :             }
    8248             :         }
    8249         475 :         if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0)
    8250             :         {
    8251             :             // netCDF code, at least for netCDF 4.7.0, is confused by filenames
    8252             :             // like /vsicurl/http[s]://example.com/foo.nc, so just pass the
    8253             :             // final part
    8254           2 :             status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode,
    8255             :                                   static_cast<size_t>(nVmaSize), pVma, &cdfid);
    8256             :         }
    8257             :         else
    8258         473 :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8259             : #else
    8260             :         if (bVsiFile)
    8261             :         {
    8262             :             CPLError(
    8263             :                 CE_Failure, CPLE_AppDefined,
    8264             :                 "Opening a /vsi file with the netCDF driver requires Linux "
    8265             :                 "userfaultfd to be available.%s",
    8266             :                 ((poDS->eFormat == NCDF_FORMAT_NC4 ||
    8267             :                   poDS->eFormat == NCDF_FORMAT_HDF5) &&
    8268             :                  GDALGetDriverByName("HDF5"))
    8269             :                     ? " Or you may set the GDAL_SKIP=netCDF "
    8270             :                       "configuration option to force the use of the HDF5 "
    8271             :                       "driver."
    8272             :                     : "");
    8273             :             status2 = NC_EIO;
    8274             :         }
    8275             :         else
    8276             :         {
    8277             :             status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid);
    8278             :         }
    8279             : #endif
    8280             :     }
    8281         490 :     if (status2 != NC_NOERR)
    8282             :     {
    8283             : #ifdef NCDF_DEBUG
    8284             :         CPLDebug("GDAL_netCDF", "error opening");
    8285             : #endif
    8286           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8287             :             // with GDALDataset own mutex.
    8288           0 :         delete poDS;
    8289           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8290           0 :         return nullptr;
    8291             :     }
    8292             : #ifdef NCDF_DEBUG
    8293             :     CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid);
    8294             : #endif
    8295             : 
    8296             : #if defined(ENABLE_NCDUMP) && !defined(_WIN32)
    8297             :     // Try to destroy the temporary file right now on Unix
    8298         490 :     if (poDS->bFileToDestroyAtClosing)
    8299             :     {
    8300           3 :         if (VSIUnlink(poDS->osFilename) == 0)
    8301             :         {
    8302           3 :             poDS->bFileToDestroyAtClosing = false;
    8303             :         }
    8304             :     }
    8305             : #endif
    8306             : 
    8307             :     // Is this a real netCDF file?
    8308             :     int ndims;
    8309             :     int ngatts;
    8310             :     int nvars;
    8311             :     int unlimdimid;
    8312         490 :     int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid);
    8313         490 :     if (status != NC_NOERR)
    8314             :     {
    8315           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8316             :             // with GDALDataset own mutex.
    8317           0 :         delete poDS;
    8318           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8319           0 :         return nullptr;
    8320             :     }
    8321             : 
    8322             :     // Get file type from netcdf.
    8323         490 :     int nTmpFormat = NCDF_FORMAT_NONE;
    8324         490 :     status = nc_inq_format(cdfid, &nTmpFormat);
    8325         490 :     if (status != NC_NOERR)
    8326             :     {
    8327           0 :         NCDF_ERR(status);
    8328             :     }
    8329             :     else
    8330             :     {
    8331         490 :         CPLDebug("GDAL_netCDF",
    8332             :                  "driver detected file type=%d, libnetcdf detected type=%d",
    8333         490 :                  poDS->eFormat, nTmpFormat);
    8334         490 :         if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat)
    8335             :         {
    8336             :             // Warn if file detection conflicts with that from libnetcdf
    8337             :             // except for NC4C, which we have no way of detecting initially.
    8338          26 :             if (nTmpFormat != NCDF_FORMAT_NC4C &&
    8339          13 :                 !STARTS_WITH(poDS->osFilename, "http://") &&
    8340           0 :                 !STARTS_WITH(poDS->osFilename, "https://"))
    8341             :             {
    8342           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    8343             :                          "NetCDF driver detected file type=%d, but libnetcdf "
    8344             :                          "detected type=%d",
    8345           0 :                          poDS->eFormat, nTmpFormat);
    8346             :             }
    8347          13 :             CPLDebug("GDAL_netCDF", "setting file type to %d, was %d",
    8348          13 :                      nTmpFormat, poDS->eFormat);
    8349          13 :             poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat);
    8350             :         }
    8351             :     }
    8352             : 
    8353             :     // Does the request variable exist?
    8354         490 :     if (bTreatAsSubdataset)
    8355             :     {
    8356             :         int dummy;
    8357          59 :         if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy,
    8358          59 :                                &dummy) != CE_None)
    8359             :         {
    8360           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    8361             :                      "%s is a netCDF file, but %s is not a variable.",
    8362             :                      poOpenInfo->pszFilename, osSubdatasetName.c_str());
    8363             : 
    8364           0 :             GDAL_nc_close(cdfid);
    8365             : #ifdef ENABLE_UFFD
    8366           0 :             NETCDF_UFFD_UNMAP(pCtx);
    8367             : #endif
    8368           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8369             :                 // deadlock with GDALDataset own mutex.
    8370           0 :             delete poDS;
    8371           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8372           0 :             return nullptr;
    8373             :         }
    8374             :     }
    8375             : 
    8376             :     // Figure out whether or not the listed dataset has support for simple
    8377             :     // geometries (CF-1.8)
    8378         490 :     poDS->nCFVersion = nccfdriver::getCFVersion(cdfid);
    8379         490 :     bool bHasSimpleGeometries = false;  // but not necessarily valid
    8380         490 :     if (poDS->nCFVersion >= 1.8)
    8381             :     {
    8382          75 :         bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid);
    8383          75 :         if (bHasSimpleGeometries)
    8384             :         {
    8385          67 :             poDS->bSGSupport = true;
    8386          67 :             poDS->vcdf.enableFullVirtualMode();
    8387             :         }
    8388             :     }
    8389             : 
    8390             :     char szConventions[NC_MAX_NAME + 1];
    8391         490 :     szConventions[0] = '\0';
    8392         490 :     nc_type nAttype = NC_NAT;
    8393         490 :     size_t nAttlen = 0;
    8394         490 :     nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen);
    8395         980 :     if (nAttlen >= sizeof(szConventions) ||
    8396         490 :         nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) !=
    8397             :             NC_NOERR)
    8398             :     {
    8399          56 :         CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute");
    8400             :         // Note that 'Conventions' is always capital 'C' in CF spec.
    8401             :     }
    8402             :     else
    8403             :     {
    8404         434 :         szConventions[nAttlen] = '\0';
    8405             :     }
    8406             : 
    8407             :     // Create band information objects.
    8408         490 :     CPLDebug("GDAL_netCDF", "var_count = %d", nvars);
    8409             : 
    8410             :     // Create a corresponding GDALDataset.
    8411             :     // Create Netcdf Subdataset if filename as NETCDF tag.
    8412         490 :     poDS->cdfid = cdfid;
    8413             : #ifdef ENABLE_UFFD
    8414         490 :     poDS->pCtx = pCtx;
    8415             : #endif
    8416         490 :     poDS->eAccess = poOpenInfo->eAccess;
    8417         490 :     poDS->bDefineMode = false;
    8418             : 
    8419         490 :     poDS->ReadAttributes(cdfid, NC_GLOBAL);
    8420             : 
    8421             :     // Identify coordinate and boundary variables that we should
    8422             :     // ignore as Raster Bands.
    8423         490 :     char **papszIgnoreVars = nullptr;
    8424         490 :     NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars);
    8425             :     // Filter variables to keep only valid 2+D raster bands and vector fields.
    8426         490 :     int nRasterVars = 0;
    8427         490 :     int nIgnoredVars = 0;
    8428         490 :     int nGroupID = -1;
    8429         490 :     int nVarID = -1;
    8430             : 
    8431             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
    8432         980 :         oMap2DDimsToGroupAndVar;
    8433        1131 :     if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8434         151 :         STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata,
    8435             :                                          "NC_GLOBAL#mission_name", ""),
    8436           1 :                     "Sentinel 3") &&
    8437           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8438             :                                    "NC_GLOBAL#altimeter_sensor_name", ""),
    8439         641 :               "SRAL") &&
    8440           1 :         EQUAL(CSLFetchNameValueDef(poDS->papszMetadata,
    8441             :                                    "NC_GLOBAL#radiometer_sensor_name", ""),
    8442             :               "MWR"))
    8443             :     {
    8444           1 :         if (poDS->eAccess == GA_Update)
    8445             :         {
    8446           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8447             :                 // deadlock with GDALDataset own mutex.
    8448           0 :             delete poDS;
    8449           0 :             return nullptr;
    8450             :         }
    8451           1 :         poDS->ProcessSentinel3_SRAL_MWR();
    8452             :     }
    8453             :     else
    8454             :     {
    8455         489 :         poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0,
    8456         639 :                          (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8457         150 :                              !bHasSimpleGeometries,
    8458             :                          papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID,
    8459             :                          &nIgnoredVars, oMap2DDimsToGroupAndVar);
    8460             :     }
    8461         490 :     CSLDestroy(papszIgnoreVars);
    8462             : 
    8463             :     // Case where there is no raster variable
    8464         490 :     if (nRasterVars == 0 && !bTreatAsSubdataset)
    8465             :     {
    8466         119 :         poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8467         119 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8468             :             // with GDALDataset own mutex.
    8469         119 :         poDS->TryLoadXML();
    8470             :         // If the dataset has been opened in raster mode only, exit
    8471         119 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    8472           9 :             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    8473             :         {
    8474           4 :             delete poDS;
    8475           4 :             poDS = nullptr;
    8476             :         }
    8477             :         // Otherwise if the dataset is opened in vector mode, that there is
    8478             :         // no vector layer and we are in read-only, exit too.
    8479         115 :         else if (poDS->GetLayerCount() == 0 &&
    8480         123 :                  (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    8481           8 :                  poOpenInfo->eAccess == GA_ReadOnly)
    8482             :         {
    8483           8 :             delete poDS;
    8484           8 :             poDS = nullptr;
    8485             :         }
    8486         119 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8487         119 :         return poDS;
    8488             :     }
    8489             : 
    8490             :     // We have more than one variable with 2 dimensions in the
    8491             :     // file, then treat this as a subdataset container dataset.
    8492         371 :     bool bSeveralVariablesAsBands = false;
    8493         371 :     const bool bListAllArrays = CPLTestBool(
    8494         371 :         CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO"));
    8495         371 :     if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset))
    8496             :     {
    8497          28 :         if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS",
    8498          34 :                          false) &&
    8499           6 :             oMap2DDimsToGroupAndVar.size() == 1)
    8500             :         {
    8501           6 :             std::tie(nGroupID, nVarID) =
    8502          12 :                 oMap2DDimsToGroupAndVar.begin()->second.front();
    8503           6 :             bSeveralVariablesAsBands = true;
    8504             :         }
    8505             :         else
    8506             :         {
    8507          22 :             poDS->CreateSubDatasetList(cdfid);
    8508          22 :             poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8509          22 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8510             :                 // deadlock with GDALDataset own mutex.
    8511          22 :             poDS->TryLoadXML();
    8512          22 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8513          22 :             return poDS;
    8514             :         }
    8515             :     }
    8516             : 
    8517             :     // If we are not treating things as a subdataset, then capture
    8518             :     // the name of the single available variable as the subdataset.
    8519         349 :     if (!bTreatAsSubdataset)
    8520             :     {
    8521         290 :         char *pszVarName = nullptr;
    8522         290 :         NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName));
    8523         290 :         osSubdatasetName = (pszVarName != nullptr ? pszVarName : "");
    8524         290 :         CPLFree(pszVarName);
    8525             :     }
    8526             : 
    8527             :     // We have ignored at least one variable, so we should report them
    8528             :     // as subdatasets for reference.
    8529         349 :     if (nIgnoredVars > 0 && !bTreatAsSubdataset)
    8530             :     {
    8531          24 :         CPLDebug("GDAL_netCDF",
    8532             :                  "As %d variables were ignored, creating subdataset list "
    8533             :                  "for reference. Variable #%d [%s] is the main variable",
    8534             :                  nIgnoredVars, nVarID, osSubdatasetName.c_str());
    8535          24 :         poDS->CreateSubDatasetList(cdfid);
    8536             :     }
    8537             : 
    8538             :     // Open the NETCDF subdataset NETCDF:"filename":subdataset.
    8539         349 :     int var = -1;
    8540         349 :     NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var);
    8541             :     // Now we can forget the root cdfid and only use the selected group.
    8542         349 :     cdfid = nGroupID;
    8543         349 :     int nd = 0;
    8544         349 :     nc_inq_varndims(cdfid, var, &nd);
    8545             : 
    8546         349 :     poDS->m_anDimIds.resize(nd);
    8547             : 
    8548             :     // X, Y, Z position in array
    8549         698 :     std::vector<int> anBandDimPos(nd);
    8550             : 
    8551         349 :     nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data());
    8552             : 
    8553             :     // Check if somebody tried to pass a variable with less than 1D.
    8554         349 :     if (nd < 1)
    8555             :     {
    8556           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8557             :                  "Variable has %d dimension(s) - not supported.", nd);
    8558           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8559             :             // with GDALDataset own mutex.
    8560           0 :         delete poDS;
    8561           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8562           0 :         return nullptr;
    8563             :     }
    8564             : 
    8565             :     // CF-1 Convention
    8566             :     //
    8567             :     // Dimensions to appear in the relative order T, then Z, then Y,
    8568             :     // then X  to the file. All other dimensions should, whenever
    8569             :     // possible, be placed to the left of the spatiotemporal
    8570             :     // dimensions.
    8571             : 
    8572             :     // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order
    8573             :     // Ideally we should detect for other ordering and act accordingly
    8574             :     // Only done if file has Conventions=CF-* and only prints warning
    8575             :     // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only
    8576             :     // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT
    8577             :     const bool bCheckDims =
    8578         698 :         CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) &&
    8579         349 :         STARTS_WITH_CI(szConventions, "CF");
    8580             : 
    8581         349 :     if (nd >= 2 && bCheckDims)
    8582             :     {
    8583         265 :         char szDimName1[NC_MAX_NAME + 1] = {};
    8584         265 :         char szDimName2[NC_MAX_NAME + 1] = {};
    8585         265 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1);
    8586         265 :         NCDF_ERR(status);
    8587         265 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2);
    8588         265 :         NCDF_ERR(status);
    8589         422 :         if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false &&
    8590         157 :             NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false)
    8591             :         {
    8592           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8593             :                      "dimension #%d (%s) is not a Longitude/X dimension.",
    8594             :                      nd - 1, szDimName1);
    8595             :         }
    8596         422 :         if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false &&
    8597         157 :             NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false)
    8598             :         {
    8599           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    8600             :                      "dimension #%d (%s) is not a Latitude/Y dimension.",
    8601             :                      nd - 2, szDimName2);
    8602             :         }
    8603         265 :         if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) ||
    8604         267 :              NCDFIsVarProjectionX(cdfid, -1, szDimName2)) &&
    8605           2 :             (NCDFIsVarLatitude(cdfid, -1, szDimName1) ||
    8606           0 :              NCDFIsVarProjectionY(cdfid, -1, szDimName1)))
    8607             :         {
    8608           2 :             poDS->bSwitchedXY = true;
    8609             :         }
    8610         265 :         if (nd >= 3)
    8611             :         {
    8612          52 :             char szDimName3[NC_MAX_NAME + 1] = {};
    8613             :             status =
    8614          52 :                 nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3);
    8615          52 :             NCDF_ERR(status);
    8616          52 :             if (nd >= 4)
    8617             :             {
    8618          13 :                 char szDimName4[NC_MAX_NAME + 1] = {};
    8619             :                 status =
    8620          13 :                     nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4);
    8621          13 :                 NCDF_ERR(status);
    8622          13 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false)
    8623             :                 {
    8624           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8625             :                              "dimension #%d (%s) is not a Vertical dimension.",
    8626             :                              nd - 3, szDimName3);
    8627             :                 }
    8628          13 :                 if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false)
    8629             :                 {
    8630           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8631             :                              "dimension #%d (%s) is not a Time dimension.",
    8632             :                              nd - 4, szDimName4);
    8633             :                 }
    8634             :             }
    8635             :             else
    8636             :             {
    8637          75 :                 if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false &&
    8638          36 :                     NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false)
    8639             :                 {
    8640           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    8641             :                              "dimension #%d (%s) is not a "
    8642             :                              "Time or Vertical dimension.",
    8643             :                              nd - 3, szDimName3);
    8644             :                 }
    8645             :             }
    8646             :         }
    8647             :     }
    8648             : 
    8649             :     // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/),
    8650             :     // dimension order is downtrack, crosstrack, bands
    8651         349 :     bool bYXBandOrder = false;
    8652         349 :     if (nd == 3)
    8653             :     {
    8654          44 :         char szDimName[NC_MAX_NAME + 1] = {};
    8655          44 :         status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName);
    8656          44 :         NCDF_ERR(status);
    8657          44 :         bYXBandOrder =
    8658          44 :             strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0;
    8659             :     }
    8660             : 
    8661             :     // Get X dimensions information.
    8662             :     size_t xdim;
    8663         349 :     poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1];
    8664         349 :     nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim);
    8665             : 
    8666             :     // Get Y dimension information.
    8667             :     size_t ydim;
    8668         349 :     if (nd >= 2)
    8669             :     {
    8670         345 :         poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2];
    8671         345 :         nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim);
    8672             :     }
    8673             :     else
    8674             :     {
    8675           4 :         poDS->nYDimID = -1;
    8676           4 :         ydim = 1;
    8677             :     }
    8678             : 
    8679         349 :     if (xdim > INT_MAX || ydim > INT_MAX)
    8680             :     {
    8681           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    8682             :                  "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB,
    8683             :                  static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim));
    8684           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8685             :             // with GDALDataset own mutex.
    8686           0 :         delete poDS;
    8687           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8688           0 :         return nullptr;
    8689             :     }
    8690             : 
    8691         349 :     poDS->nRasterXSize = static_cast<int>(xdim);
    8692         349 :     poDS->nRasterYSize = static_cast<int>(ydim);
    8693             : 
    8694         349 :     unsigned int k = 0;
    8695        1122 :     for (int j = 0; j < nd; j++)
    8696             :     {
    8697         773 :         if (poDS->m_anDimIds[j] == poDS->nXDimID)
    8698             :         {
    8699         349 :             anBandDimPos[0] = j;  // Save Position of XDim
    8700         349 :             k++;
    8701             :         }
    8702         773 :         if (poDS->m_anDimIds[j] == poDS->nYDimID)
    8703             :         {
    8704         345 :             anBandDimPos[1] = j;  // Save Position of YDim
    8705         345 :             k++;
    8706             :         }
    8707             :     }
    8708             :     // X and Y Dimension Ids were not found!
    8709         349 :     if ((nd >= 2 && k != 2) || (nd == 1 && k != 1))
    8710             :     {
    8711           0 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    8712             :             // with GDALDataset own mutex.
    8713           0 :         delete poDS;
    8714           0 :         CPLAcquireMutex(hNCMutex, 1000.0);
    8715           0 :         return nullptr;
    8716             :     }
    8717             : 
    8718             :     // Read Metadata for this variable.
    8719             : 
    8720             :     // Should disable as is also done at band level, except driver needs the
    8721             :     // variables as metadata (e.g. projection).
    8722         349 :     poDS->ReadAttributes(cdfid, var);
    8723             : 
    8724             :     // Read Metadata for each dimension.
    8725         349 :     int *panDimIds = nullptr;
    8726         349 :     NCDFGetVisibleDims(cdfid, &ndims, &panDimIds);
    8727             :     // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like
    8728             :     // in NetCDF-3 because we see only the dimensions of the selected group
    8729             :     // and its parents.
    8730             :     // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs
    8731             :     // [0..max(panDimIds)], but they are not all useful so we fill names
    8732             :     // of useless dims with empty string.
    8733         349 :     if (panDimIds)
    8734             :     {
    8735         349 :         const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims);
    8736         349 :         std::set<int> oSetExistingDimIds;
    8737        1158 :         for (int i = 0; i < ndims; i++)
    8738             :         {
    8739         809 :             oSetExistingDimIds.insert(panDimIds[i]);
    8740             :         }
    8741         349 :         std::set<int> oSetDimIdsUsedByVar;
    8742        1122 :         for (int i = 0; i < nd; i++)
    8743             :         {
    8744         773 :             oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]);
    8745             :         }
    8746        1160 :         for (int j = 0; j <= nMaxDimId; j++)
    8747             :         {
    8748             :             // Is j dim used?
    8749         811 :             if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end())
    8750             :             {
    8751             :                 // Useful dim.
    8752         809 :                 char szTemp[NC_MAX_NAME + 1] = {};
    8753         809 :                 status = nc_inq_dimname(cdfid, j, szTemp);
    8754         809 :                 if (status != NC_NOERR)
    8755             :                 {
    8756           0 :                     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8757             :                         // deadlock with GDALDataset own
    8758             :                         // mutex.
    8759           0 :                     delete poDS;
    8760           0 :                     CPLAcquireMutex(hNCMutex, 1000.0);
    8761           0 :                     return nullptr;
    8762             :                 }
    8763         809 :                 poDS->papszDimName.AddString(szTemp);
    8764             : 
    8765         809 :                 if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end())
    8766             :                 {
    8767         773 :                     int nDimGroupId = -1;
    8768         773 :                     int nDimVarId = -1;
    8769         773 :                     if (NCDFResolveVar(cdfid, poDS->papszDimName[j],
    8770         773 :                                        &nDimGroupId, &nDimVarId) == CE_None)
    8771             :                     {
    8772         573 :                         poDS->ReadAttributes(nDimGroupId, nDimVarId);
    8773             :                     }
    8774             :                 }
    8775             :             }
    8776             :             else
    8777             :             {
    8778             :                 // Useless dim.
    8779           2 :                 poDS->papszDimName.AddString("");
    8780             :             }
    8781             :         }
    8782         349 :         CPLFree(panDimIds);
    8783             :     }
    8784             : 
    8785             :     // Set projection info.
    8786         698 :     std::vector<std::string> aosRemovedMDItems;
    8787         349 :     if (nd > 1)
    8788             :     {
    8789         345 :         poDS->SetProjectionFromVar(cdfid, var,
    8790             :                                    /*bReadSRSOnly=*/false,
    8791             :                                    /* pszGivenGM = */ nullptr,
    8792             :                                    /* returnProjStr = */ nullptr,
    8793             :                                    /* sg = */ nullptr, &aosRemovedMDItems);
    8794             :     }
    8795             : 
    8796             :     // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option.
    8797         349 :     const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr);
    8798         349 :     if (pszValue)
    8799             :     {
    8800          24 :         poDS->bBottomUp = CPLTestBool(pszValue);
    8801          24 :         CPLDebug("GDAL_netCDF",
    8802             :                  "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s",
    8803          24 :                  static_cast<int>(poDS->bBottomUp), pszValue);
    8804             :     }
    8805             : 
    8806             :     // Save non-spatial dimension info.
    8807             : 
    8808         349 :     int *panBandZLev = nullptr;
    8809         349 :     int nDim = (nd >= 2) ? 2 : 1;
    8810             :     size_t lev_count;
    8811         349 :     size_t nTotLevCount = 1;
    8812         349 :     nc_type nType = NC_NAT;
    8813             : 
    8814         698 :     CPLString osExtraDimNames;
    8815             : 
    8816         349 :     if (nd > 2)
    8817             :     {
    8818          60 :         nDim = 2;
    8819          60 :         panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int)));
    8820             : 
    8821          60 :         osExtraDimNames = "{";
    8822             : 
    8823          60 :         char szDimName[NC_MAX_NAME + 1] = {};
    8824             : 
    8825          60 :         bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false;
    8826         259 :         for (int j = 0; j < nd; j++)
    8827             :         {
    8828         338 :             if ((poDS->m_anDimIds[j] != poDS->nXDimID) &&
    8829         139 :                 (poDS->m_anDimIds[j] != poDS->nYDimID))
    8830             :             {
    8831          79 :                 nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count);
    8832          79 :                 nTotLevCount *= lev_count;
    8833          79 :                 panBandZLev[nDim - 2] = static_cast<int>(lev_count);
    8834          79 :                 anBandDimPos[nDim] = j;  // Save Position of ZDim
    8835             :                 // Save non-spatial dimension names.
    8836          79 :                 if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) ==
    8837             :                     NC_NOERR)
    8838             :                 {
    8839          79 :                     osExtraDimNames += szDimName;
    8840          79 :                     if (j < nd - 3)
    8841             :                     {
    8842          19 :                         osExtraDimNames += ",";
    8843             :                     }
    8844             : 
    8845          79 :                     int nIdxGroupID = -1;
    8846          79 :                     int nIdxVarID = Get1DVariableIndexedByDimension(
    8847          79 :                         cdfid, poDS->m_anDimIds[j], szDimName, true,
    8848          79 :                         &nIdxGroupID);
    8849          79 :                     poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID);
    8850          79 :                     poDS->m_anExtraDimVarIds.push_back(nIdxVarID);
    8851             : 
    8852          79 :                     if (nIdxVarID >= 0)
    8853             :                     {
    8854          70 :                         nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType);
    8855             :                         char szExtraDimDef[NC_MAX_NAME + 1];
    8856          70 :                         snprintf(szExtraDimDef, sizeof(szExtraDimDef),
    8857             :                                  "{%ld,%d}", (long)lev_count, nType);
    8858             :                         char szTemp[NC_MAX_NAME + 32 + 1];
    8859          70 :                         snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    8860             :                                  szDimName);
    8861          70 :                         poDS->papszMetadata = CSLSetNameValue(
    8862             :                             poDS->papszMetadata, szTemp, szExtraDimDef);
    8863             : 
    8864             :                         // Retrieving data for unlimited dimensions might be
    8865             :                         // costly on network storage, so don't do it.
    8866             :                         // Each band will capture the value along the extra
    8867             :                         // dimension in its NETCDF_DIM_xxxx band metadata item
    8868             :                         // Addresses use case of
    8869             :                         // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html
    8870             :                         const bool bIsLocal =
    8871          70 :                             VSIIsLocal(osFilenameForNCOpen.c_str());
    8872             :                         bool bListDimValues =
    8873          71 :                             bIsLocal || lev_count == 1 ||
    8874           1 :                             !NCDFIsUnlimitedDim(poDS->eFormat ==
    8875             :                                                     NCDF_FORMAT_NC4,
    8876           1 :                                                 cdfid, poDS->m_anDimIds[j]);
    8877             :                         const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES =
    8878          70 :                             CPLGetConfigOption(
    8879             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr);
    8880          70 :                         if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES)
    8881             :                         {
    8882           2 :                             bListDimValues = CPLTestBool(
    8883             :                                 pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES);
    8884             :                         }
    8885          68 :                         else if (!bListDimValues && !bIsLocal &&
    8886           1 :                                  !bREPORT_EXTRA_DIM_VALUESWarningEmitted)
    8887             :                         {
    8888           1 :                             bREPORT_EXTRA_DIM_VALUESWarningEmitted = true;
    8889           1 :                             CPLDebug(
    8890             :                                 "GDAL_netCDF",
    8891             :                                 "Listing extra dimension values is skipped "
    8892             :                                 "because this dataset is hosted on a network "
    8893             :                                 "file system, and such an operation could be "
    8894             :                                 "slow. If you still want to proceed, set the "
    8895             :                                 "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES "
    8896             :                                 "configuration option to YES");
    8897             :                         }
    8898          70 :                         if (bListDimValues)
    8899             :                         {
    8900          68 :                             char *pszTemp = nullptr;
    8901          68 :                             if (NCDFGet1DVar(nIdxGroupID, nIdxVarID,
    8902          68 :                                              &pszTemp) == CE_None)
    8903             :                             {
    8904          68 :                                 snprintf(szTemp, sizeof(szTemp),
    8905             :                                          "NETCDF_DIM_%s_VALUES", szDimName);
    8906          68 :                                 poDS->papszMetadata = CSLSetNameValue(
    8907             :                                     poDS->papszMetadata, szTemp, pszTemp);
    8908          68 :                                 CPLFree(pszTemp);
    8909             :                             }
    8910             :                         }
    8911             :                     }
    8912             :                 }
    8913             :                 else
    8914             :                 {
    8915           0 :                     poDS->m_anExtraDimGroupIds.push_back(-1);
    8916           0 :                     poDS->m_anExtraDimVarIds.push_back(-1);
    8917             :                 }
    8918             : 
    8919          79 :                 nDim++;
    8920             :             }
    8921             :         }
    8922          60 :         osExtraDimNames += "}";
    8923          60 :         poDS->papszMetadata = CSLSetNameValue(
    8924             :             poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames);
    8925             :     }
    8926             : 
    8927             :     // Store Metadata.
    8928         359 :     for (const auto &osStr : aosRemovedMDItems)
    8929          10 :         poDS->papszMetadata =
    8930          10 :             CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr);
    8931             : 
    8932         349 :     poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata);
    8933             : 
    8934             :     // Create bands.
    8935             : 
    8936             :     // Arbitrary threshold.
    8937             :     int nMaxBandCount =
    8938         349 :         atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768"));
    8939         349 :     if (nMaxBandCount <= 0)
    8940           0 :         nMaxBandCount = 32768;
    8941         349 :     if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount))
    8942             :     {
    8943           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    8944             :                  "Limiting number of bands to %d instead of %u", nMaxBandCount,
    8945             :                  static_cast<unsigned int>(nTotLevCount));
    8946           0 :         nTotLevCount = static_cast<unsigned int>(nMaxBandCount);
    8947             :     }
    8948         349 :     if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0)
    8949             :     {
    8950           0 :         poDS->nRasterXSize = 0;
    8951           0 :         poDS->nRasterYSize = 0;
    8952           0 :         nTotLevCount = 0;
    8953           0 :         if (poDS->GetLayerCount() == 0)
    8954             :         {
    8955           0 :             CPLFree(panBandZLev);
    8956           0 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    8957             :                 // deadlock with GDALDataset own mutex.
    8958           0 :             delete poDS;
    8959           0 :             CPLAcquireMutex(hNCMutex, 1000.0);
    8960           0 :             return nullptr;
    8961             :         }
    8962             :     }
    8963         349 :     if (bSeveralVariablesAsBands)
    8964             :     {
    8965           6 :         const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second;
    8966          24 :         for (int iBand = 0; iBand < static_cast<int>(listVariables.size());
    8967             :              ++iBand)
    8968             :         {
    8969          18 :             int bandVarGroupId = listVariables[iBand].first;
    8970          18 :             int bandVarId = listVariables[iBand].second;
    8971             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    8972           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId,
    8973          18 :                 bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1);
    8974          18 :             poDS->SetBand(iBand + 1, poBand);
    8975             :         }
    8976             :     }
    8977             :     else
    8978             :     {
    8979         794 :         for (unsigned int lev = 0; lev < nTotLevCount; lev++)
    8980             :         {
    8981             :             netCDFRasterBand *poBand = new netCDFRasterBand(
    8982           0 :                 netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim,
    8983         451 :                 lev, panBandZLev, anBandDimPos.data(), lev + 1);
    8984         451 :             poDS->SetBand(lev + 1, poBand);
    8985             :         }
    8986             :     }
    8987             : 
    8988         349 :     if (panBandZLev)
    8989          60 :         CPLFree(panBandZLev);
    8990             :     // Handle angular geographic coordinates here
    8991             : 
    8992             :     // Initialize any PAM information.
    8993         349 :     if (bTreatAsSubdataset)
    8994             :     {
    8995          59 :         poDS->SetPhysicalFilename(poDS->osFilename);
    8996          59 :         poDS->SetSubdatasetName(osSubdatasetName);
    8997             :     }
    8998             : 
    8999         349 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9000             :         // GDALDataset own mutex.
    9001         349 :     poDS->TryLoadXML();
    9002             : 
    9003         349 :     if (bTreatAsSubdataset)
    9004          59 :         poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
    9005             :     else
    9006         290 :         poDS->oOvManager.Initialize(poDS, poDS->osFilename);
    9007             : 
    9008         349 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9009             : 
    9010         349 :     return poDS;
    9011             : }
    9012             : 
    9013             : /************************************************************************/
    9014             : /*                            CopyMetadata()                            */
    9015             : /*                                                                      */
    9016             : /*      Create a copy of metadata for NC_GLOBAL or a variable           */
    9017             : /************************************************************************/
    9018             : 
    9019         155 : static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand,
    9020             :                          GDALRasterBand *poDstBand, int nCdfId, int CDFVarID,
    9021             :                          const char *pszPrefix)
    9022             : {
    9023             :     // Remove the following band meta but set them later from band data.
    9024         155 :     const char *const papszIgnoreBand[] = {
    9025             :         CF_ADD_OFFSET,  CF_SCALE_FACTOR, "valid_range", "_Unsigned",
    9026             :         NCDF_FillValue, "coordinates",   nullptr};
    9027         155 :     const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr};
    9028             : 
    9029         155 :     CSLConstList papszMetadata = nullptr;
    9030         155 :     if (poSrcDS)
    9031             :     {
    9032          65 :         papszMetadata = poSrcDS->GetMetadata();
    9033             :     }
    9034          90 :     else if (poSrcBand)
    9035             :     {
    9036          90 :         papszMetadata = poSrcBand->GetMetadata();
    9037             :     }
    9038             : 
    9039         635 :     for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata))
    9040             :     {
    9041             : #ifdef NCDF_DEBUG
    9042             :         CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue);
    9043             : #endif
    9044             : 
    9045         480 :         CPLString osMetaName(pszKey);
    9046             : 
    9047             :         // Check for items that match pszPrefix if applicable.
    9048         480 :         if (pszPrefix && !EQUAL(pszPrefix, ""))
    9049             :         {
    9050             :             // Remove prefix.
    9051         115 :             if (STARTS_WITH(osMetaName.c_str(), pszPrefix))
    9052             :             {
    9053          17 :                 osMetaName = osMetaName.substr(strlen(pszPrefix));
    9054             :             }
    9055             :             // Only copy items that match prefix.
    9056             :             else
    9057             :             {
    9058          98 :                 continue;
    9059             :             }
    9060             :         }
    9061             : 
    9062             :         // Fix various issues with metadata translation.
    9063         382 :         if (CDFVarID == NC_GLOBAL)
    9064             :         {
    9065             :             // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*.
    9066         481 :             if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) ||
    9067         238 :                 (STARTS_WITH(osMetaName, "NETCDF_DIM_")))
    9068          21 :                 continue;
    9069             :             // Remove NC_GLOBAL prefix for netcdf global Metadata.
    9070         222 :             else if (STARTS_WITH(osMetaName, "NC_GLOBAL#"))
    9071             :             {
    9072          33 :                 osMetaName = osMetaName.substr(strlen("NC_GLOBAL#"));
    9073             :             }
    9074             :             // GDAL Metadata renamed as GDAL-[meta].
    9075         189 :             else if (strstr(osMetaName, "#") == nullptr)
    9076             :             {
    9077          16 :                 osMetaName = "GDAL_" + osMetaName;
    9078             :             }
    9079             :             // Keep time, lev and depth information for safe-keeping.
    9080             :             // Time and vertical coordinate handling need improvements.
    9081             :             /*
    9082             :             else if( STARTS_WITH(szMetaName, "time#") )
    9083             :             {
    9084             :                 szMetaName[4] = '-';
    9085             :             }
    9086             :             else if( STARTS_WITH(szMetaName, "lev#") )
    9087             :             {
    9088             :                 szMetaName[3] = '-';
    9089             :             }
    9090             :             else if( STARTS_WITH(szMetaName, "depth#") )
    9091             :             {
    9092             :                 szMetaName[5] = '-';
    9093             :             }
    9094             :             */
    9095             :             // Only copy data without # (previously all data was copied).
    9096         222 :             if (strstr(osMetaName, "#") != nullptr)
    9097         173 :                 continue;
    9098             :             // netCDF attributes do not like the '#' character.
    9099             :             // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) {
    9100             :             //     if( szMetaName[h] == '#') szMetaName[h] = '-';
    9101             :             // }
    9102             :         }
    9103             :         else
    9104             :         {
    9105             :             // Do not copy varname, stats, NETCDF_DIM_*, nodata
    9106             :             // and items in papszIgnoreBand.
    9107         139 :             if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") ||
    9108         107 :                 STARTS_WITH(osMetaName, "STATISTICS_") ||
    9109         107 :                 STARTS_WITH(osMetaName, "NETCDF_DIM_") ||
    9110          74 :                 STARTS_WITH(osMetaName, "missing_value") ||
    9111         293 :                 STARTS_WITH(osMetaName, "_FillValue") ||
    9112          47 :                 CSLFindString(papszIgnoreBand, osMetaName) != -1)
    9113          97 :                 continue;
    9114             :         }
    9115             : 
    9116             : #ifdef NCDF_DEBUG
    9117             :         CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(),
    9118             :                  pszValue);
    9119             : #endif
    9120          91 :         if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None)
    9121             :         {
    9122           0 :             CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed",
    9123             :                      nCdfId, CDFVarID, osMetaName.c_str(), pszValue);
    9124             :         }
    9125             :     }
    9126             : 
    9127             :     // Set add_offset and scale_factor here if present.
    9128         155 :     if (poSrcBand && poDstBand)
    9129             :     {
    9130             : 
    9131          90 :         int bGotAddOffset = FALSE;
    9132          90 :         const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset);
    9133          90 :         int bGotScale = FALSE;
    9134          90 :         const double dfScale = poSrcBand->GetScale(&bGotScale);
    9135             : 
    9136          90 :         if (bGotAddOffset && dfAddOffset != 0.0)
    9137           1 :             poDstBand->SetOffset(dfAddOffset);
    9138          90 :         if (bGotScale && dfScale != 1.0)
    9139           1 :             poDstBand->SetScale(dfScale);
    9140             :     }
    9141         155 : }
    9142             : 
    9143             : /************************************************************************/
    9144             : /*                            CreateLL()                                */
    9145             : /*                                                                      */
    9146             : /*      Shared functionality between netCDFDataset::Create() and        */
    9147             : /*      netCDF::CreateCopy() for creating netcdf file based on a set of */
    9148             : /*      options and a configuration.                                    */
    9149             : /************************************************************************/
    9150             : 
    9151         197 : netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize,
    9152             :                                        int nYSize, int nBandsIn,
    9153             :                                        char **papszOptions)
    9154             : {
    9155         197 :     if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) ||
    9156         125 :           (nXSize > 0 && nYSize > 0 && nBandsIn > 0)))
    9157             :     {
    9158           1 :         return nullptr;
    9159             :     }
    9160             : 
    9161         196 :     CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock with
    9162             :         // GDALDataset own mutex.
    9163         196 :     netCDFDataset *poDS = new netCDFDataset();
    9164         196 :     CPLAcquireMutex(hNCMutex, 1000.0);
    9165             : 
    9166         196 :     poDS->nRasterXSize = nXSize;
    9167         196 :     poDS->nRasterYSize = nYSize;
    9168         196 :     poDS->eAccess = GA_Update;
    9169         196 :     poDS->osFilename = pszFilename;
    9170             : 
    9171             :     // From gtiff driver, is this ok?
    9172             :     /*
    9173             :     poDS->nBlockXSize = nXSize;
    9174             :     poDS->nBlockYSize = 1;
    9175             :     poDS->nBlocksPerBand =
    9176             :         ((nYSize + poDS->nBlockYSize - 1) / poDS->nBlockYSize)
    9177             :         * ((nXSize + poDS->nBlockXSize - 1) / poDS->nBlockXSize);
    9178             :         */
    9179             : 
    9180             :     // process options.
    9181         196 :     poDS->papszCreationOptions = CSLDuplicate(papszOptions);
    9182         196 :     poDS->ProcessCreationOptions();
    9183             : 
    9184         196 :     if (poDS->eMultipleLayerBehavior == SEPARATE_FILES)
    9185             :     {
    9186             :         VSIStatBuf sStat;
    9187           2 :         if (VSIStat(pszFilename, &sStat) == 0)
    9188             :         {
    9189           0 :             if (!VSI_ISDIR(sStat.st_mode))
    9190             :             {
    9191           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    9192             :                          "%s is an existing file, but not a directory",
    9193             :                          pszFilename);
    9194           0 :                 CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9195             :                     // deadlock with GDALDataset own
    9196             :                     // mutex.
    9197           0 :                 delete poDS;
    9198           0 :                 CPLAcquireMutex(hNCMutex, 1000.0);
    9199           0 :                 return nullptr;
    9200             :             }
    9201             :         }
    9202           2 :         else if (VSIMkdir(pszFilename, 0755) != 0)
    9203             :         {
    9204           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory",
    9205             :                      pszFilename);
    9206           1 :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9207             :                 // deadlock with GDALDataset own mutex.
    9208           1 :             delete poDS;
    9209           1 :             CPLAcquireMutex(hNCMutex, 1000.0);
    9210           1 :             return nullptr;
    9211             :         }
    9212             : 
    9213           1 :         return poDS;
    9214             :     }
    9215             :     // Create the dataset.
    9216         388 :     CPLString osFilenameForNCCreate(pszFilename);
    9217             : #if defined(_WIN32) && !defined(NETCDF_USES_UTF8)
    9218             :     if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES")))
    9219             :     {
    9220             :         char *pszTemp =
    9221             :             CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP");
    9222             :         osFilenameForNCCreate = pszTemp;
    9223             :         CPLFree(pszTemp);
    9224             :     }
    9225             : #endif
    9226             : 
    9227             : #if defined(_WIN32)
    9228             :     {
    9229             :         // Works around bug of msys2 netCDF 4.9.0 package where nc_create()
    9230             :         // crashes
    9231             :         VSIStatBuf sStat;
    9232             :         const std::string osDirname =
    9233             :             CPLGetDirnameSafe(osFilenameForNCCreate.c_str());
    9234             :         if (VSIStat(osDirname.c_str(), &sStat) != 0)
    9235             :         {
    9236             :             CPLError(CE_Failure, CPLE_OpenFailed,
    9237             :                      "Unable to create netCDF file %s: non existing output "
    9238             :                      "directory",
    9239             :                      pszFilename);
    9240             :             CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll
    9241             :                 // deadlock with GDALDataset own mutex.
    9242             :             delete poDS;
    9243             :             CPLAcquireMutex(hNCMutex, 1000.0);
    9244             :             return nullptr;
    9245             :         }
    9246             :     }
    9247             : #endif
    9248             : 
    9249             :     int status =
    9250         194 :         nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid));
    9251             : 
    9252             :     // Put into define mode.
    9253         194 :     poDS->SetDefineMode(true);
    9254             : 
    9255         194 :     if (status != NC_NOERR)
    9256             :     {
    9257          30 :         CPLError(CE_Failure, CPLE_OpenFailed,
    9258             :                  "Unable to create netCDF file %s (Error code %d): %s .",
    9259             :                  pszFilename, status, nc_strerror(status));
    9260          30 :         CPLReleaseMutex(hNCMutex);  // Release mutex otherwise we'll deadlock
    9261             :             // with GDALDataset own mutex.
    9262          30 :         delete poDS;
    9263          30 :         CPLAcquireMutex(hNCMutex, 1000.0);
    9264          30 :         return nullptr;
    9265             :     }
    9266             : 
    9267             :     // Define dimensions.
    9268         164 :     if (nXSize > 0 && nYSize > 0)
    9269             :     {
    9270         111 :         poDS->papszDimName.AddString(NCDF_DIMNAME_X);
    9271             :         status =
    9272         111 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID));
    9273         111 :         NCDF_ERR(status);
    9274         111 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9275             :                  poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID);
    9276             : 
    9277         111 :         poDS->papszDimName.AddString(NCDF_DIMNAME_Y);
    9278             :         status =
    9279         111 :             nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID));
    9280         111 :         NCDF_ERR(status);
    9281         111 :         CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d",
    9282             :                  poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID);
    9283             :     }
    9284             : 
    9285         164 :     return poDS;
    9286             : }
    9287             : 
    9288             : /************************************************************************/
    9289             : /*                            Create()                                  */
    9290             : /************************************************************************/
    9291             : 
    9292         126 : GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize,
    9293             :                                    int nYSize, int nBandsIn, GDALDataType eType,
    9294             :                                    char **papszOptions)
    9295             : {
    9296         126 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)",
    9297             :              pszFilename);
    9298             : 
    9299             :     const char *legacyCreationOp =
    9300         126 :         CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8");
    9301         252 :     std::string legacyCreationOp_s = std::string(legacyCreationOp);
    9302             : 
    9303             :     // Check legacy creation op FIRST
    9304             : 
    9305         126 :     bool legacyCreateMode = false;
    9306             : 
    9307         126 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0)
    9308             :     {
    9309          56 :         legacyCreateMode = true;
    9310             :     }
    9311          70 :     else if (legacyCreationOp_s == "CF_1.8")
    9312             :     {
    9313          54 :         legacyCreateMode = false;
    9314             :     }
    9315             : 
    9316          16 :     else if (legacyCreationOp_s == "WKT")
    9317             :     {
    9318          16 :         legacyCreateMode = true;
    9319             :     }
    9320             : 
    9321             :     else
    9322             :     {
    9323           0 :         CPLError(
    9324             :             CE_Failure, CPLE_NotSupported,
    9325             :             "Dataset creation option GEOMETRY_ENCODING=%s is not supported.",
    9326             :             legacyCreationOp_s.c_str());
    9327           0 :         return nullptr;
    9328             :     }
    9329             : 
    9330         252 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9331         238 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9332         112 :         (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 ||
    9333             :          eType == GDT_Int64))
    9334             :     {
    9335          10 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9336          10 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9337             :     }
    9338             : 
    9339         252 :     CPLStringList aosBandNames;
    9340         126 :     if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES"))
    9341             :     {
    9342             :         aosBandNames =
    9343           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9344             : 
    9345           2 :         if (aosBandNames.Count() != nBandsIn)
    9346             :         {
    9347           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9348             :                      "Attempted to create netCDF with %d bands but %d names "
    9349             :                      "provided in BAND_NAMES.",
    9350             :                      nBandsIn, aosBandNames.Count());
    9351             : 
    9352           1 :             return nullptr;
    9353             :         }
    9354             :     }
    9355             : 
    9356         250 :     CPLMutexHolderD(&hNCMutex);
    9357             : 
    9358         125 :     auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn,
    9359             :                                         aosOptions.List());
    9360             : 
    9361         125 :     if (!poDS)
    9362          19 :         return nullptr;
    9363             : 
    9364         106 :     if (!legacyCreateMode)
    9365             :     {
    9366          37 :         poDS->bSGSupport = true;
    9367          37 :         poDS->vcdf.enableFullVirtualMode();
    9368             :     }
    9369             : 
    9370             :     else
    9371             :     {
    9372          69 :         poDS->bSGSupport = false;
    9373             :     }
    9374             : 
    9375             :     // Should we write signed or unsigned byte?
    9376             :     // TODO should this only be done in Create()
    9377         106 :     poDS->bSignedData = true;
    9378         106 :     const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", "");
    9379         106 :     if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE"))
    9380          15 :         poDS->bSignedData = false;
    9381             : 
    9382             :     // Add Conventions, GDAL info and history.
    9383         106 :     if (poDS->cdfid >= 0)
    9384             :     {
    9385             :         const char *CF_Vector_Conv =
    9386         173 :             poDS->bSGSupport ||
    9387             :                     // Use of variable length strings require CF-1.8
    9388          68 :                     EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4")
    9389             :                 ? NCDF_CONVENTIONS_CF_V1_8
    9390         173 :                 : NCDF_CONVENTIONS_CF_V1_6;
    9391         105 :         poDS->bWriteGDALVersion = CPLTestBool(
    9392             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9393         105 :         poDS->bWriteGDALHistory = CPLTestBool(
    9394             :             CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9395         105 :         NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion,
    9396         105 :                            poDS->bWriteGDALHistory, "", "Create",
    9397             :                            (nBandsIn == 0) ? CF_Vector_Conv
    9398             :                                            : GDAL_DEFAULT_NCDF_CONVENTIONS);
    9399             :     }
    9400             : 
    9401             :     // Define bands.
    9402         197 :     for (int iBand = 1; iBand <= nBandsIn; iBand++)
    9403             :     {
    9404             :         const char *pszBandName =
    9405          91 :             aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1];
    9406             : 
    9407          91 :         poDS->SetBand(iBand, new netCDFRasterBand(
    9408          91 :                                  netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS,
    9409          91 :                                  eType, iBand, poDS->bSignedData, pszBandName));
    9410             :     }
    9411             : 
    9412         106 :     CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename);
    9413             :     // Return same dataset.
    9414         106 :     return poDS;
    9415             : }
    9416             : 
    9417             : template <class T>
    9418          90 : static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,
    9419             :                            int nXSize, int nYSize, GDALProgressFunc pfnProgress,
    9420             :                            void *pProgressData)
    9421             : {
    9422          90 :     GDALDataType eDT = poSrcBand->GetRasterDataType();
    9423          90 :     CPLErr eErr = CE_None;
    9424          90 :     T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T)));
    9425             : 
    9426        2707 :     for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++)
    9427             :     {
    9428        2617 :         eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline,
    9429             :                                    nXSize, 1, eDT, 0, 0, nullptr);
    9430        2617 :         if (eErr != CE_None)
    9431             :         {
    9432           0 :             CPLDebug(
    9433             :                 "GDAL_netCDF",
    9434             :                 "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d",
    9435             :                 eErr);
    9436             :         }
    9437             :         else
    9438             :         {
    9439        2617 :             eErr =
    9440             :                 poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline,
    9441             :                                     nXSize, 1, eDT, 0, 0, nullptr);
    9442        2617 :             if (eErr != CE_None)
    9443           0 :                 CPLDebug("GDAL_netCDF",
    9444             :                          "NCDFCopyBand(), poDstBand->RasterIO() returned error "
    9445             :                          "code %d",
    9446             :                          eErr);
    9447             :         }
    9448             : 
    9449        2617 :         if (nYSize > 10 && (iLine % (nYSize / 10) == 1))
    9450             :         {
    9451         267 :             if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData))
    9452             :             {
    9453           0 :                 eErr = CE_Failure;
    9454           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt,
    9455             :                          "User terminated CreateCopy()");
    9456             :             }
    9457             :         }
    9458             :     }
    9459             : 
    9460          90 :     CPLFree(patScanline);
    9461             : 
    9462          90 :     pfnProgress(1.0, nullptr, pProgressData);
    9463             : 
    9464          90 :     return eErr;
    9465             : }
    9466             : 
    9467             : /************************************************************************/
    9468             : /*                            CreateCopy()                              */
    9469             : /************************************************************************/
    9470             : 
    9471             : GDALDataset *
    9472          81 : netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
    9473             :                           CPL_UNUSED int bStrict, char **papszOptions,
    9474             :                           GDALProgressFunc pfnProgress, void *pProgressData)
    9475             : {
    9476         162 :     CPLMutexHolderD(&hNCMutex);
    9477             : 
    9478          81 :     CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)",
    9479             :              pszFilename);
    9480             : 
    9481          81 :     if (poSrcDS->GetRootGroup())
    9482             :     {
    9483           5 :         auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF"));
    9484           5 :         if (poDrv)
    9485             :         {
    9486           5 :             return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    9487             :                                             papszOptions, pfnProgress,
    9488           5 :                                             pProgressData);
    9489             :         }
    9490             :     }
    9491             : 
    9492          76 :     const int nBands = poSrcDS->GetRasterCount();
    9493          76 :     const int nXSize = poSrcDS->GetRasterXSize();
    9494          76 :     const int nYSize = poSrcDS->GetRasterYSize();
    9495          76 :     const char *pszWKT = poSrcDS->GetProjectionRef();
    9496             : 
    9497             :     // Check input bands for errors.
    9498          76 :     if (nBands == 0)
    9499             :     {
    9500           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    9501             :                  "NetCDF driver does not support "
    9502             :                  "source dataset with zero band.");
    9503           1 :         return nullptr;
    9504             :     }
    9505             : 
    9506          75 :     GDALDataType eDT = GDT_Unknown;
    9507          75 :     GDALRasterBand *poSrcBand = nullptr;
    9508         179 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9509             :     {
    9510         108 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9511         108 :         eDT = poSrcBand->GetRasterDataType();
    9512         108 :         if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT))
    9513             :         {
    9514           4 :             CPLError(CE_Failure, CPLE_NotSupported,
    9515             :                      "NetCDF driver does not support source dataset with band "
    9516             :                      "of complex type.");
    9517           4 :             return nullptr;
    9518             :         }
    9519             :     }
    9520             : 
    9521         142 :     CPLStringList aosBandNames;
    9522          71 :     if (const char *pszBandNames =
    9523          71 :             CSLFetchNameValue(papszOptions, "BAND_NAMES"))
    9524             :     {
    9525             :         aosBandNames =
    9526           2 :             CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS);
    9527             : 
    9528           2 :         if (aosBandNames.Count() != nBands)
    9529             :         {
    9530           1 :             CPLError(CE_Failure, CPLE_OpenFailed,
    9531             :                      "Attempted to create netCDF with %d bands but %d names "
    9532             :                      "provided in BAND_NAMES.",
    9533             :                      nBands, aosBandNames.Count());
    9534             : 
    9535           1 :             return nullptr;
    9536             :         }
    9537             :     }
    9538             : 
    9539          70 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    9540           0 :         return nullptr;
    9541             : 
    9542             :     // Same as in Create().
    9543         140 :     CPLStringList aosOptions(CSLDuplicate(papszOptions));
    9544         131 :     if (aosOptions.FetchNameValue("FORMAT") == nullptr &&
    9545          61 :         (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 ||
    9546             :          eDT == GDT_Int64))
    9547             :     {
    9548           6 :         CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type");
    9549           6 :         aosOptions.SetNameValue("FORMAT", "NC4");
    9550             :     }
    9551          70 :     netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize,
    9552             :                                                   nBands, aosOptions.List());
    9553          70 :     if (!poDS)
    9554          13 :         return nullptr;
    9555             : 
    9556             :     // Copy global metadata.
    9557             :     // Add Conventions, GDAL info and history.
    9558          57 :     CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr);
    9559          57 :     const bool bWriteGDALVersion = CPLTestBool(
    9560             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES"));
    9561          57 :     const bool bWriteGDALHistory = CPLTestBool(
    9562             :         CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES"));
    9563          57 :     NCDFAddGDALHistory(
    9564             :         poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory,
    9565          57 :         poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy",
    9566          57 :         poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions"));
    9567             : 
    9568          57 :     pfnProgress(0.1, nullptr, pProgressData);
    9569             : 
    9570             :     // Check for extra dimensions.
    9571          57 :     int nDim = 2;
    9572             :     char **papszExtraDimNames =
    9573          57 :         NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9574          57 :     char **papszExtraDimValues = nullptr;
    9575             : 
    9576          57 :     if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0)
    9577             :     {
    9578           5 :         size_t nDimSizeTot = 1;
    9579             :         // first make sure dimensions lengths compatible with band count
    9580             :         // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) {
    9581          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9582             :         {
    9583             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9584           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9585           8 :                      papszExtraDimNames[i]);
    9586             :             papszExtraDimValues =
    9587           8 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9588           8 :             const size_t nDimSize = atol(papszExtraDimValues[0]);
    9589           8 :             CSLDestroy(papszExtraDimValues);
    9590           8 :             nDimSizeTot *= nDimSize;
    9591             :         }
    9592           5 :         if (nDimSizeTot == (size_t)nBands)
    9593             :         {
    9594           5 :             nDim = 2 + CSLCount(papszExtraDimNames);
    9595             :         }
    9596             :         else
    9597             :         {
    9598             :             // if nBands != #bands computed raise a warning
    9599             :             // just issue a debug message, because it was probably intentional
    9600           0 :             CPLDebug("GDAL_netCDF",
    9601             :                      "Warning: Number of bands (%d) is not compatible with "
    9602             :                      "dimensions "
    9603             :                      "(total=%ld names=%s)",
    9604             :                      nBands, (long)nDimSizeTot,
    9605           0 :                      poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", ""));
    9606           0 :             CSLDestroy(papszExtraDimNames);
    9607           0 :             papszExtraDimNames = nullptr;
    9608             :         }
    9609             :     }
    9610             : 
    9611          57 :     int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9612          57 :     int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int)));
    9613             : 
    9614             :     nc_type nVarType;
    9615          57 :     int *panBandZLev = nullptr;
    9616          57 :     int *panDimVarIds = nullptr;
    9617             : 
    9618          57 :     if (nDim > 2)
    9619             :     {
    9620           5 :         panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9621           5 :         panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int)));
    9622             : 
    9623             :         // Define all dims.
    9624          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9625             :         {
    9626           8 :             poDS->papszDimName.AddString(papszExtraDimNames[i]);
    9627             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9628           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF",
    9629           8 :                      papszExtraDimNames[i]);
    9630             :             papszExtraDimValues =
    9631           8 :                 NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, ""));
    9632           8 :             const int nDimSize = papszExtraDimValues && papszExtraDimValues[0]
    9633          16 :                                      ? atoi(papszExtraDimValues[0])
    9634             :                                      : 0;
    9635             :             // nc_type is an enum in netcdf-3, needs casting.
    9636           8 :             nVarType = static_cast<nc_type>(papszExtraDimValues &&
    9637           8 :                                                     papszExtraDimValues[0] &&
    9638           8 :                                                     papszExtraDimValues[1]
    9639           8 :                                                 ? atol(papszExtraDimValues[1])
    9640             :                                                 : 0);
    9641           8 :             CSLDestroy(papszExtraDimValues);
    9642           8 :             panBandZLev[i] = nDimSize;
    9643           8 :             panBandDimPos[i + 2] = i;  // Save Position of ZDim.
    9644             : 
    9645             :             // Define dim.
    9646          16 :             int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i],
    9647           8 :                                     nDimSize, &(panDimIds[i]));
    9648           8 :             NCDF_ERR(status);
    9649             : 
    9650             :             // Define dim var.
    9651           8 :             int anDim[1] = {panDimIds[i]};
    9652          16 :             status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1,
    9653           8 :                                 anDim, &(panDimVarIds[i]));
    9654           8 :             NCDF_ERR(status);
    9655             : 
    9656             :             // Add dim metadata, using global var# items.
    9657           8 :             snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]);
    9658           8 :             CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid,
    9659           8 :                          panDimVarIds[i], szTemp);
    9660             :         }
    9661             :     }
    9662             : 
    9663             :     // Copy GeoTransform and Projection.
    9664             : 
    9665             :     // Copy geolocation info.
    9666          57 :     char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION");
    9667          57 :     if (papszGeolocationInfo != nullptr)
    9668           5 :         poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION");
    9669             : 
    9670             :     // Copy geotransform.
    9671          57 :     bool bGotGeoTransform = false;
    9672             :     double adfGeoTransform[6];
    9673          57 :     CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform);
    9674          57 :     if (eErr == CE_None)
    9675             :     {
    9676          39 :         poDS->SetGeoTransform(adfGeoTransform);
    9677             :         // Disable AddProjectionVars() from being called.
    9678          39 :         bGotGeoTransform = true;
    9679          39 :         poDS->m_bHasGeoTransform = false;
    9680             :     }
    9681             : 
    9682             :     // Copy projection.
    9683          57 :     void *pScaledProgress = nullptr;
    9684          57 :     if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0))
    9685             :     {
    9686          40 :         poDS->SetProjection(pszWKT ? pszWKT : "");
    9687             : 
    9688             :         // Now we can call AddProjectionVars() directly.
    9689          40 :         poDS->m_bHasGeoTransform = bGotGeoTransform;
    9690          40 :         poDS->AddProjectionVars(true, nullptr, nullptr);
    9691             :         pScaledProgress =
    9692          40 :             GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData);
    9693          40 :         poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress);
    9694          40 :         GDALDestroyScaledProgress(pScaledProgress);
    9695             :     }
    9696             :     else
    9697             :     {
    9698          17 :         poDS->bBottomUp =
    9699          17 :             CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE));
    9700          17 :         if (papszGeolocationInfo)
    9701             :         {
    9702           4 :             poDS->AddProjectionVars(true, nullptr, nullptr);
    9703           4 :             poDS->AddProjectionVars(false, nullptr, nullptr);
    9704             :         }
    9705             :     }
    9706             : 
    9707             :     // Save X,Y dim positions.
    9708          57 :     panDimIds[nDim - 1] = poDS->nXDimID;
    9709          57 :     panBandDimPos[0] = nDim - 1;
    9710          57 :     panDimIds[nDim - 2] = poDS->nYDimID;
    9711          57 :     panBandDimPos[1] = nDim - 2;
    9712             : 
    9713             :     // Write extra dim values - after projection for optimization.
    9714          57 :     if (nDim > 2)
    9715             :     {
    9716             :         // Make sure we are in data mode.
    9717           5 :         static_cast<netCDFDataset *>(poDS)->SetDefineMode(false);
    9718          13 :         for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--)
    9719             :         {
    9720             :             char szTemp[NC_MAX_NAME + 32 + 1];
    9721           8 :             snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES",
    9722           8 :                      papszExtraDimNames[i]);
    9723           8 :             if (poSrcDS->GetMetadataItem(szTemp) != nullptr)
    9724             :             {
    9725           8 :                 NCDFPut1DVar(poDS->cdfid, panDimVarIds[i],
    9726           8 :                              poSrcDS->GetMetadataItem(szTemp));
    9727             :             }
    9728             :         }
    9729             :     }
    9730             : 
    9731          57 :     pfnProgress(0.25, nullptr, pProgressData);
    9732             : 
    9733             :     // Define Bands.
    9734          57 :     netCDFRasterBand *poBand = nullptr;
    9735          57 :     int nBandID = -1;
    9736             : 
    9737         147 :     for (int iBand = 1; iBand <= nBands; iBand++)
    9738             :     {
    9739          90 :         CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand,
    9740             :                  nBands, nDim);
    9741             : 
    9742          90 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9743          90 :         eDT = poSrcBand->GetRasterDataType();
    9744             : 
    9745             :         // Get var name from NETCDF_VARNAME.
    9746             :         const char *pszNETCDF_VARNAME =
    9747          90 :             poSrcBand->GetMetadataItem("NETCDF_VARNAME");
    9748             :         char szBandName[NC_MAX_NAME + 1];
    9749          90 :         if (!aosBandNames.empty())
    9750             :         {
    9751           2 :             snprintf(szBandName, sizeof(szBandName), "%s",
    9752             :                      aosBandNames[iBand - 1]);
    9753             :         }
    9754          88 :         else if (pszNETCDF_VARNAME)
    9755             :         {
    9756          32 :             if (nBands > 1 && papszExtraDimNames == nullptr)
    9757           0 :                 snprintf(szBandName, sizeof(szBandName), "%s%d",
    9758             :                          pszNETCDF_VARNAME, iBand);
    9759             :             else
    9760          32 :                 snprintf(szBandName, sizeof(szBandName), "%s",
    9761             :                          pszNETCDF_VARNAME);
    9762             :         }
    9763             :         else
    9764             :         {
    9765          56 :             szBandName[0] = '\0';
    9766             :         }
    9767             : 
    9768             :         // Get long_name from <var>#long_name.
    9769          90 :         const char *pszLongName = "";
    9770          90 :         if (pszNETCDF_VARNAME)
    9771             :         {
    9772             :             pszLongName =
    9773          64 :                 poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME)
    9774          32 :                                              .append("#")
    9775          32 :                                              .append(CF_LNG_NAME)
    9776          32 :                                              .c_str());
    9777          32 :             if (!pszLongName)
    9778          25 :                 pszLongName = "";
    9779             :         }
    9780             : 
    9781          90 :         constexpr bool bSignedData = false;
    9782             : 
    9783          90 :         if (nDim > 2)
    9784          27 :             poBand = new netCDFRasterBand(
    9785          27 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9786             :                 bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1,
    9787          27 :                 panBandZLev, panBandDimPos, panDimIds);
    9788             :         else
    9789          63 :             poBand = new netCDFRasterBand(
    9790          63 :                 netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand,
    9791          63 :                 bSignedData, szBandName, pszLongName);
    9792             : 
    9793          90 :         poDS->SetBand(iBand, poBand);
    9794             : 
    9795             :         // Set nodata value, if any.
    9796          90 :         GDALCopyNoDataValue(poBand, poSrcBand);
    9797             : 
    9798             :         // Copy Metadata for band.
    9799          90 :         CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand,
    9800             :                      poDS->cdfid, poBand->nZId);
    9801             : 
    9802             :         // If more than 2D pass the first band's netcdf var ID to subsequent
    9803             :         // bands.
    9804          90 :         if (nDim > 2)
    9805          27 :             nBandID = poBand->nZId;
    9806             :     }
    9807             : 
    9808             :     // Write projection variable to band variable.
    9809          57 :     poDS->AddGridMappingRef();
    9810             : 
    9811          57 :     pfnProgress(0.5, nullptr, pProgressData);
    9812             : 
    9813             :     // Write bands.
    9814             : 
    9815             :     // Make sure we are in data mode.
    9816          57 :     poDS->SetDefineMode(false);
    9817             : 
    9818          57 :     double dfTemp = 0.5;
    9819             : 
    9820          57 :     eErr = CE_None;
    9821             : 
    9822         147 :     for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++)
    9823             :     {
    9824          90 :         const double dfTemp2 = dfTemp + 0.4 / nBands;
    9825          90 :         pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress,
    9826             :                                                    pProgressData);
    9827          90 :         dfTemp = dfTemp2;
    9828             : 
    9829          90 :         CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands);
    9830             : 
    9831          90 :         poSrcBand = poSrcDS->GetRasterBand(iBand);
    9832          90 :         eDT = poSrcBand->GetRasterDataType();
    9833             : 
    9834          90 :         GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand);
    9835             : 
    9836             :         // Copy band data.
    9837          90 :         if (eDT == GDT_Byte)
    9838             :         {
    9839          50 :             CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand);
    9840          50 :             eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize,
    9841             :                                        GDALScaledProgress, pScaledProgress);
    9842             :         }
    9843          40 :         else if (eDT == GDT_Int8)
    9844             :         {
    9845           1 :             CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand);
    9846           1 :             eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize,
    9847             :                                        GDALScaledProgress, pScaledProgress);
    9848             :         }
    9849          39 :         else if (eDT == GDT_UInt16)
    9850             :         {
    9851           2 :             CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand);
    9852           2 :             eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9853             :                                         GDALScaledProgress, pScaledProgress);
    9854             :         }
    9855          37 :         else if (eDT == GDT_Int16)
    9856             :         {
    9857           5 :             CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand);
    9858           5 :             eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize,
    9859             :                                          GDALScaledProgress, pScaledProgress);
    9860             :         }
    9861          32 :         else if (eDT == GDT_UInt32)
    9862             :         {
    9863           2 :             CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand);
    9864           2 :             eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9865             :                                          GDALScaledProgress, pScaledProgress);
    9866             :         }
    9867          30 :         else if (eDT == GDT_Int32)
    9868             :         {
    9869          18 :             CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand);
    9870          18 :             eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize,
    9871             :                                         GDALScaledProgress, pScaledProgress);
    9872             :         }
    9873          12 :         else if (eDT == GDT_UInt64)
    9874             :         {
    9875           2 :             CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand);
    9876           2 :             eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize,
    9877             :                                                nYSize, GDALScaledProgress,
    9878             :                                                pScaledProgress);
    9879             :         }
    9880          10 :         else if (eDT == GDT_Int64)
    9881             :         {
    9882           2 :             CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand);
    9883             :             eErr =
    9884           2 :                 NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize,
    9885             :                                            GDALScaledProgress, pScaledProgress);
    9886             :         }
    9887           8 :         else if (eDT == GDT_Float32)
    9888             :         {
    9889           6 :             CPLDebug("GDAL_netCDF", "float Band#%d", iBand);
    9890           6 :             eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize,
    9891             :                                        GDALScaledProgress, pScaledProgress);
    9892             :         }
    9893           2 :         else if (eDT == GDT_Float64)
    9894             :         {
    9895           2 :             CPLDebug("GDAL_netCDF", "double Band#%d", iBand);
    9896           2 :             eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize,
    9897             :                                         GDALScaledProgress, pScaledProgress);
    9898             :         }
    9899             :         else
    9900             :         {
    9901           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9902             :                      "The NetCDF driver does not support GDAL data type %d",
    9903             :                      eDT);
    9904             :         }
    9905             : 
    9906          90 :         GDALDestroyScaledProgress(pScaledProgress);
    9907             :     }
    9908             : 
    9909          57 :     delete (poDS);
    9910             : 
    9911          57 :     CPLFree(panDimIds);
    9912          57 :     CPLFree(panBandDimPos);
    9913          57 :     CPLFree(panBandZLev);
    9914          57 :     CPLFree(panDimVarIds);
    9915          57 :     if (papszExtraDimNames)
    9916           5 :         CSLDestroy(papszExtraDimNames);
    9917             : 
    9918          57 :     if (eErr != CE_None)
    9919           0 :         return nullptr;
    9920             : 
    9921          57 :     pfnProgress(0.95, nullptr, pProgressData);
    9922             : 
    9923             :     // Re-open dataset so we can return it.
    9924         114 :     CPLStringList aosOpenOptions;
    9925          57 :     aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES");
    9926          57 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
    9927          57 :     oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE;
    9928          57 :     oOpenInfo.papszOpenOptions = aosOpenOptions.List();
    9929          57 :     auto poRetDS = Open(&oOpenInfo);
    9930             : 
    9931             :     // PAM cloning is disabled. See bug #4244.
    9932             :     // if( poDS )
    9933             :     //     poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
    9934             : 
    9935          57 :     pfnProgress(1.0, nullptr, pProgressData);
    9936             : 
    9937          57 :     return poRetDS;
    9938             : }
    9939             : 
    9940             : // Note: some logic depends on bIsProjected and bIsGeoGraphic.
    9941             : // May not be known when Create() is called, see AddProjectionVars().
    9942         253 : void netCDFDataset::ProcessCreationOptions()
    9943             : {
    9944             :     const char *pszConfig =
    9945         253 :         CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE");
    9946         253 :     if (pszConfig != nullptr)
    9947             :     {
    9948           4 :         if (oWriterConfig.Parse(pszConfig))
    9949             :         {
    9950             :             // Override dataset creation options from the config file
    9951           2 :             std::map<CPLString, CPLString>::iterator oIter;
    9952           3 :             for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin();
    9953           3 :                  oIter != oWriterConfig.m_oDatasetCreationOptions.end();
    9954           1 :                  ++oIter)
    9955             :             {
    9956           2 :                 papszCreationOptions = CSLSetNameValue(
    9957           2 :                     papszCreationOptions, oIter->first, oIter->second);
    9958             :             }
    9959             :         }
    9960             :     }
    9961             : 
    9962             :     // File format.
    9963         253 :     eFormat = NCDF_FORMAT_NC;
    9964         253 :     const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT");
    9965         253 :     if (pszValue != nullptr)
    9966             :     {
    9967          93 :         if (EQUAL(pszValue, "NC"))
    9968             :         {
    9969           3 :             eFormat = NCDF_FORMAT_NC;
    9970             :         }
    9971             : #ifdef NETCDF_HAS_NC2
    9972          90 :         else if (EQUAL(pszValue, "NC2"))
    9973             :         {
    9974           1 :             eFormat = NCDF_FORMAT_NC2;
    9975             :         }
    9976             : #endif
    9977          89 :         else if (EQUAL(pszValue, "NC4"))
    9978             :         {
    9979          85 :             eFormat = NCDF_FORMAT_NC4;
    9980             :         }
    9981           4 :         else if (EQUAL(pszValue, "NC4C"))
    9982             :         {
    9983           4 :             eFormat = NCDF_FORMAT_NC4C;
    9984             :         }
    9985             :         else
    9986             :         {
    9987           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    9988             :                      "FORMAT=%s in not supported, using the default NC format.",
    9989             :                      pszValue);
    9990             :         }
    9991             :     }
    9992             : 
    9993             :     // COMPRESS option.
    9994         253 :     pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS");
    9995         253 :     if (pszValue != nullptr)
    9996             :     {
    9997           3 :         if (EQUAL(pszValue, "NONE"))
    9998             :         {
    9999           1 :             eCompress = NCDF_COMPRESS_NONE;
   10000             :         }
   10001           2 :         else if (EQUAL(pszValue, "DEFLATE"))
   10002             :         {
   10003           2 :             eCompress = NCDF_COMPRESS_DEFLATE;
   10004           2 :             if (!((eFormat == NCDF_FORMAT_NC4) ||
   10005           2 :                   (eFormat == NCDF_FORMAT_NC4C)))
   10006             :             {
   10007           1 :                 CPLError(CE_Warning, CPLE_IllegalArg,
   10008             :                          "NOTICE: Format set to NC4C because compression is "
   10009             :                          "set to DEFLATE.");
   10010           1 :                 eFormat = NCDF_FORMAT_NC4C;
   10011             :             }
   10012             :         }
   10013             :         else
   10014             :         {
   10015           0 :             CPLError(CE_Failure, CPLE_NotSupported,
   10016             :                      "COMPRESS=%s is not supported.", pszValue);
   10017             :         }
   10018             :     }
   10019             : 
   10020             :     // ZLEVEL option.
   10021         253 :     pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL");
   10022         253 :     if (pszValue != nullptr)
   10023             :     {
   10024           1 :         nZLevel = atoi(pszValue);
   10025           1 :         if (!(nZLevel >= 1 && nZLevel <= 9))
   10026             :         {
   10027           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10028             :                      "ZLEVEL=%s value not recognised, ignoring.", pszValue);
   10029           0 :             nZLevel = NCDF_DEFLATE_LEVEL;
   10030             :         }
   10031             :     }
   10032             : 
   10033             :     // CHUNKING option.
   10034         253 :     bChunking =
   10035         253 :         CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE));
   10036             : 
   10037             :     // MULTIPLE_LAYERS option.
   10038             :     const char *pszMultipleLayerBehavior =
   10039         253 :         CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO");
   10040         506 :     const char *pszGeometryEnc = CSLFetchNameValueDef(
   10041         253 :         papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8");
   10042         253 :     if (EQUAL(pszMultipleLayerBehavior, "NO") ||
   10043           3 :         EQUAL(pszGeometryEnc, "CF_1.8"))
   10044             :     {
   10045         250 :         eMultipleLayerBehavior = SINGLE_LAYER;
   10046             :     }
   10047           3 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES"))
   10048             :     {
   10049           2 :         eMultipleLayerBehavior = SEPARATE_FILES;
   10050             :     }
   10051           1 :     else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS"))
   10052             :     {
   10053           1 :         if (eFormat == NCDF_FORMAT_NC4)
   10054             :         {
   10055           1 :             eMultipleLayerBehavior = SEPARATE_GROUPS;
   10056             :         }
   10057             :         else
   10058             :         {
   10059           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
   10060             :                      "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4",
   10061             :                      pszMultipleLayerBehavior);
   10062             :         }
   10063             :     }
   10064             :     else
   10065             :     {
   10066           0 :         CPLError(CE_Warning, CPLE_IllegalArg,
   10067             :                  "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior);
   10068             :     }
   10069             : 
   10070             :     // Set nCreateMode based on eFormat.
   10071         253 :     switch (eFormat)
   10072             :     {
   10073             : #ifdef NETCDF_HAS_NC2
   10074           1 :         case NCDF_FORMAT_NC2:
   10075           1 :             nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET;
   10076           1 :             break;
   10077             : #endif
   10078          85 :         case NCDF_FORMAT_NC4:
   10079          85 :             nCreateMode = NC_CLOBBER | NC_NETCDF4;
   10080          85 :             break;
   10081           5 :         case NCDF_FORMAT_NC4C:
   10082           5 :             nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL;
   10083           5 :             break;
   10084         162 :         case NCDF_FORMAT_NC:
   10085             :         default:
   10086         162 :             nCreateMode = NC_CLOBBER;
   10087         162 :             break;
   10088             :     }
   10089             : 
   10090         253 :     CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d",
   10091         253 :              eFormat, eCompress, nZLevel);
   10092         253 : }
   10093             : 
   10094         275 : int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg)
   10095             : {
   10096         275 :     if (eCompress == NCDF_COMPRESS_DEFLATE)
   10097             :     {
   10098             :         // Must set chunk size to avoid huge performance hit (set
   10099             :         // bChunkingArg=TRUE)
   10100             :         // perhaps another solution it to change the chunk cache?
   10101             :         // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache
   10102             :         // TODO: make sure this is okay.
   10103           2 :         CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId,
   10104             :                  static_cast<int>(bChunkingArg), nZLevel);
   10105             : 
   10106           2 :         int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel);
   10107           2 :         NCDF_ERR(status);
   10108             : 
   10109           2 :         if (status == NC_NOERR && bChunkingArg && bChunking)
   10110             :         {
   10111             :             // set chunking to be 1 for all dims, except X dim
   10112             :             // size_t chunksize[] = { 1, (size_t)nRasterXSize };
   10113             :             size_t chunksize[MAX_NC_DIMS];
   10114             :             int nd;
   10115           2 :             nc_inq_varndims(cdfid, nVarId, &nd);
   10116           2 :             chunksize[0] = (size_t)1;
   10117           2 :             chunksize[1] = (size_t)1;
   10118           2 :             for (int i = 2; i < nd; i++)
   10119           0 :                 chunksize[i] = (size_t)1;
   10120           2 :             chunksize[nd - 1] = (size_t)nRasterXSize;
   10121             : 
   10122             :             // Config options just for testing purposes
   10123             :             const char *pszBlockXSize =
   10124           2 :                 CPLGetConfigOption("BLOCKXSIZE", nullptr);
   10125           2 :             if (pszBlockXSize)
   10126           0 :                 chunksize[nd - 1] = (size_t)atoi(pszBlockXSize);
   10127             : 
   10128             :             const char *pszBlockYSize =
   10129           2 :                 CPLGetConfigOption("BLOCKYSIZE", nullptr);
   10130           2 :             if (nd >= 2 && pszBlockYSize)
   10131           0 :                 chunksize[nd - 2] = (size_t)atoi(pszBlockYSize);
   10132             : 
   10133           2 :             CPLDebug("GDAL_netCDF",
   10134             :                      "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d",
   10135           2 :                      (long)chunksize[0], (long)chunksize[1],
   10136           2 :                      (long)chunksize[nd - 1], nd);
   10137             : #ifdef NCDF_DEBUG
   10138             :             for (int i = 0; i < nd; i++)
   10139             :                 CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i,
   10140             :                          chunksize[i]);
   10141             : #endif
   10142             : 
   10143           2 :             status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize);
   10144           2 :             NCDF_ERR(status);
   10145             :         }
   10146             :         else
   10147             :         {
   10148           0 :             CPLDebug("GDAL_netCDF", "chunksize not set");
   10149             :         }
   10150           2 :         return status;
   10151             :     }
   10152         273 :     return NC_NOERR;
   10153             : }
   10154             : 
   10155             : /************************************************************************/
   10156             : /*                           NCDFUnloadDriver()                         */
   10157             : /************************************************************************/
   10158             : 
   10159           8 : static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver)
   10160             : {
   10161           8 :     if (hNCMutex != nullptr)
   10162           4 :         CPLDestroyMutex(hNCMutex);
   10163           8 :     hNCMutex = nullptr;
   10164           8 : }
   10165             : 
   10166             : /************************************************************************/
   10167             : /*                          GDALRegister_netCDF()                       */
   10168             : /************************************************************************/
   10169             : 
   10170             : class GDALnetCDFDriver final : public GDALDriver
   10171             : {
   10172             :   public:
   10173          18 :     GDALnetCDFDriver() = default;
   10174             : 
   10175        1339 :     const char *GetMetadataItem(const char *pszName,
   10176             :                                 const char *pszDomain) override
   10177             :     {
   10178        1339 :         if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO))
   10179             :         {
   10180          13 :             InitializeDCAPVirtualIO();
   10181             :         }
   10182        1339 :         return GDALDriver::GetMetadataItem(pszName, pszDomain);
   10183             :     }
   10184             : 
   10185          85 :     char **GetMetadata(const char *pszDomain) override
   10186             :     {
   10187          85 :         InitializeDCAPVirtualIO();
   10188          85 :         return GDALDriver::GetMetadata(pszDomain);
   10189             :     }
   10190             : 
   10191             :   private:
   10192             :     bool m_bInitialized = false;
   10193             : 
   10194          98 :     void InitializeDCAPVirtualIO()
   10195             :     {
   10196          98 :         if (!m_bInitialized)
   10197             :         {
   10198          12 :             m_bInitialized = true;
   10199             : 
   10200             : #ifdef ENABLE_UFFD
   10201          12 :             if (CPLIsUserFaultMappingSupported())
   10202             :             {
   10203          12 :                 SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
   10204             :             }
   10205             : #endif
   10206             :         }
   10207          98 :     }
   10208             : };
   10209             : 
   10210          18 : void GDALRegister_netCDF()
   10211             : 
   10212             : {
   10213          18 :     if (!GDAL_CHECK_VERSION("netCDF driver"))
   10214           0 :         return;
   10215             : 
   10216          18 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
   10217           0 :         return;
   10218             : 
   10219          18 :     GDALDriver *poDriver = new GDALnetCDFDriver();
   10220          18 :     netCDFDriverSetCommonMetadata(poDriver);
   10221             : 
   10222          18 :     poDriver->SetMetadataItem("NETCDF_CONVENTIONS",
   10223          18 :                               GDAL_DEFAULT_NCDF_CONVENTIONS);
   10224          18 :     poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers());
   10225             : 
   10226             :     // Set pfns and register driver.
   10227          18 :     poDriver->pfnOpen = netCDFDataset::Open;
   10228          18 :     poDriver->pfnCreateCopy = netCDFDataset::CreateCopy;
   10229          18 :     poDriver->pfnCreate = netCDFDataset::Create;
   10230          18 :     poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional;
   10231          18 :     poDriver->pfnUnloadDriver = NCDFUnloadDriver;
   10232             : 
   10233          18 :     GetGDALDriverManager()->RegisterDriver(poDriver);
   10234             : }
   10235             : 
   10236             : /************************************************************************/
   10237             : /*                          New functions                               */
   10238             : /************************************************************************/
   10239             : 
   10240             : /* Test for GDAL version string >= target */
   10241         239 : static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget)
   10242             : {
   10243             : 
   10244             :     // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ".
   10245         239 :     if (pszVersion == nullptr || EQUAL(pszVersion, ""))
   10246           0 :         return false;
   10247         239 :     else if (!STARTS_WITH_CI(pszVersion, "GDAL "))
   10248           0 :         return false;
   10249             :     // 2.0dev of 2011/12/29 has been later renamed as 1.10dev.
   10250         239 :     else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion))
   10251           0 :         return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0);
   10252         239 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev"))
   10253           2 :         return nTarget <= 1900;
   10254         237 :     else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev"))
   10255           0 :         return nTarget <= 1800;
   10256             : 
   10257         237 :     char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0);
   10258             : 
   10259         237 :     int nVersions[] = {0, 0, 0, 0};
   10260         948 :     for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken];
   10261             :          iToken++)
   10262             :     {
   10263         711 :         nVersions[iToken] = atoi(papszTokens[iToken]);
   10264         711 :         if (nVersions[iToken] < 0)
   10265           0 :             nVersions[iToken] = 0;
   10266         711 :         else if (nVersions[iToken] > 99)
   10267           0 :             nVersions[iToken] = 99;
   10268             :     }
   10269             : 
   10270         237 :     int nVersion = 0;
   10271         237 :     if (nVersions[0] > 1 || nVersions[1] >= 10)
   10272         237 :         nVersion =
   10273         237 :             GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]);
   10274             :     else
   10275           0 :         nVersion = nVersions[0] * 1000 + nVersions[1] * 100 +
   10276           0 :                    nVersions[2] * 10 + nVersions[3];
   10277             : 
   10278         237 :     CSLDestroy(papszTokens);
   10279         237 :     return nTarget <= nVersion;
   10280             : }
   10281             : 
   10282             : // Add Conventions, GDAL version and history.
   10283         166 : static void NCDFAddGDALHistory(int fpImage, const char *pszFilename,
   10284             :                                bool bWriteGDALVersion, bool bWriteGDALHistory,
   10285             :                                const char *pszOldHist,
   10286             :                                const char *pszFunctionName,
   10287             :                                const char *pszCFVersion)
   10288             : {
   10289         166 :     if (pszCFVersion == nullptr)
   10290             :     {
   10291          41 :         pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS;
   10292             :     }
   10293         166 :     int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions",
   10294             :                                  strlen(pszCFVersion), pszCFVersion);
   10295         166 :     NCDF_ERR(status);
   10296             : 
   10297         166 :     if (bWriteGDALVersion)
   10298             :     {
   10299         164 :         const char *pszNCDF_GDAL = GDALVersionInfo("--version");
   10300         164 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL",
   10301             :                                  strlen(pszNCDF_GDAL), pszNCDF_GDAL);
   10302         164 :         NCDF_ERR(status);
   10303             :     }
   10304             : 
   10305         166 :     if (bWriteGDALHistory)
   10306             :     {
   10307             :         // Add history.
   10308         328 :         CPLString osTmp;
   10309             : #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP
   10310             :         if (!EQUAL(GDALGetCmdLine(), ""))
   10311             :             osTmp = GDALGetCmdLine();
   10312             :         else
   10313             :             osTmp =
   10314             :                 CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10315             : #else
   10316         164 :         osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename);
   10317             : #endif
   10318             : 
   10319         164 :         NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist);
   10320             :     }
   10321           2 :     else if (pszOldHist != nullptr)
   10322             :     {
   10323           0 :         status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10324             :                                  strlen(pszOldHist), pszOldHist);
   10325           0 :         NCDF_ERR(status);
   10326             :     }
   10327         166 : }
   10328             : 
   10329             : // Code taken from cdo and libcdi, used for writing the history attribute.
   10330             : 
   10331             : // void cdoDefHistory(int fileID, char *histstring)
   10332         164 : static void NCDFAddHistory(int fpImage, const char *pszAddHist,
   10333             :                            const char *pszOldHist)
   10334             : {
   10335             :     // Check pszOldHist - as if there was no previous history, it will be
   10336             :     // a null pointer - if so set as empty.
   10337         164 :     if (nullptr == pszOldHist)
   10338             :     {
   10339          52 :         pszOldHist = "";
   10340             :     }
   10341             : 
   10342             :     char strtime[32];
   10343         164 :     strtime[0] = '\0';
   10344             : 
   10345         164 :     time_t tp = time(nullptr);
   10346         164 :     if (tp != -1)
   10347             :     {
   10348             :         struct tm ltime;
   10349         164 :         VSILocalTime(&tp, &ltime);
   10350         164 :         (void)strftime(strtime, sizeof(strtime),
   10351             :                        "%a %b %d %H:%M:%S %Y: ", &ltime);
   10352             :     }
   10353             : 
   10354             :     // status = nc_get_att_text(fpImage, NC_GLOBAL,
   10355             :     //                           "history", pszOldHist);
   10356             :     // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist);
   10357             : 
   10358         164 :     size_t nNewHistSize =
   10359         164 :         strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1;
   10360             :     char *pszNewHist =
   10361         164 :         static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char)));
   10362             : 
   10363         164 :     strcpy(pszNewHist, strtime);
   10364         164 :     strcat(pszNewHist, pszAddHist);
   10365             : 
   10366             :     // int disableHistory = FALSE;
   10367             :     // if( !disableHistory )
   10368             :     {
   10369         164 :         if (!EQUAL(pszOldHist, ""))
   10370           3 :             strcat(pszNewHist, "\n");
   10371         164 :         strcat(pszNewHist, pszOldHist);
   10372             :     }
   10373             : 
   10374         164 :     const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history",
   10375             :                                        strlen(pszNewHist), pszNewHist);
   10376         164 :     NCDF_ERR(status);
   10377             : 
   10378         164 :     CPLFree(pszNewHist);
   10379         164 : }
   10380             : 
   10381        6010 : static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc,
   10382             :                              size_t *nDestSize)
   10383             : {
   10384             :     /* Reallocate the data string until the content fits */
   10385        6010 :     while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1))
   10386             :     {
   10387         394 :         (*nDestSize) *= 2;
   10388         394 :         *ppszDest = static_cast<char *>(
   10389         394 :             CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize));
   10390             : #ifdef NCDF_DEBUG
   10391             :         CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld",
   10392             :                  (*nDestSize) / 2, *nDestSize);
   10393             : #endif
   10394             :     }
   10395        5616 :     strcat(*ppszDest, pszSrc);
   10396             : 
   10397        5616 :     return CE_None;
   10398             : }
   10399             : 
   10400             : /* helper function for NCDFGetAttr() */
   10401             : /* if pdfValue != nullptr, sets *pdfValue to first value returned */
   10402             : /* if ppszValue != nullptr, sets *ppszValue with all attribute values */
   10403             : /* *ppszValue is the responsibility of the caller and must be freed */
   10404       62786 : static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName,
   10405             :                            double *pdfValue, char **ppszValue)
   10406             : {
   10407       62786 :     nc_type nAttrType = NC_NAT;
   10408       62786 :     size_t nAttrLen = 0;
   10409             : 
   10410       62786 :     if (ppszValue)
   10411       61640 :         *ppszValue = nullptr;
   10412             : 
   10413       62786 :     int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen);
   10414       62786 :     if (status != NC_NOERR)
   10415       33741 :         return CE_Failure;
   10416             : 
   10417             : #ifdef NCDF_DEBUG
   10418             :     CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName,
   10419             :              nAttrLen, nAttrType);
   10420             : #endif
   10421       29045 :     if (nAttrLen == 0 && nAttrType != NC_CHAR)
   10422           1 :         return CE_Failure;
   10423             : 
   10424             :     /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */
   10425       29044 :     size_t nAttrValueSize = nAttrLen + 1;
   10426       29044 :     if (nAttrType != NC_CHAR && nAttrValueSize < 10)
   10427        3139 :         nAttrValueSize = 10;
   10428       29044 :     if (nAttrType == NC_DOUBLE && nAttrValueSize < 20)
   10429        1547 :         nAttrValueSize = 20;
   10430       29044 :     if (nAttrType == NC_INT64 && nAttrValueSize < 20)
   10431          20 :         nAttrValueSize = 22;
   10432             :     char *pszAttrValue =
   10433       29044 :         static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char)));
   10434       29044 :     *pszAttrValue = '\0';
   10435             : 
   10436       29044 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10437         572 :         NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize);
   10438             : 
   10439       29044 :     double dfValue = 0.0;
   10440       29044 :     size_t m = 0;
   10441             :     char szTemp[256];
   10442       29044 :     bool bSetDoubleFromStr = false;
   10443             : 
   10444       29044 :     switch (nAttrType)
   10445             :     {
   10446       25903 :         case NC_CHAR:
   10447       25903 :             CPL_IGNORE_RET_VAL(
   10448       25903 :                 nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue));
   10449       25903 :             pszAttrValue[nAttrLen] = '\0';
   10450       25903 :             bSetDoubleFromStr = true;
   10451       25903 :             dfValue = 0.0;
   10452       25903 :             break;
   10453          90 :         case NC_BYTE:
   10454             :         {
   10455             :             signed char *pscTemp = static_cast<signed char *>(
   10456          90 :                 CPLCalloc(nAttrLen, sizeof(signed char)));
   10457          90 :             nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp);
   10458          90 :             dfValue = static_cast<double>(pscTemp[0]);
   10459          90 :             if (nAttrLen > 1)
   10460             :             {
   10461          20 :                 for (m = 0; m < nAttrLen - 1; m++)
   10462             :                 {
   10463          11 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10464          11 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10465             :                 }
   10466             :             }
   10467          90 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10468          90 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10469          90 :             CPLFree(pscTemp);
   10470          90 :             break;
   10471             :         }
   10472         475 :         case NC_SHORT:
   10473             :         {
   10474             :             short *psTemp =
   10475         475 :                 static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short)));
   10476         475 :             nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp);
   10477         475 :             dfValue = static_cast<double>(psTemp[0]);
   10478         475 :             if (nAttrLen > 1)
   10479             :             {
   10480         748 :                 for (m = 0; m < nAttrLen - 1; m++)
   10481             :                 {
   10482         374 :                     snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10483         374 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10484             :                 }
   10485             :             }
   10486         475 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10487         475 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10488         475 :             CPLFree(psTemp);
   10489         475 :             break;
   10490             :         }
   10491         524 :         case NC_INT:
   10492             :         {
   10493         524 :             int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10494         524 :             nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp);
   10495         524 :             dfValue = static_cast<double>(pnTemp[0]);
   10496         524 :             if (nAttrLen > 1)
   10497             :             {
   10498         214 :                 for (m = 0; m < nAttrLen - 1; m++)
   10499             :                 {
   10500         137 :                     snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10501         137 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10502             :                 }
   10503             :             }
   10504         524 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10505         524 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10506         524 :             CPLFree(pnTemp);
   10507         524 :             break;
   10508             :         }
   10509         391 :         case NC_FLOAT:
   10510             :         {
   10511             :             float *pfTemp =
   10512         391 :                 static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10513         391 :             nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp);
   10514         391 :             dfValue = static_cast<double>(pfTemp[0]);
   10515         391 :             if (nAttrLen > 1)
   10516             :             {
   10517          56 :                 for (m = 0; m < nAttrLen - 1; m++)
   10518             :                 {
   10519          28 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   10520          28 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10521             :                 }
   10522             :             }
   10523         391 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   10524         391 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10525         391 :             CPLFree(pfTemp);
   10526         391 :             break;
   10527             :         }
   10528        1547 :         case NC_DOUBLE:
   10529             :         {
   10530             :             double *pdfTemp =
   10531        1547 :                 static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10532        1547 :             nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp);
   10533        1547 :             dfValue = pdfTemp[0];
   10534        1547 :             if (nAttrLen > 1)
   10535             :             {
   10536         162 :                 for (m = 0; m < nAttrLen - 1; m++)
   10537             :                 {
   10538          88 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   10539          88 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10540             :                 }
   10541             :             }
   10542        1547 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   10543        1547 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10544        1547 :             CPLFree(pdfTemp);
   10545        1547 :             break;
   10546             :         }
   10547           8 :         case NC_STRING:
   10548             :         {
   10549             :             char **ppszTemp =
   10550           8 :                 static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *)));
   10551           8 :             nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp);
   10552           8 :             bSetDoubleFromStr = true;
   10553           8 :             dfValue = 0.0;
   10554           8 :             if (nAttrLen > 1)
   10555             :             {
   10556          15 :                 for (m = 0; m < nAttrLen - 1; m++)
   10557             :                 {
   10558          10 :                     NCDFSafeStrcat(&pszAttrValue,
   10559          10 :                                    ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10560             :                                    &nAttrValueSize);
   10561          10 :                     NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize);
   10562             :                 }
   10563             :             }
   10564           8 :             NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}",
   10565             :                            &nAttrValueSize);
   10566           8 :             nc_free_string(nAttrLen, ppszTemp);
   10567           8 :             CPLFree(ppszTemp);
   10568           8 :             break;
   10569             :         }
   10570          26 :         case NC_UBYTE:
   10571             :         {
   10572             :             unsigned char *pucTemp = static_cast<unsigned char *>(
   10573          26 :                 CPLCalloc(nAttrLen, sizeof(unsigned char)));
   10574          26 :             nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp);
   10575          26 :             dfValue = static_cast<double>(pucTemp[0]);
   10576          26 :             if (nAttrLen > 1)
   10577             :             {
   10578           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10579             :                 {
   10580           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   10581           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10582             :                 }
   10583             :             }
   10584          26 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   10585          26 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10586          26 :             CPLFree(pucTemp);
   10587          26 :             break;
   10588             :         }
   10589          24 :         case NC_USHORT:
   10590             :         {
   10591             :             unsigned short *pusTemp;
   10592             :             pusTemp = static_cast<unsigned short *>(
   10593          24 :                 CPLCalloc(nAttrLen, sizeof(unsigned short)));
   10594          24 :             nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp);
   10595          24 :             dfValue = static_cast<double>(pusTemp[0]);
   10596          24 :             if (nAttrLen > 1)
   10597             :             {
   10598          10 :                 for (m = 0; m < nAttrLen - 1; m++)
   10599             :                 {
   10600           5 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   10601           5 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10602             :                 }
   10603             :             }
   10604          24 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   10605          24 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10606          24 :             CPLFree(pusTemp);
   10607          24 :             break;
   10608             :         }
   10609          16 :         case NC_UINT:
   10610             :         {
   10611             :             unsigned int *punTemp =
   10612          16 :                 static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10613          16 :             nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp);
   10614          16 :             dfValue = static_cast<double>(punTemp[0]);
   10615          16 :             if (nAttrLen > 1)
   10616             :             {
   10617           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10618             :                 {
   10619           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   10620           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10621             :                 }
   10622             :             }
   10623          16 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   10624          16 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10625          16 :             CPLFree(punTemp);
   10626          16 :             break;
   10627             :         }
   10628          20 :         case NC_INT64:
   10629             :         {
   10630             :             GIntBig *panTemp =
   10631          20 :                 static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig)));
   10632          20 :             nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp);
   10633          20 :             dfValue = static_cast<double>(panTemp[0]);
   10634          20 :             if (nAttrLen > 1)
   10635             :             {
   10636           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10637             :                 {
   10638           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",",
   10639           0 :                                 panTemp[m]);
   10640           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10641             :                 }
   10642             :             }
   10643          20 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]);
   10644          20 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10645          20 :             CPLFree(panTemp);
   10646          20 :             break;
   10647             :         }
   10648          20 :         case NC_UINT64:
   10649             :         {
   10650             :             GUIntBig *panTemp =
   10651          20 :                 static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig)));
   10652          20 :             nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp);
   10653          20 :             dfValue = static_cast<double>(panTemp[0]);
   10654          20 :             if (nAttrLen > 1)
   10655             :             {
   10656           0 :                 for (m = 0; m < nAttrLen - 1; m++)
   10657             :                 {
   10658           0 :                     CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",",
   10659           0 :                                 panTemp[m]);
   10660           0 :                     NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10661             :                 }
   10662             :             }
   10663          20 :             CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]);
   10664          20 :             NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize);
   10665          20 :             CPLFree(panTemp);
   10666          20 :             break;
   10667             :         }
   10668           0 :         default:
   10669           0 :             CPLDebug("GDAL_netCDF",
   10670             :                      "NCDFGetAttr unsupported type %d for attribute %s",
   10671             :                      nAttrType, pszAttrName);
   10672           0 :             break;
   10673             :     }
   10674             : 
   10675       29044 :     if (nAttrLen > 1 && nAttrType != NC_CHAR)
   10676         572 :         NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize);
   10677             : 
   10678       29044 :     if (bSetDoubleFromStr)
   10679             :     {
   10680       25911 :         if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING)
   10681             :         {
   10682       25729 :             if (ppszValue == nullptr && pdfValue != nullptr)
   10683             :             {
   10684           1 :                 CPLFree(pszAttrValue);
   10685           1 :                 return CE_Failure;
   10686             :             }
   10687             :         }
   10688       25910 :         dfValue = CPLAtof(pszAttrValue);
   10689             :     }
   10690             : 
   10691             :     /* set return values */
   10692       29043 :     if (ppszValue)
   10693       28731 :         *ppszValue = pszAttrValue;
   10694             :     else
   10695         312 :         CPLFree(pszAttrValue);
   10696             : 
   10697       29043 :     if (pdfValue)
   10698         312 :         *pdfValue = dfValue;
   10699             : 
   10700       29043 :     return CE_None;
   10701             : }
   10702             : 
   10703             : /* sets pdfValue to first value found */
   10704        1146 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10705             :                    double *pdfValue)
   10706             : {
   10707        1146 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr);
   10708             : }
   10709             : 
   10710             : /* pszValue is the responsibility of the caller and must be freed */
   10711       61640 : CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10712             :                    char **pszValue)
   10713             : {
   10714       61640 :     return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue);
   10715             : }
   10716             : 
   10717             : /* By default write NC_CHAR, but detect for int/float/double and */
   10718             : /* NC4 string arrays */
   10719         106 : static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName,
   10720             :                           const char *pszValue)
   10721             : {
   10722         106 :     int status = 0;
   10723         106 :     char *pszTemp = nullptr;
   10724             : 
   10725             :     /* get the attribute values as tokens */
   10726         106 :     char **papszValues = NCDFTokenizeArray(pszValue);
   10727         106 :     if (papszValues == nullptr)
   10728           0 :         return CE_Failure;
   10729             : 
   10730         106 :     size_t nAttrLen = CSLCount(papszValues);
   10731             : 
   10732             :     /* first detect type */
   10733         106 :     nc_type nAttrType = NC_CHAR;
   10734         106 :     nc_type nTmpAttrType = NC_CHAR;
   10735         225 :     for (size_t i = 0; i < nAttrLen; i++)
   10736             :     {
   10737         119 :         nTmpAttrType = NC_CHAR;
   10738         119 :         bool bFoundType = false;
   10739         119 :         errno = 0;
   10740         119 :         int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   10741             :         /* test for int */
   10742             :         /* TODO test for Byte and short - can this be done safely? */
   10743         119 :         if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0)
   10744             :         {
   10745             :             char szTemp[256];
   10746          19 :             CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue);
   10747          19 :             if (EQUAL(szTemp, papszValues[i]))
   10748             :             {
   10749          19 :                 bFoundType = true;
   10750          19 :                 nTmpAttrType = NC_INT;
   10751             :             }
   10752             :             else
   10753             :             {
   10754             :                 unsigned int unValue = static_cast<unsigned int>(
   10755           0 :                     strtoul(papszValues[i], &pszTemp, 10));
   10756           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue);
   10757           0 :                 if (EQUAL(szTemp, papszValues[i]))
   10758             :                 {
   10759           0 :                     bFoundType = true;
   10760           0 :                     nTmpAttrType = NC_UINT;
   10761             :                 }
   10762             :             }
   10763             :         }
   10764         119 :         if (!bFoundType)
   10765             :         {
   10766             :             /* test for double */
   10767         100 :             errno = 0;
   10768         100 :             double dfValue = CPLStrtod(papszValues[i], &pszTemp);
   10769         100 :             if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0))
   10770             :             {
   10771             :                 // Test for float instead of double.
   10772             :                 // strtof() is C89, which is not available in MSVC.
   10773             :                 // See if we loose precision if we cast to float and write to
   10774             :                 // char*.
   10775          14 :                 float fValue = float(dfValue);
   10776             :                 char szTemp[256];
   10777          14 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue);
   10778          14 :                 if (EQUAL(szTemp, papszValues[i]))
   10779           8 :                     nTmpAttrType = NC_FLOAT;
   10780             :                 else
   10781           6 :                     nTmpAttrType = NC_DOUBLE;
   10782             :             }
   10783             :         }
   10784         119 :         if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE &&
   10785          99 :              nTmpAttrType > nAttrType) ||
   10786          99 :             (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) ||
   10787           5 :             (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT))
   10788          20 :             nAttrType = nTmpAttrType;
   10789             :     }
   10790             : 
   10791             : #ifdef DEBUG
   10792         106 :     if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR"))
   10793             :     {
   10794           0 :         nAttrType = NC_DOUBLE;
   10795           0 :         nAttrLen = 0;
   10796             :     }
   10797             : #endif
   10798             : 
   10799             :     /* now write the data */
   10800         106 :     if (nAttrType == NC_CHAR)
   10801             :     {
   10802          86 :         int nTmpFormat = 0;
   10803          86 :         if (nAttrLen > 1)
   10804             :         {
   10805           0 :             status = nc_inq_format(nCdfId, &nTmpFormat);
   10806           0 :             NCDF_ERR(status);
   10807             :         }
   10808          86 :         if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4)
   10809           0 :             status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen,
   10810             :                                        const_cast<const char **>(papszValues));
   10811             :         else
   10812          86 :             status = nc_put_att_text(nCdfId, nVarId, pszAttrName,
   10813             :                                      strlen(pszValue), pszValue);
   10814          86 :         NCDF_ERR(status);
   10815             :     }
   10816             :     else
   10817             :     {
   10818          20 :         switch (nAttrType)
   10819             :         {
   10820          11 :             case NC_INT:
   10821             :             {
   10822             :                 int *pnTemp =
   10823          11 :                     static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int)));
   10824          30 :                 for (size_t i = 0; i < nAttrLen; i++)
   10825             :                 {
   10826          19 :                     pnTemp[i] =
   10827          19 :                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   10828             :                 }
   10829          11 :                 status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT,
   10830             :                                         nAttrLen, pnTemp);
   10831          11 :                 NCDF_ERR(status);
   10832          11 :                 CPLFree(pnTemp);
   10833          11 :                 break;
   10834             :             }
   10835           0 :             case NC_UINT:
   10836             :             {
   10837             :                 unsigned int *punTemp = static_cast<unsigned int *>(
   10838           0 :                     CPLCalloc(nAttrLen, sizeof(unsigned int)));
   10839           0 :                 for (size_t i = 0; i < nAttrLen; i++)
   10840             :                 {
   10841           0 :                     punTemp[i] = static_cast<unsigned int>(
   10842           0 :                         strtol(papszValues[i], &pszTemp, 10));
   10843             :                 }
   10844           0 :                 status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT,
   10845             :                                          nAttrLen, punTemp);
   10846           0 :                 NCDF_ERR(status);
   10847           0 :                 CPLFree(punTemp);
   10848           0 :                 break;
   10849             :             }
   10850           6 :             case NC_FLOAT:
   10851             :             {
   10852             :                 float *pfTemp =
   10853           6 :                     static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float)));
   10854          14 :                 for (size_t i = 0; i < nAttrLen; i++)
   10855             :                 {
   10856           8 :                     pfTemp[i] =
   10857           8 :                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
   10858             :                 }
   10859           6 :                 status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT,
   10860             :                                           nAttrLen, pfTemp);
   10861           6 :                 NCDF_ERR(status);
   10862           6 :                 CPLFree(pfTemp);
   10863           6 :                 break;
   10864             :             }
   10865           3 :             case NC_DOUBLE:
   10866             :             {
   10867             :                 double *pdfTemp =
   10868           3 :                     static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double)));
   10869           9 :                 for (size_t i = 0; i < nAttrLen; i++)
   10870             :                 {
   10871           6 :                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
   10872             :                 }
   10873           3 :                 status = nc_put_att_double(nCdfId, nVarId, pszAttrName,
   10874             :                                            NC_DOUBLE, nAttrLen, pdfTemp);
   10875           3 :                 NCDF_ERR(status);
   10876           3 :                 CPLFree(pdfTemp);
   10877           3 :                 break;
   10878             :             }
   10879           0 :             default:
   10880           0 :                 if (papszValues)
   10881           0 :                     CSLDestroy(papszValues);
   10882           0 :                 return CE_Failure;
   10883             :                 break;
   10884             :         }
   10885             :     }
   10886             : 
   10887         106 :     if (papszValues)
   10888         106 :         CSLDestroy(papszValues);
   10889             : 
   10890         106 :     return CE_None;
   10891             : }
   10892             : 
   10893          76 : static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue)
   10894             : {
   10895             :     /* get var information */
   10896          76 :     int nVarDimId = -1;
   10897          76 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   10898          76 :     if (status != NC_NOERR || nVarDimId != 1)
   10899           0 :         return CE_Failure;
   10900             : 
   10901          76 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   10902          76 :     if (status != NC_NOERR)
   10903           0 :         return CE_Failure;
   10904             : 
   10905          76 :     nc_type nVarType = NC_NAT;
   10906          76 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   10907          76 :     if (status != NC_NOERR)
   10908           0 :         return CE_Failure;
   10909             : 
   10910          76 :     size_t nVarLen = 0;
   10911          76 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   10912          76 :     if (status != NC_NOERR)
   10913           0 :         return CE_Failure;
   10914             : 
   10915          76 :     size_t start[1] = {0};
   10916          76 :     size_t count[1] = {nVarLen};
   10917             : 
   10918             :     /* Allocate guaranteed minimum size */
   10919          76 :     size_t nVarValueSize = NCDF_MAX_STR_LEN;
   10920             :     char *pszVarValue =
   10921          76 :         static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char)));
   10922          76 :     *pszVarValue = '\0';
   10923             : 
   10924          76 :     if (nVarLen == 0)
   10925             :     {
   10926             :         /* set return values */
   10927           1 :         *pszValue = pszVarValue;
   10928             : 
   10929           1 :         return CE_None;
   10930             :     }
   10931             : 
   10932          75 :     if (nVarLen > 1 && nVarType != NC_CHAR)
   10933          40 :         NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize);
   10934             : 
   10935          75 :     switch (nVarType)
   10936             :     {
   10937           0 :         case NC_CHAR:
   10938           0 :             nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue);
   10939           0 :             pszVarValue[nVarLen] = '\0';
   10940           0 :             break;
   10941           0 :         case NC_BYTE:
   10942             :         {
   10943             :             signed char *pscTemp = static_cast<signed char *>(
   10944           0 :                 CPLCalloc(nVarLen, sizeof(signed char)));
   10945           0 :             nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   10946             :             char szTemp[256];
   10947           0 :             size_t m = 0;
   10948           0 :             for (; m < nVarLen - 1; m++)
   10949             :             {
   10950           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]);
   10951           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10952             :             }
   10953           0 :             snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]);
   10954           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10955           0 :             CPLFree(pscTemp);
   10956           0 :             break;
   10957             :         }
   10958           0 :         case NC_SHORT:
   10959             :         {
   10960             :             short *psTemp =
   10961           0 :                 static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   10962           0 :             nc_get_vara_short(nCdfId, nVarId, start, count, psTemp);
   10963             :             char szTemp[256];
   10964           0 :             size_t m = 0;
   10965           0 :             for (; m < nVarLen - 1; m++)
   10966             :             {
   10967           0 :                 snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]);
   10968           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10969             :             }
   10970           0 :             snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]);
   10971           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10972           0 :             CPLFree(psTemp);
   10973           0 :             break;
   10974             :         }
   10975          19 :         case NC_INT:
   10976             :         {
   10977          19 :             int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   10978          19 :             nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp);
   10979             :             char szTemp[256];
   10980          19 :             size_t m = 0;
   10981          36 :             for (; m < nVarLen - 1; m++)
   10982             :             {
   10983          17 :                 snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]);
   10984          17 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10985             :             }
   10986          19 :             snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]);
   10987          19 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   10988          19 :             CPLFree(pnTemp);
   10989          19 :             break;
   10990             :         }
   10991           8 :         case NC_FLOAT:
   10992             :         {
   10993             :             float *pfTemp =
   10994           8 :                 static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   10995           8 :             nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp);
   10996             :             char szTemp[256];
   10997           8 :             size_t m = 0;
   10998         325 :             for (; m < nVarLen - 1; m++)
   10999             :             {
   11000         317 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]);
   11001         317 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11002             :             }
   11003           8 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]);
   11004           8 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11005           8 :             CPLFree(pfTemp);
   11006           8 :             break;
   11007             :         }
   11008          47 :         case NC_DOUBLE:
   11009             :         {
   11010             :             double *pdfTemp =
   11011          47 :                 static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11012          47 :             nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11013             :             char szTemp[256];
   11014          47 :             size_t m = 0;
   11015         225 :             for (; m < nVarLen - 1; m++)
   11016             :             {
   11017         178 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]);
   11018         178 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11019             :             }
   11020          47 :             CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]);
   11021          47 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11022          47 :             CPLFree(pdfTemp);
   11023          47 :             break;
   11024             :         }
   11025           0 :         case NC_STRING:
   11026             :         {
   11027             :             char **ppszTemp =
   11028           0 :                 static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *)));
   11029           0 :             nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp);
   11030           0 :             size_t m = 0;
   11031           0 :             for (; m < nVarLen - 1; m++)
   11032             :             {
   11033           0 :                 NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11034           0 :                 NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize);
   11035             :             }
   11036           0 :             NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize);
   11037           0 :             nc_free_string(nVarLen, ppszTemp);
   11038           0 :             CPLFree(ppszTemp);
   11039           0 :             break;
   11040             :         }
   11041           0 :         case NC_UBYTE:
   11042             :         {
   11043             :             unsigned char *pucTemp;
   11044             :             pucTemp = static_cast<unsigned char *>(
   11045           0 :                 CPLCalloc(nVarLen, sizeof(unsigned char)));
   11046           0 :             nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp);
   11047             :             char szTemp[256];
   11048           0 :             size_t m = 0;
   11049           0 :             for (; m < nVarLen - 1; m++)
   11050             :             {
   11051           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]);
   11052           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11053             :             }
   11054           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]);
   11055           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11056           0 :             CPLFree(pucTemp);
   11057           0 :             break;
   11058             :         }
   11059           0 :         case NC_USHORT:
   11060             :         {
   11061             :             unsigned short *pusTemp;
   11062             :             pusTemp = static_cast<unsigned short *>(
   11063           0 :                 CPLCalloc(nVarLen, sizeof(unsigned short)));
   11064           0 :             nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp);
   11065             :             char szTemp[256];
   11066           0 :             size_t m = 0;
   11067           0 :             for (; m < nVarLen - 1; m++)
   11068             :             {
   11069           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]);
   11070           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11071             :             }
   11072           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]);
   11073           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11074           0 :             CPLFree(pusTemp);
   11075           0 :             break;
   11076             :         }
   11077           0 :         case NC_UINT:
   11078             :         {
   11079             :             unsigned int *punTemp;
   11080             :             punTemp = static_cast<unsigned int *>(
   11081           0 :                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11082           0 :             nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp);
   11083             :             char szTemp[256];
   11084           0 :             size_t m = 0;
   11085           0 :             for (; m < nVarLen - 1; m++)
   11086             :             {
   11087           0 :                 CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]);
   11088           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11089             :             }
   11090           0 :             CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]);
   11091           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11092           0 :             CPLFree(punTemp);
   11093           0 :             break;
   11094             :         }
   11095           1 :         case NC_INT64:
   11096             :         {
   11097             :             long long *pnTemp =
   11098           1 :                 static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long)));
   11099           1 :             nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp);
   11100             :             char szTemp[256];
   11101           1 :             size_t m = 0;
   11102           2 :             for (; m < nVarLen - 1; m++)
   11103             :             {
   11104           1 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]);
   11105           1 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11106             :             }
   11107           1 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]);
   11108           1 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11109           1 :             CPLFree(pnTemp);
   11110           1 :             break;
   11111             :         }
   11112           0 :         case NC_UINT64:
   11113             :         {
   11114             :             unsigned long long *pnTemp = static_cast<unsigned long long *>(
   11115           0 :                 CPLCalloc(nVarLen, sizeof(unsigned long long)));
   11116           0 :             nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp);
   11117             :             char szTemp[256];
   11118           0 :             size_t m = 0;
   11119           0 :             for (; m < nVarLen - 1; m++)
   11120             :             {
   11121           0 :                 snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]);
   11122           0 :                 NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11123             :             }
   11124           0 :             snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]);
   11125           0 :             NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize);
   11126           0 :             CPLFree(pnTemp);
   11127           0 :             break;
   11128             :         }
   11129           0 :         default:
   11130           0 :             CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d",
   11131             :                      nVarType);
   11132           0 :             CPLFree(pszVarValue);
   11133           0 :             pszVarValue = nullptr;
   11134           0 :             break;
   11135             :     }
   11136             : 
   11137          75 :     if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR)
   11138          40 :         NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize);
   11139             : 
   11140             :     /* set return values */
   11141          75 :     *pszValue = pszVarValue;
   11142             : 
   11143          75 :     return CE_None;
   11144             : }
   11145             : 
   11146           8 : static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue)
   11147             : {
   11148           8 :     if (EQUAL(pszValue, ""))
   11149           0 :         return CE_Failure;
   11150             : 
   11151             :     /* get var information */
   11152           8 :     int nVarDimId = -1;
   11153           8 :     int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId);
   11154           8 :     if (status != NC_NOERR || nVarDimId != 1)
   11155           0 :         return CE_Failure;
   11156             : 
   11157           8 :     status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId);
   11158           8 :     if (status != NC_NOERR)
   11159           0 :         return CE_Failure;
   11160             : 
   11161           8 :     nc_type nVarType = NC_CHAR;
   11162           8 :     status = nc_inq_vartype(nCdfId, nVarId, &nVarType);
   11163           8 :     if (status != NC_NOERR)
   11164           0 :         return CE_Failure;
   11165             : 
   11166           8 :     size_t nVarLen = 0;
   11167           8 :     status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen);
   11168           8 :     if (status != NC_NOERR)
   11169           0 :         return CE_Failure;
   11170             : 
   11171           8 :     size_t start[1] = {0};
   11172           8 :     size_t count[1] = {nVarLen};
   11173             : 
   11174             :     /* get the values as tokens */
   11175           8 :     char **papszValues = NCDFTokenizeArray(pszValue);
   11176           8 :     if (papszValues == nullptr)
   11177           0 :         return CE_Failure;
   11178             : 
   11179           8 :     nVarLen = CSLCount(papszValues);
   11180             : 
   11181             :     /* now write the data */
   11182           8 :     if (nVarType == NC_CHAR)
   11183             :     {
   11184           0 :         status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue);
   11185           0 :         NCDF_ERR(status);
   11186             :     }
   11187             :     else
   11188             :     {
   11189           8 :         switch (nVarType)
   11190             :         {
   11191           0 :             case NC_BYTE:
   11192             :             {
   11193             :                 signed char *pscTemp = static_cast<signed char *>(
   11194           0 :                     CPLCalloc(nVarLen, sizeof(signed char)));
   11195           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11196             :                 {
   11197           0 :                     char *pszTemp = nullptr;
   11198           0 :                     pscTemp[i] = static_cast<signed char>(
   11199           0 :                         strtol(papszValues[i], &pszTemp, 10));
   11200             :                 }
   11201             :                 status =
   11202           0 :                     nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp);
   11203           0 :                 NCDF_ERR(status);
   11204           0 :                 CPLFree(pscTemp);
   11205           0 :                 break;
   11206             :             }
   11207           0 :             case NC_SHORT:
   11208             :             {
   11209             :                 short *psTemp =
   11210           0 :                     static_cast<short *>(CPLCalloc(nVarLen, sizeof(short)));
   11211           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11212             :                 {
   11213           0 :                     char *pszTemp = nullptr;
   11214           0 :                     psTemp[i] = static_cast<short>(
   11215           0 :                         strtol(papszValues[i], &pszTemp, 10));
   11216             :                 }
   11217             :                 status =
   11218           0 :                     nc_put_vara_short(nCdfId, nVarId, start, count, psTemp);
   11219           0 :                 NCDF_ERR(status);
   11220           0 :                 CPLFree(psTemp);
   11221           0 :                 break;
   11222             :             }
   11223           3 :             case NC_INT:
   11224             :             {
   11225             :                 int *pnTemp =
   11226           3 :                     static_cast<int *>(CPLCalloc(nVarLen, sizeof(int)));
   11227          11 :                 for (size_t i = 0; i < nVarLen; i++)
   11228             :                 {
   11229           8 :                     char *pszTemp = nullptr;
   11230           8 :                     pnTemp[i] =
   11231           8 :                         static_cast<int>(strtol(papszValues[i], &pszTemp, 10));
   11232             :                 }
   11233           3 :                 status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp);
   11234           3 :                 NCDF_ERR(status);
   11235           3 :                 CPLFree(pnTemp);
   11236           3 :                 break;
   11237             :             }
   11238           0 :             case NC_FLOAT:
   11239             :             {
   11240             :                 float *pfTemp =
   11241           0 :                     static_cast<float *>(CPLCalloc(nVarLen, sizeof(float)));
   11242           0 :                 for (size_t i = 0; i < nVarLen; i++)
   11243             :                 {
   11244           0 :                     char *pszTemp = nullptr;
   11245           0 :                     pfTemp[i] =
   11246           0 :                         static_cast<float>(CPLStrtod(papszValues[i], &pszTemp));
   11247             :                 }
   11248             :                 status =
   11249           0 :                     nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp);
   11250           0 :                 NCDF_ERR(status);
   11251           0 :                 CPLFree(pfTemp);
   11252           0 :                 break;
   11253             :             }
   11254           5 :             case NC_DOUBLE:
   11255             :             {
   11256             :                 double *pdfTemp =
   11257           5 :                     static_cast<double *>(CPLCalloc(nVarLen, sizeof(double)));
   11258          19 :                 for (size_t i = 0; i < nVarLen; i++)
   11259             :                 {
   11260          14 :                     char *pszTemp = nullptr;
   11261          14 :                     pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp);
   11262             :                 }
   11263             :                 status =
   11264           5 :                     nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp);
   11265           5 :                 NCDF_ERR(status);
   11266           5 :                 CPLFree(pdfTemp);
   11267           5 :                 break;
   11268             :             }
   11269           0 :             default:
   11270             :             {
   11271           0 :                 int nTmpFormat = 0;
   11272           0 :                 status = nc_inq_format(nCdfId, &nTmpFormat);
   11273           0 :                 NCDF_ERR(status);
   11274           0 :                 if (nTmpFormat == NCDF_FORMAT_NC4)
   11275             :                 {
   11276           0 :                     switch (nVarType)
   11277             :                     {
   11278           0 :                         case NC_STRING:
   11279             :                         {
   11280             :                             status =
   11281           0 :                                 nc_put_vara_string(nCdfId, nVarId, start, count,
   11282             :                                                    (const char **)papszValues);
   11283           0 :                             NCDF_ERR(status);
   11284           0 :                             break;
   11285             :                         }
   11286           0 :                         case NC_UBYTE:
   11287             :                         {
   11288             :                             unsigned char *pucTemp =
   11289             :                                 static_cast<unsigned char *>(
   11290           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned char)));
   11291           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11292             :                             {
   11293           0 :                                 char *pszTemp = nullptr;
   11294           0 :                                 pucTemp[i] = static_cast<unsigned char>(
   11295           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11296             :                             }
   11297           0 :                             status = nc_put_vara_uchar(nCdfId, nVarId, start,
   11298             :                                                        count, pucTemp);
   11299           0 :                             NCDF_ERR(status);
   11300           0 :                             CPLFree(pucTemp);
   11301           0 :                             break;
   11302             :                         }
   11303           0 :                         case NC_USHORT:
   11304             :                         {
   11305             :                             unsigned short *pusTemp =
   11306             :                                 static_cast<unsigned short *>(
   11307           0 :                                     CPLCalloc(nVarLen, sizeof(unsigned short)));
   11308           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11309             :                             {
   11310           0 :                                 char *pszTemp = nullptr;
   11311           0 :                                 pusTemp[i] = static_cast<unsigned short>(
   11312           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11313             :                             }
   11314           0 :                             status = nc_put_vara_ushort(nCdfId, nVarId, start,
   11315             :                                                         count, pusTemp);
   11316           0 :                             NCDF_ERR(status);
   11317           0 :                             CPLFree(pusTemp);
   11318           0 :                             break;
   11319             :                         }
   11320           0 :                         case NC_UINT:
   11321             :                         {
   11322             :                             unsigned int *punTemp = static_cast<unsigned int *>(
   11323           0 :                                 CPLCalloc(nVarLen, sizeof(unsigned int)));
   11324           0 :                             for (size_t i = 0; i < nVarLen; i++)
   11325             :                             {
   11326           0 :                                 char *pszTemp = nullptr;
   11327           0 :                                 punTemp[i] = static_cast<unsigned int>(
   11328           0 :                                     strtoul(papszValues[i], &pszTemp, 10));
   11329             :                             }
   11330           0 :                             status = nc_put_vara_uint(nCdfId, nVarId, start,
   11331             :                                                       count, punTemp);
   11332           0 :                             NCDF_ERR(status);
   11333           0 :                             CPLFree(punTemp);
   11334           0 :                             break;
   11335             :                         }
   11336           0 :                         default:
   11337           0 :                             if (papszValues)
   11338           0 :                                 CSLDestroy(papszValues);
   11339           0 :                             return CE_Failure;
   11340             :                             break;
   11341             :                     }
   11342             :                 }
   11343           0 :                 break;
   11344             :             }
   11345             :         }
   11346             :     }
   11347             : 
   11348           8 :     if (papszValues)
   11349           8 :         CSLDestroy(papszValues);
   11350             : 
   11351           8 :     return CE_None;
   11352             : }
   11353             : 
   11354             : /************************************************************************/
   11355             : /*                           GetDefaultNoDataValue()                    */
   11356             : /************************************************************************/
   11357             : 
   11358         188 : double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType,
   11359             :                                  bool &bGotNoData)
   11360             : 
   11361             : {
   11362         188 :     int nNoFill = 0;
   11363         188 :     double dfNoData = 0.0;
   11364             : 
   11365         188 :     switch (nVarType)
   11366             :     {
   11367           0 :         case NC_CHAR:
   11368             :         case NC_BYTE:
   11369             :         case NC_UBYTE:
   11370             :             // Don't do default fill-values for bytes, too risky.
   11371             :             // This function should not be called in those cases.
   11372           0 :             CPLAssert(false);
   11373             :             break;
   11374          24 :         case NC_SHORT:
   11375             :         {
   11376          24 :             short nFillVal = 0;
   11377          24 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11378             :                 NC_NOERR)
   11379             :             {
   11380          24 :                 if (!nNoFill)
   11381             :                 {
   11382          23 :                     bGotNoData = true;
   11383          23 :                     dfNoData = nFillVal;
   11384             :                 }
   11385             :             }
   11386             :             else
   11387           0 :                 dfNoData = NC_FILL_SHORT;
   11388          24 :             break;
   11389             :         }
   11390          26 :         case NC_INT:
   11391             :         {
   11392          26 :             int nFillVal = 0;
   11393          26 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11394             :                 NC_NOERR)
   11395             :             {
   11396          26 :                 if (!nNoFill)
   11397             :                 {
   11398          25 :                     bGotNoData = true;
   11399          25 :                     dfNoData = nFillVal;
   11400             :                 }
   11401             :             }
   11402             :             else
   11403           0 :                 dfNoData = NC_FILL_INT;
   11404          26 :             break;
   11405             :         }
   11406          71 :         case NC_FLOAT:
   11407             :         {
   11408          71 :             float fFillVal = 0;
   11409          71 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) ==
   11410             :                 NC_NOERR)
   11411             :             {
   11412          71 :                 if (!nNoFill)
   11413             :                 {
   11414          67 :                     bGotNoData = true;
   11415          67 :                     dfNoData = fFillVal;
   11416             :                 }
   11417             :             }
   11418             :             else
   11419           0 :                 dfNoData = NC_FILL_FLOAT;
   11420          71 :             break;
   11421             :         }
   11422          34 :         case NC_DOUBLE:
   11423             :         {
   11424          34 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) ==
   11425             :                 NC_NOERR)
   11426             :             {
   11427          34 :                 if (!nNoFill)
   11428             :                 {
   11429          34 :                     bGotNoData = true;
   11430             :                 }
   11431             :             }
   11432             :             else
   11433           0 :                 dfNoData = NC_FILL_DOUBLE;
   11434          34 :             break;
   11435             :         }
   11436           7 :         case NC_USHORT:
   11437             :         {
   11438           7 :             unsigned short nFillVal = 0;
   11439           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11440             :                 NC_NOERR)
   11441             :             {
   11442           7 :                 if (!nNoFill)
   11443             :                 {
   11444           7 :                     bGotNoData = true;
   11445           7 :                     dfNoData = nFillVal;
   11446             :                 }
   11447             :             }
   11448             :             else
   11449           0 :                 dfNoData = NC_FILL_USHORT;
   11450           7 :             break;
   11451             :         }
   11452           7 :         case NC_UINT:
   11453             :         {
   11454           7 :             unsigned int nFillVal = 0;
   11455           7 :             if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) ==
   11456             :                 NC_NOERR)
   11457             :             {
   11458           7 :                 if (!nNoFill)
   11459             :                 {
   11460           7 :                     bGotNoData = true;
   11461           7 :                     dfNoData = nFillVal;
   11462             :                 }
   11463             :             }
   11464             :             else
   11465           0 :                 dfNoData = NC_FILL_UINT;
   11466           7 :             break;
   11467             :         }
   11468          19 :         default:
   11469          19 :             dfNoData = 0.0;
   11470          19 :             break;
   11471             :     }
   11472             : 
   11473         188 :     return dfNoData;
   11474             : }
   11475             : 
   11476             : /************************************************************************/
   11477             : /*                      NCDFGetDefaultNoDataValueAsInt64()              */
   11478             : /************************************************************************/
   11479             : 
   11480           2 : int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId,
   11481             :                                          bool &bGotNoData)
   11482             : 
   11483             : {
   11484           2 :     int nNoFill = 0;
   11485           2 :     long long nFillVal = 0;
   11486           2 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11487             :     {
   11488           2 :         if (!nNoFill)
   11489             :         {
   11490           2 :             bGotNoData = true;
   11491           2 :             return static_cast<int64_t>(nFillVal);
   11492             :         }
   11493             :     }
   11494             :     else
   11495           0 :         return static_cast<int64_t>(NC_FILL_INT64);
   11496           0 :     return 0;
   11497             : }
   11498             : 
   11499             : /************************************************************************/
   11500             : /*                     NCDFGetDefaultNoDataValueAsUInt64()              */
   11501             : /************************************************************************/
   11502             : 
   11503           1 : uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId,
   11504             :                                            bool &bGotNoData)
   11505             : 
   11506             : {
   11507           1 :     int nNoFill = 0;
   11508           1 :     unsigned long long nFillVal = 0;
   11509           1 :     if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR)
   11510             :     {
   11511           1 :         if (!nNoFill)
   11512             :         {
   11513           1 :             bGotNoData = true;
   11514           1 :             return static_cast<uint64_t>(nFillVal);
   11515             :         }
   11516             :     }
   11517             :     else
   11518           0 :         return static_cast<uint64_t>(NC_FILL_UINT64);
   11519           0 :     return 0;
   11520             : }
   11521             : 
   11522       11027 : static int NCDFDoesVarContainAttribVal(int nCdfId,
   11523             :                                        const char *const *papszAttribNames,
   11524             :                                        const char *const *papszAttribValues,
   11525             :                                        int nVarId, const char *pszVarName,
   11526             :                                        bool bStrict = true)
   11527             : {
   11528       11027 :     if (nVarId == -1 && pszVarName != nullptr)
   11529        8077 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11530             : 
   11531       11027 :     if (nVarId == -1)
   11532         878 :         return -1;
   11533             : 
   11534       10149 :     bool bFound = false;
   11535       47371 :     for (int i = 0; !bFound && papszAttribNames != nullptr &&
   11536       45127 :                     papszAttribNames[i] != nullptr;
   11537             :          i++)
   11538             :     {
   11539       37222 :         char *pszTemp = nullptr;
   11540       37222 :         if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) ==
   11541       53459 :                 CE_None &&
   11542       16237 :             pszTemp != nullptr)
   11543             :         {
   11544       16237 :             if (bStrict)
   11545             :             {
   11546       16237 :                 if (EQUAL(pszTemp, papszAttribValues[i]))
   11547        2244 :                     bFound = true;
   11548             :             }
   11549             :             else
   11550             :             {
   11551           0 :                 if (EQUALN(pszTemp, papszAttribValues[i],
   11552             :                            strlen(papszAttribValues[i])))
   11553           0 :                     bFound = true;
   11554             :             }
   11555       16237 :             CPLFree(pszTemp);
   11556             :         }
   11557             :     }
   11558       10149 :     return bFound;
   11559             : }
   11560             : 
   11561        1952 : static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName,
   11562             :                                         const char *const *papszAttribValues,
   11563             :                                         int nVarId, const char *pszVarName,
   11564             :                                         int bStrict = true)
   11565             : {
   11566        1952 :     if (nVarId == -1 && pszVarName != nullptr)
   11567        1569 :         NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId);
   11568             : 
   11569        1952 :     if (nVarId == -1)
   11570           0 :         return -1;
   11571             : 
   11572        1952 :     bool bFound = false;
   11573        1952 :     char *pszTemp = nullptr;
   11574        2331 :     if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None ||
   11575         379 :         pszTemp == nullptr)
   11576        1573 :         return FALSE;
   11577             : 
   11578        7703 :     for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++)
   11579             :     {
   11580        7324 :         if (bStrict)
   11581             :         {
   11582        7296 :             if (EQUAL(pszTemp, papszAttribValues[i]))
   11583          31 :                 bFound = true;
   11584             :         }
   11585             :         else
   11586             :         {
   11587          28 :             if (EQUALN(pszTemp, papszAttribValues[i],
   11588             :                        strlen(papszAttribValues[i])))
   11589           0 :                 bFound = true;
   11590             :         }
   11591             :     }
   11592             : 
   11593         379 :     CPLFree(pszTemp);
   11594             : 
   11595         379 :     return bFound;
   11596             : }
   11597             : 
   11598         876 : static bool NCDFEqual(const char *papszName, const char *const *papszValues)
   11599             : {
   11600         876 :     if (papszName == nullptr || EQUAL(papszName, ""))
   11601           0 :         return false;
   11602             : 
   11603        2392 :     for (int i = 0; papszValues && papszValues[i]; ++i)
   11604             :     {
   11605        1636 :         if (EQUAL(papszName, papszValues[i]))
   11606         120 :             return true;
   11607             :     }
   11608             : 
   11609         756 :     return false;
   11610             : }
   11611             : 
   11612             : // Test that a variable is longitude/latitude coordinate,
   11613             : // following CF 4.1 and 4.2.
   11614        3759 : bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName)
   11615             : {
   11616             :     // Check for matching attributes.
   11617        3759 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames,
   11618             :                                            papszCFLongitudeAttribValues, nVarId,
   11619             :                                            pszVarName);
   11620             :     // If not found using attributes then check using var name
   11621             :     // unless GDAL_NETCDF_VERIFY_DIMS=STRICT.
   11622        3759 :     if (bVal == -1)
   11623             :     {
   11624         280 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11625             :                    "STRICT"))
   11626         280 :             bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames);
   11627             :         else
   11628           0 :             bVal = FALSE;
   11629             :     }
   11630        3479 :     else if (bVal)
   11631             :     {
   11632             :         // Check that the units is not 'm' or '1'. See #6759
   11633         784 :         char *pszTemp = nullptr;
   11634        1161 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11635         377 :             pszTemp != nullptr)
   11636             :         {
   11637         377 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11638          97 :                 bVal = false;
   11639         377 :             CPLFree(pszTemp);
   11640             :         }
   11641             :     }
   11642             : 
   11643        3759 :     return CPL_TO_BOOL(bVal);
   11644             : }
   11645             : 
   11646        2155 : bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName)
   11647             : {
   11648        2155 :     int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames,
   11649             :                                            papszCFLatitudeAttribValues, nVarId,
   11650             :                                            pszVarName);
   11651        2155 :     if (bVal == -1)
   11652             :     {
   11653         163 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11654             :                    "STRICT"))
   11655         163 :             bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames);
   11656             :         else
   11657           0 :             bVal = FALSE;
   11658             :     }
   11659        1992 :     else if (bVal)
   11660             :     {
   11661             :         // Check that the units is not 'm' or '1'. See #6759
   11662         540 :         char *pszTemp = nullptr;
   11663         681 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11664         141 :             pszTemp != nullptr)
   11665             :         {
   11666         141 :             if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1"))
   11667          36 :                 bVal = false;
   11668         141 :             CPLFree(pszTemp);
   11669             :         }
   11670             :     }
   11671             : 
   11672        2155 :     return CPL_TO_BOOL(bVal);
   11673             : }
   11674             : 
   11675        2280 : bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName)
   11676             : {
   11677        2280 :     int bVal = NCDFDoesVarContainAttribVal(
   11678             :         nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues,
   11679             :         nVarId, pszVarName);
   11680        2280 :     if (bVal == -1)
   11681             :     {
   11682         274 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11683             :                    "STRICT"))
   11684         274 :             bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames);
   11685             :         else
   11686           0 :             bVal = FALSE;
   11687             :     }
   11688        2006 :     else if (bVal)
   11689             :     {
   11690             :         // Check that the units is not '1'
   11691         371 :         char *pszTemp = nullptr;
   11692         529 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11693         158 :             pszTemp != nullptr)
   11694             :         {
   11695         158 :             if (EQUAL(pszTemp, "1"))
   11696           5 :                 bVal = false;
   11697         158 :             CPLFree(pszTemp);
   11698             :         }
   11699             :     }
   11700             : 
   11701        2280 :     return CPL_TO_BOOL(bVal);
   11702             : }
   11703             : 
   11704        1614 : bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName)
   11705             : {
   11706        1614 :     int bVal = NCDFDoesVarContainAttribVal(
   11707             :         nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues,
   11708             :         nVarId, pszVarName);
   11709        1614 :     if (bVal == -1)
   11710             :     {
   11711         159 :         if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"),
   11712             :                    "STRICT"))
   11713         159 :             bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames);
   11714             :         else
   11715           0 :             bVal = FALSE;
   11716             :     }
   11717        1455 :     else if (bVal)
   11718             :     {
   11719             :         // Check that the units is not '1'
   11720         370 :         char *pszTemp = nullptr;
   11721         527 :         if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None &&
   11722         157 :             pszTemp != nullptr)
   11723             :         {
   11724         157 :             if (EQUAL(pszTemp, "1"))
   11725           5 :                 bVal = false;
   11726         157 :             CPLFree(pszTemp);
   11727             :         }
   11728             :     }
   11729             : 
   11730        1614 :     return CPL_TO_BOOL(bVal);
   11731             : }
   11732             : 
   11733             : /* test that a variable is a vertical coordinate, following CF 4.3 */
   11734        1017 : bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName)
   11735             : {
   11736             :     /* check for matching attributes */
   11737        1017 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames,
   11738             :                                     papszCFVerticalAttribValues, nVarId,
   11739        1017 :                                     pszVarName))
   11740          72 :         return true;
   11741             :     /* check for matching units */
   11742         945 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11743             :                                           papszCFVerticalUnitsValues, nVarId,
   11744         945 :                                           pszVarName))
   11745          31 :         return true;
   11746             :     /* check for matching standard name */
   11747         914 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME,
   11748             :                                           papszCFVerticalStandardNameValues,
   11749         914 :                                           nVarId, pszVarName))
   11750           0 :         return true;
   11751             :     else
   11752         914 :         return false;
   11753             : }
   11754             : 
   11755             : /* test that a variable is a time coordinate, following CF 4.4 */
   11756         202 : bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName)
   11757             : {
   11758             :     /* check for matching attributes */
   11759         202 :     if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames,
   11760             :                                     papszCFTimeAttribValues, nVarId,
   11761         202 :                                     pszVarName))
   11762         109 :         return true;
   11763             :     /* check for matching units */
   11764          93 :     else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS,
   11765             :                                           papszCFTimeUnitsValues, nVarId,
   11766          93 :                                           pszVarName, false))
   11767           0 :         return true;
   11768             :     else
   11769          93 :         return false;
   11770             : }
   11771             : 
   11772             : // Parse a string, and return as a string list.
   11773             : // If it an array of the form {a,b}, then tokenize it.
   11774             : // Otherwise, return a copy.
   11775         187 : static char **NCDFTokenizeArray(const char *pszValue)
   11776             : {
   11777         187 :     if (pszValue == nullptr || EQUAL(pszValue, ""))
   11778          52 :         return nullptr;
   11779             : 
   11780         135 :     char **papszValues = nullptr;
   11781         135 :     const int nLen = static_cast<int>(strlen(pszValue));
   11782             : 
   11783         135 :     if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}')
   11784             :     {
   11785          41 :         char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1));
   11786          41 :         strncpy(pszTemp, pszValue + 1, nLen - 2);
   11787          41 :         pszTemp[nLen - 2] = '\0';
   11788          41 :         papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS);
   11789          41 :         CPLFree(pszTemp);
   11790             :     }
   11791             :     else
   11792             :     {
   11793          94 :         papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *)));
   11794          94 :         papszValues[0] = CPLStrdup(pszValue);
   11795          94 :         papszValues[1] = nullptr;
   11796             :     }
   11797             : 
   11798         135 :     return papszValues;
   11799             : }
   11800             : 
   11801             : // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var.
   11802             : // Leading slash is optional.
   11803         408 : static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName,
   11804             :                                  int *pnGroupId, int *pnVarId)
   11805             : {
   11806         408 :     *pnGroupId = -1;
   11807         408 :     *pnVarId = -1;
   11808             : 
   11809             :     // Open group.
   11810             :     char *pszGroupFullName =
   11811         408 :         CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str());
   11812             :     // Add a leading slash if needed.
   11813         408 :     if (pszGroupFullName[0] != '/')
   11814             :     {
   11815         392 :         char *old = pszGroupFullName;
   11816         392 :         pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName));
   11817         392 :         CPLFree(old);
   11818             :     }
   11819             :     // Detect root group.
   11820         408 :     if (EQUAL(pszGroupFullName, "/"))
   11821             :     {
   11822         392 :         *pnGroupId = nCdfId;
   11823         392 :         CPLFree(pszGroupFullName);
   11824             :     }
   11825             :     else
   11826             :     {
   11827          16 :         int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId);
   11828          16 :         CPLFree(pszGroupFullName);
   11829          16 :         NCDF_ERR_RET(status);
   11830             :     }
   11831             : 
   11832             :     // Open var.
   11833         408 :     const char *pszVarName = CPLGetFilename(pszSubdatasetName);
   11834         408 :     NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId));
   11835             : 
   11836         408 :     return CE_None;
   11837             : }
   11838             : 
   11839             : // Get all dimensions visible from a given NetCDF (or group) ID and any of
   11840             : // its parents.
   11841         349 : static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds)
   11842             : {
   11843         349 :     int nDims = 0;
   11844         349 :     int *panDimIds = nullptr;
   11845         349 :     NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true));
   11846             : 
   11847         349 :     panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int)));
   11848             : 
   11849         349 :     int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true);
   11850         349 :     if (status != NC_NOERR)
   11851           0 :         CPLFree(panDimIds);
   11852         349 :     NCDF_ERR_RET(status);
   11853             : 
   11854         349 :     *pnDims = nDims;
   11855         349 :     *ppanDimIds = panDimIds;
   11856             : 
   11857         349 :     return CE_None;
   11858             : }
   11859             : 
   11860             : // Get direct sub-groups IDs of a given NetCDF (or group) ID.
   11861             : // Consider only direct children, does not get children of children.
   11862        3039 : static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups,
   11863             :                                int **ppanSubGroupIds)
   11864             : {
   11865        3039 :     *pnSubGroups = 0;
   11866        3039 :     *ppanSubGroupIds = nullptr;
   11867             : 
   11868             :     int nSubGroups;
   11869        3039 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr));
   11870             :     int *panSubGroupIds =
   11871        3039 :         static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int)));
   11872        3039 :     NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds));
   11873        3039 :     *pnSubGroups = nSubGroups;
   11874        3039 :     *ppanSubGroupIds = panSubGroupIds;
   11875             : 
   11876        3039 :     return CE_None;
   11877             : }
   11878             : 
   11879             : // Get the full name of a given NetCDF (or group) ID
   11880             : // (e.g. /group1/group2/.../groupn).
   11881             : // bNC3Compat remove the leading slash for top-level variables for
   11882             : // backward compatibility (top-level variables are the ones in the root group).
   11883       15405 : static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName,
   11884             :                                    bool bNC3Compat)
   11885             : {
   11886       15405 :     *ppszFullName = nullptr;
   11887             : 
   11888             :     size_t nFullNameLen;
   11889       15405 :     NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen));
   11890       15405 :     *ppszFullName =
   11891       15405 :         static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char)));
   11892       15405 :     int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName);
   11893       15405 :     if (status != NC_NOERR)
   11894             :     {
   11895           0 :         CPLFree(*ppszFullName);
   11896           0 :         *ppszFullName = nullptr;
   11897           0 :         NCDF_ERR_RET(status);
   11898             :     }
   11899             : 
   11900       15405 :     if (bNC3Compat && EQUAL(*ppszFullName, "/"))
   11901        7892 :         (*ppszFullName)[0] = '\0';
   11902             : 
   11903       15405 :     return CE_None;
   11904             : }
   11905             : 
   11906        7325 : CPLString NCDFGetGroupFullName(int nGroupId)
   11907             : {
   11908        7325 :     char *pszFullname = nullptr;
   11909        7325 :     NCDFGetGroupFullName(nGroupId, &pszFullname, false);
   11910        7325 :     CPLString osRet(pszFullname ? pszFullname : "");
   11911        7325 :     CPLFree(pszFullname);
   11912       14650 :     return osRet;
   11913             : }
   11914             : 
   11915             : // Get the full name of a given NetCDF variable ID
   11916             : // (e.g. /group1/group2/.../groupn/var).
   11917             : // Handle also NC_GLOBAL as nVarId.
   11918             : // bNC3Compat remove the leading slash for top-level variables for
   11919             : // backward compatibility (top-level variables are the ones in the root group).
   11920        8031 : static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName,
   11921             :                                  bool bNC3Compat)
   11922             : {
   11923        8031 :     *ppszFullName = nullptr;
   11924        8031 :     char *pszGroupFullName = nullptr;
   11925        8031 :     ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat));
   11926             :     char szVarName[NC_MAX_NAME + 1];
   11927        8031 :     if (nVarId == NC_GLOBAL)
   11928             :     {
   11929        1084 :         strcpy(szVarName, "NC_GLOBAL");
   11930             :     }
   11931             :     else
   11932             :     {
   11933        6947 :         int status = nc_inq_varname(nGroupId, nVarId, szVarName);
   11934        6947 :         if (status != NC_NOERR)
   11935             :         {
   11936           0 :             CPLFree(pszGroupFullName);
   11937           0 :             NCDF_ERR_RET(status);
   11938             :         }
   11939             :     }
   11940        8031 :     const char *pszSep = "/";
   11941        8031 :     if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, ""))
   11942        7845 :         pszSep = "";
   11943        8031 :     *ppszFullName =
   11944        8031 :         CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName));
   11945        8031 :     CPLFree(pszGroupFullName);
   11946        8031 :     return CE_None;
   11947             : }
   11948             : 
   11949             : // Get the NetCDF root group ID of a given group ID.
   11950           0 : static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId)
   11951             : {
   11952           0 :     *pnRootGroupId = -1;
   11953             :     // Recurse on parent group.
   11954             :     int nParentGroupId;
   11955           0 :     int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId);
   11956           0 :     if (status == NC_NOERR)
   11957           0 :         return NCDFGetRootGroup(nParentGroupId, pnRootGroupId);
   11958           0 :     else if (status != NC_ENOGRP)
   11959           0 :         NCDF_ERR_RET(status);
   11960             :     else  // No more parent group.
   11961             :     {
   11962           0 :         *pnRootGroupId = nStartGroupId;
   11963             :     }
   11964             : 
   11965           0 :     return CE_None;
   11966             : }
   11967             : 
   11968             : // Implementation of NCDFResolveVar/Att.
   11969       13121 : static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar,
   11970             :                               const char *pszAtt, int *pnGroupId, int *pnId,
   11971             :                               bool bMandatory)
   11972             : {
   11973       13121 :     if (!pszVar && !pszAtt)
   11974             :     {
   11975           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
   11976             :                  "pszVar and pszAtt NCDFResolveElem() args are both null.");
   11977           0 :         return CE_Failure;
   11978             :     }
   11979             : 
   11980             :     enum
   11981             :     {
   11982             :         NCRM_PARENT,
   11983             :         NCRM_WIDTH_WISE
   11984       13121 :     } eNCResolveMode = NCRM_PARENT;
   11985             : 
   11986       26242 :     std::queue<int> aoQueueGroupIdsToVisit;
   11987       13121 :     aoQueueGroupIdsToVisit.push(nStartGroupId);
   11988             : 
   11989       14788 :     while (!aoQueueGroupIdsToVisit.empty())
   11990             :     {
   11991             :         // Get the first group of the FIFO queue.
   11992       13268 :         *pnGroupId = aoQueueGroupIdsToVisit.front();
   11993       13268 :         aoQueueGroupIdsToVisit.pop();
   11994             : 
   11995             :         // Look if this group contains the searched element.
   11996             :         int status;
   11997       13268 :         if (pszVar)
   11998       13051 :             status = nc_inq_varid(*pnGroupId, pszVar, pnId);
   11999             :         else  // pszAtt != nullptr.
   12000         217 :             status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId);
   12001             : 
   12002       13268 :         if (status == NC_NOERR)
   12003             :         {
   12004       11601 :             return CE_None;
   12005             :         }
   12006        1667 :         else if ((pszVar && status != NC_ENOTVAR) ||
   12007         214 :                  (pszAtt && status != NC_ENOTATT))
   12008             :         {
   12009           0 :             NCDF_ERR(status);
   12010             :         }
   12011             :         // Element not found, in NC4 case we must search in other groups
   12012             :         // following the CF logic.
   12013             : 
   12014             :         // The first resolve mode consists to search on parent groups.
   12015        1667 :         if (eNCResolveMode == NCRM_PARENT)
   12016             :         {
   12017        1567 :             int nParentGroupId = -1;
   12018        1567 :             int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId);
   12019        1567 :             if (status2 == NC_NOERR)
   12020          45 :                 aoQueueGroupIdsToVisit.push(nParentGroupId);
   12021        1522 :             else if (status2 != NC_ENOGRP)
   12022           0 :                 NCDF_ERR(status2);
   12023        1522 :             else if (pszVar)
   12024             :                 // When resolving a variable, if there is no more
   12025             :                 // parent group then we switch to width-wise search mode
   12026             :                 // starting from the latest found parent group.
   12027        1311 :                 eNCResolveMode = NCRM_WIDTH_WISE;
   12028             :         }
   12029             : 
   12030             :         // The second resolve mode is a width-wise search.
   12031        1667 :         if (eNCResolveMode == NCRM_WIDTH_WISE)
   12032             :         {
   12033             :             // Enqueue all direct sub-groups.
   12034        1411 :             int nSubGroups = 0;
   12035        1411 :             int *panSubGroupIds = nullptr;
   12036        1411 :             NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds);
   12037        1513 :             for (int i = 0; i < nSubGroups; i++)
   12038         102 :                 aoQueueGroupIdsToVisit.push(panSubGroupIds[i]);
   12039        1411 :             CPLFree(panSubGroupIds);
   12040             :         }
   12041             :     }
   12042             : 
   12043        1520 :     if (bMandatory)
   12044             :     {
   12045           0 :         char *pszStartGroupFullName = nullptr;
   12046           0 :         NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName);
   12047           0 :         CPLError(CE_Failure, CPLE_AppDefined,
   12048             :                  "Cannot resolve mandatory %s %s from group %s",
   12049             :                  (pszVar ? pszVar : pszAtt),
   12050             :                  (pszVar ? "variable" : "attribute"),
   12051           0 :                  (pszStartGroupFullName ? pszStartGroupFullName : ""));
   12052           0 :         CPLFree(pszStartGroupFullName);
   12053             :     }
   12054             : 
   12055        1520 :     *pnGroupId = -1;
   12056        1520 :     *pnId = -1;
   12057        1520 :     return CE_Failure;
   12058             : }
   12059             : 
   12060             : // Resolve a variable name from a given starting group following the CF logic:
   12061             : // - if var name is an absolute path then directly open it
   12062             : // - first search in the starting group and its parent groups
   12063             : // - then if there is no more parent group we switch to a width-wise search
   12064             : //   mode starting from the latest found parent group.
   12065             : // The full CF logic is described here:
   12066             : // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope
   12067             : // If bMandatory then print an error if resolving fails.
   12068             : // TODO: implement support of relative paths.
   12069             : // TODO: to follow strictly the CF logic, when searching for a coordinate
   12070             : //       variable, we must stop the parent search mode once the corresponding
   12071             : //       dimension is found and start the width-wise search from this group.
   12072             : // TODO: to follow strictly the CF logic, when searching in width-wise mode
   12073             : //       we should skip every groups already visited during the parent
   12074             : //       search mode (but revisiting them should have no impact so we could
   12075             : //       let as it is if it is simpler...)
   12076             : // TODO: CF specifies that the width-wise search order is "left-to-right" so
   12077             : //       maybe we must sort sibling groups alphabetically? but maybe not
   12078             : //       necessary if nc_inq_grps() already sort them?
   12079       12907 : CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId,
   12080             :                       int *pnVarId, bool bMandatory)
   12081             : {
   12082       12907 :     *pnGroupId = -1;
   12083       12907 :     *pnVarId = -1;
   12084       12907 :     int nGroupId = nStartGroupId, nVarId;
   12085       12907 :     if (pszVar[0] == '/')
   12086             :     {
   12087             :         // This is an absolute path: we can open the var directly.
   12088             :         int nRootGroupId;
   12089           0 :         ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId));
   12090           0 :         ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId));
   12091             :     }
   12092             :     else
   12093             :     {
   12094             :         // We have to search the variable following the CF logic.
   12095       12907 :         ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId,
   12096             :                                 &nVarId, bMandatory));
   12097             :     }
   12098       11598 :     *pnGroupId = nGroupId;
   12099       11598 :     *pnVarId = nVarId;
   12100       11598 :     return CE_None;
   12101             : }
   12102             : 
   12103             : // Like NCDFResolveVar but returns directly the var full name.
   12104        1380 : static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar,
   12105             :                                      char **ppszFullName, bool bMandatory)
   12106             : {
   12107        1380 :     *ppszFullName = nullptr;
   12108             :     int nGroupId, nVarId;
   12109        1380 :     ERR_RET(
   12110             :         NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory));
   12111        1354 :     return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName);
   12112             : }
   12113             : 
   12114             : // Like NCDFResolveVar but resolves an attribute instead a variable and
   12115             : // returns its integer value.
   12116             : // Only GLOBAL attributes are supported for the moment.
   12117         214 : static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId,
   12118             :                                 const char *pszAtt, int *pnAtt, bool bMandatory)
   12119             : {
   12120         214 :     int nGroupId = nStartGroupId, nAttId = nStartVarId;
   12121         214 :     ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId,
   12122             :                             bMandatory));
   12123           3 :     NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt));
   12124           3 :     return CE_None;
   12125             : }
   12126             : 
   12127             : // Filter variables to keep only valid 2+D raster bands and vector fields in
   12128             : // a given a NetCDF (or group) ID and its sub-groups.
   12129             : // Coordinate or boundary variables are ignored.
   12130             : // It also creates corresponding vector layers.
   12131         521 : CPLErr netCDFDataset::FilterVars(
   12132             :     int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars,
   12133             :     int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars,
   12134             :     std::map<std::array<int, 3>, std::vector<std::pair<int, int>>>
   12135             :         &oMap2DDimsToGroupAndVar)
   12136             : {
   12137         521 :     int nVars = 0;
   12138         521 :     int nRasterVars = 0;
   12139         521 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12140             : 
   12141        1042 :     std::vector<int> anPotentialVectorVarID;
   12142             :     // oMapDimIdToCount[x] = number of times dim x is the first dimension of
   12143             :     // potential vector variables
   12144        1042 :     std::map<int, int> oMapDimIdToCount;
   12145         521 :     int nVarXId = -1;
   12146         521 :     int nVarYId = -1;
   12147         521 :     int nVarZId = -1;
   12148         521 :     int nVarTimeId = -1;
   12149         521 :     int nVarTimeDimId = -1;
   12150         521 :     bool bIsVectorOnly = true;
   12151         521 :     int nProfileDimId = -1;
   12152         521 :     int nParentIndexVarID = -1;
   12153             : 
   12154        3177 :     for (int v = 0; v < nVars; v++)
   12155             :     {
   12156             :         int nVarDims;
   12157        2656 :         NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims));
   12158             :         // Should we ignore this variable?
   12159             :         char szTemp[NC_MAX_NAME + 1];
   12160        2656 :         szTemp[0] = '\0';
   12161        2656 :         NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp));
   12162             : 
   12163        2656 :         if (strstr(szTemp, "_node_coordinates") ||
   12164        2656 :             strstr(szTemp, "_node_count"))
   12165             :         {
   12166             :             // Ignore CF-1.8 Simple Geometries helper variables
   12167          69 :             continue;
   12168             :         }
   12169             : 
   12170        3875 :         if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) ||
   12171        1288 :                               NCDFIsVarProjectionX(nCdfId, -1, szTemp)))
   12172             :         {
   12173         356 :             nVarXId = v;
   12174             :         }
   12175        3163 :         else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) ||
   12176         932 :                                    NCDFIsVarProjectionY(nCdfId, -1, szTemp)))
   12177             :         {
   12178         356 :             nVarYId = v;
   12179             :         }
   12180        1875 :         else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp))
   12181             :         {
   12182          78 :             nVarZId = v;
   12183             :         }
   12184             :         else
   12185             :         {
   12186        1797 :             char *pszVarFullName = nullptr;
   12187        1797 :             CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName);
   12188        1797 :             if (eErr != CE_None)
   12189             :             {
   12190           0 :                 CPLFree(pszVarFullName);
   12191           0 :                 continue;
   12192             :             }
   12193             :             bool bIgnoreVar =
   12194        1797 :                 (CSLFindString(papszIgnoreVars, pszVarFullName) != -1);
   12195        1797 :             CPLFree(pszVarFullName);
   12196        1797 :             if (bIgnoreVar)
   12197             :             {
   12198         102 :                 if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp))
   12199             :                 {
   12200          11 :                     nVarTimeId = v;
   12201          11 :                     nc_inq_vardimid(nCdfId, v, &nVarTimeDimId);
   12202             :                 }
   12203          91 :                 else if (nVarDims > 1)
   12204             :                 {
   12205          87 :                     (*pnIgnoredVars)++;
   12206          87 :                     CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v,
   12207             :                              szTemp);
   12208             :                 }
   12209             :             }
   12210             :             // Only accept 2+D vars.
   12211        1695 :             else if (nVarDims >= 2)
   12212             :             {
   12213         678 :                 bool bRasterCandidate = true;
   12214             :                 // Identify variables that might be vector variables
   12215         678 :                 if (nVarDims == 2)
   12216             :                 {
   12217         607 :                     int anDimIds[2] = {-1, -1};
   12218         607 :                     nc_inq_vardimid(nCdfId, v, anDimIds);
   12219             : 
   12220         607 :                     nc_type vartype = NC_NAT;
   12221         607 :                     nc_inq_vartype(nCdfId, v, &vartype);
   12222             : 
   12223             :                     char szDimNameFirst[NC_MAX_NAME + 1];
   12224             :                     char szDimNameSecond[NC_MAX_NAME + 1];
   12225         607 :                     szDimNameFirst[0] = '\0';
   12226         607 :                     szDimNameSecond[0] = '\0';
   12227        1370 :                     if (vartype == NC_CHAR &&
   12228         156 :                         nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) ==
   12229         156 :                             NC_NOERR &&
   12230         156 :                         nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) ==
   12231         156 :                             NC_NOERR &&
   12232         156 :                         !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) &&
   12233         156 :                         !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) &&
   12234         919 :                         !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) &&
   12235         156 :                         !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst))
   12236             :                     {
   12237         156 :                         anPotentialVectorVarID.push_back(v);
   12238         156 :                         oMapDimIdToCount[anDimIds[0]]++;
   12239         156 :                         if (strstr(szDimNameSecond, "_max_width"))
   12240             :                         {
   12241         127 :                             bRasterCandidate = false;
   12242             :                         }
   12243             :                         else
   12244             :                         {
   12245          29 :                             std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12246          29 :                                                     vartype};
   12247          29 :                             oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12248          29 :                                 std::pair(nCdfId, v));
   12249             :                         }
   12250             :                     }
   12251             :                     else
   12252             :                     {
   12253         451 :                         std::array<int, 3> oKey{anDimIds[0], anDimIds[1],
   12254         451 :                                                 vartype};
   12255         451 :                         oMap2DDimsToGroupAndVar[oKey].emplace_back(
   12256         451 :                             std::pair(nCdfId, v));
   12257         451 :                         bIsVectorOnly = false;
   12258             :                     }
   12259             :                 }
   12260             :                 else
   12261             :                 {
   12262          71 :                     bIsVectorOnly = false;
   12263             :                 }
   12264         678 :                 if (bKeepRasters && bRasterCandidate)
   12265             :                 {
   12266         522 :                     *pnGroupId = nCdfId;
   12267         522 :                     *pnVarId = v;
   12268         522 :                     nRasterVars++;
   12269             :                 }
   12270             :             }
   12271        1017 :             else if (nVarDims == 1)
   12272             :             {
   12273         725 :                 nc_type atttype = NC_NAT;
   12274         725 :                 size_t attlen = 0;
   12275         725 :                 if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype,
   12276          14 :                                &attlen) == NC_NOERR &&
   12277         725 :                     atttype == NC_CHAR && attlen < NC_MAX_NAME)
   12278             :                 {
   12279             :                     char szInstanceDimension[NC_MAX_NAME + 1];
   12280          14 :                     if (nc_get_att_text(nCdfId, v, "instance_dimension",
   12281          14 :                                         szInstanceDimension) == NC_NOERR)
   12282             :                     {
   12283          14 :                         szInstanceDimension[attlen] = 0;
   12284          14 :                         int status = nc_inq_dimid(nCdfId, szInstanceDimension,
   12285             :                                                   &nProfileDimId);
   12286          14 :                         if (status == NC_NOERR)
   12287          14 :                             nParentIndexVarID = v;
   12288             :                         else
   12289           0 :                             nProfileDimId = -1;
   12290          14 :                         if (status == NC_EBADDIM)
   12291           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
   12292             :                                      "Attribute instance_dimension='%s' refers "
   12293             :                                      "to a non existing dimension",
   12294             :                                      szInstanceDimension);
   12295             :                         else
   12296          14 :                             NCDF_ERR(status);
   12297             :                     }
   12298             :                 }
   12299         725 :                 if (v != nParentIndexVarID)
   12300             :                 {
   12301         711 :                     anPotentialVectorVarID.push_back(v);
   12302         711 :                     int nDimId = -1;
   12303         711 :                     nc_inq_vardimid(nCdfId, v, &nDimId);
   12304         711 :                     oMapDimIdToCount[nDimId]++;
   12305             :                 }
   12306             :             }
   12307             :         }
   12308             :     }
   12309             : 
   12310             :     // If we are opened in raster-only mode and that there are only 1D or 2D
   12311             :     // variables and that the 2D variables have no X/Y dim, and all
   12312             :     // variables refer to the same main dimension (or 2 dimensions for
   12313             :     // featureType=profile), then it is a pure vector dataset
   12314             :     CPLString osFeatureType(
   12315         521 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", ""));
   12316         410 :     if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 &&
   12317         931 :         !anPotentialVectorVarID.empty() &&
   12318           0 :         (oMapDimIdToCount.size() == 1 ||
   12319           0 :          (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 &&
   12320           0 :           nProfileDimId >= 0)))
   12321             :     {
   12322           0 :         anPotentialVectorVarID.resize(0);
   12323             :     }
   12324             :     else
   12325             :     {
   12326         521 :         *pnRasterVars += nRasterVars;
   12327             :     }
   12328             : 
   12329         521 :     if (!anPotentialVectorVarID.empty() && bKeepVectors)
   12330             :     {
   12331             :         // Take the dimension that is referenced the most times.
   12332          64 :         if (!(oMapDimIdToCount.size() == 1 ||
   12333          27 :               (EQUAL(osFeatureType, "profile") &&
   12334          26 :                oMapDimIdToCount.size() == 2 && nProfileDimId >= 0)))
   12335             :         {
   12336           1 :             CPLError(CE_Warning, CPLE_AppDefined,
   12337             :                      "The dataset has several variables that could be "
   12338             :                      "identified as vector fields, but not all share the same "
   12339             :                      "primary dimension. Consequently they will be ignored.");
   12340             :         }
   12341             :         else
   12342             :         {
   12343          50 :             if (nVarTimeId >= 0 &&
   12344          50 :                 oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end())
   12345             :             {
   12346           1 :                 anPotentialVectorVarID.push_back(nVarTimeId);
   12347             :             }
   12348          49 :             CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID,
   12349             :                                   oMapDimIdToCount, nVarXId, nVarYId, nVarZId,
   12350             :                                   nProfileDimId, nParentIndexVarID,
   12351             :                                   bKeepRasters);
   12352             :         }
   12353             :     }
   12354             : 
   12355             :     // Recurse on sub-groups.
   12356         521 :     int nSubGroups = 0;
   12357         521 :     int *panSubGroupIds = nullptr;
   12358         521 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12359         553 :     for (int i = 0; i < nSubGroups; i++)
   12360             :     {
   12361          32 :         FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors,
   12362             :                    papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId,
   12363             :                    pnIgnoredVars, oMap2DDimsToGroupAndVar);
   12364             :     }
   12365         521 :     CPLFree(panSubGroupIds);
   12366             : 
   12367         521 :     return CE_None;
   12368             : }
   12369             : 
   12370             : // Create vector layers from given potentially identified vector variables
   12371             : // resulting from the scanning of a NetCDF (or group) ID.
   12372          49 : CPLErr netCDFDataset::CreateGrpVectorLayers(
   12373             :     int nCdfId, const CPLString &osFeatureType,
   12374             :     const std::vector<int> &anPotentialVectorVarID,
   12375             :     const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId,
   12376             :     int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters)
   12377             : {
   12378          49 :     char *pszGroupName = nullptr;
   12379          49 :     NCDFGetGroupFullName(nCdfId, &pszGroupName);
   12380          49 :     if (pszGroupName == nullptr || pszGroupName[0] == '\0')
   12381             :     {
   12382          47 :         CPLFree(pszGroupName);
   12383          47 :         pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str());
   12384             :     }
   12385          49 :     OGRwkbGeometryType eGType = wkbUnknown;
   12386             :     CPLString osLayerName = CSLFetchNameValueDef(
   12387          98 :         papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName);
   12388          49 :     CPLFree(pszGroupName);
   12389          49 :     papszMetadata =
   12390          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr);
   12391             : 
   12392          49 :     if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile"))
   12393             :     {
   12394          33 :         papszMetadata =
   12395          33 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr);
   12396          33 :         eGType = wkbPoint;
   12397             :     }
   12398             : 
   12399             :     const char *pszLayerType =
   12400          49 :         CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type");
   12401          49 :     if (pszLayerType != nullptr)
   12402             :     {
   12403           9 :         eGType = OGRFromOGCGeomType(pszLayerType);
   12404           9 :         papszMetadata =
   12405           9 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr);
   12406             :     }
   12407             : 
   12408             :     CPLString osGeometryField =
   12409          98 :         CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", "");
   12410          49 :     papszMetadata =
   12411          49 :         CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr);
   12412             : 
   12413          49 :     int nFirstVarId = -1;
   12414          49 :     int nVectorDim = oMapDimIdToCount.rbegin()->first;
   12415          49 :     if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2)
   12416             :     {
   12417          13 :         if (nVectorDim == nProfileDimId)
   12418           0 :             nVectorDim = oMapDimIdToCount.begin()->first;
   12419             :     }
   12420             :     else
   12421             :     {
   12422          36 :         nProfileDimId = -1;
   12423             :     }
   12424          62 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12425             :     {
   12426          62 :         int anDimIds[2] = {-1, -1};
   12427          62 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12428          62 :         if (nVectorDim == anDimIds[0])
   12429             :         {
   12430          49 :             nFirstVarId = anPotentialVectorVarID[j];
   12431          49 :             break;
   12432             :         }
   12433             :     }
   12434             : 
   12435             :     // In case where coordinates are explicitly specified for one of the
   12436             :     // field/variable, use them in priority over the ones that might have been
   12437             :     // identified above.
   12438          49 :     char *pszCoordinates = nullptr;
   12439          49 :     if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) ==
   12440             :         CE_None)
   12441             :     {
   12442          34 :         char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates);
   12443          34 :         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
   12444             :              i++)
   12445             :         {
   12446           0 :             if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) ||
   12447           0 :                 NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i]))
   12448             :             {
   12449           0 :                 nVarXId = -1;
   12450           0 :                 CPL_IGNORE_RET_VAL(
   12451           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarXId));
   12452             :             }
   12453           0 :             else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) ||
   12454           0 :                      NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i]))
   12455             :             {
   12456           0 :                 nVarYId = -1;
   12457           0 :                 CPL_IGNORE_RET_VAL(
   12458           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarYId));
   12459             :             }
   12460           0 :             else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i]))
   12461             :             {
   12462           0 :                 nVarZId = -1;
   12463           0 :                 CPL_IGNORE_RET_VAL(
   12464           0 :                     nc_inq_varid(nCdfId, papszTokens[i], &nVarZId));
   12465             :             }
   12466             :         }
   12467          34 :         CSLDestroy(papszTokens);
   12468             :     }
   12469          49 :     CPLFree(pszCoordinates);
   12470             : 
   12471             :     // Check that the X,Y,Z vars share 1D and share the same dimension as
   12472             :     // attribute variables.
   12473          49 :     if (nVarXId >= 0 && nVarYId >= 0)
   12474             :     {
   12475          38 :         int nVarDimCount = -1;
   12476          38 :         int nVarDimId = -1;
   12477          38 :         if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR ||
   12478          38 :             nVarDimCount != 1 ||
   12479          38 :             nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR ||
   12480          38 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) ||
   12481          35 :             nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR ||
   12482          35 :             nVarDimCount != 1 ||
   12483         111 :             nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR ||
   12484          35 :             nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim))
   12485             :         {
   12486           3 :             nVarXId = nVarYId = -1;
   12487             :         }
   12488          69 :         else if (nVarZId >= 0 &&
   12489          34 :                  (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR ||
   12490          34 :                   nVarDimCount != 1 ||
   12491          34 :                   nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR ||
   12492          34 :                   nVarDimId != nVectorDim))
   12493             :         {
   12494           0 :             nVarZId = -1;
   12495             :         }
   12496             :     }
   12497             : 
   12498          49 :     if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0)
   12499             :     {
   12500           2 :         eGType = wkbPoint;
   12501             :     }
   12502          49 :     if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0)
   12503             :     {
   12504          34 :         eGType = wkbPoint25D;
   12505             :     }
   12506          49 :     if (eGType == wkbUnknown && osGeometryField.empty())
   12507             :     {
   12508           5 :         eGType = wkbNone;
   12509             :     }
   12510             : 
   12511             :     // Read projection info
   12512          49 :     char **papszMetadataBackup = CSLDuplicate(papszMetadata);
   12513          49 :     ReadAttributes(nCdfId, nFirstVarId);
   12514          49 :     if (!this->bSGSupport)
   12515          49 :         SetProjectionFromVar(nCdfId, nFirstVarId, true);
   12516          49 :     const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING);
   12517          49 :     char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr);
   12518          49 :     CSLDestroy(papszMetadata);
   12519          49 :     papszMetadata = papszMetadataBackup;
   12520             : 
   12521          49 :     OGRSpatialReference *poSRS = nullptr;
   12522          49 :     if (!m_oSRS.IsEmpty())
   12523             :     {
   12524          21 :         poSRS = m_oSRS.Clone();
   12525             :     }
   12526             :     // Reset if there's a 2D raster
   12527          49 :     m_bHasProjection = false;
   12528          49 :     m_bHasGeoTransform = false;
   12529             : 
   12530          49 :     if (!bKeepRasters)
   12531             :     {
   12532             :         // Strip out uninteresting metadata.
   12533          45 :         papszMetadata =
   12534          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr);
   12535          45 :         papszMetadata =
   12536          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr);
   12537          45 :         papszMetadata =
   12538          45 :             CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr);
   12539             :     }
   12540             : 
   12541             :     std::shared_ptr<netCDFLayer> poLayer(
   12542          49 :         new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS));
   12543          49 :     if (poSRS != nullptr)
   12544          21 :         poSRS->Release();
   12545          49 :     poLayer->SetRecordDimID(nVectorDim);
   12546          49 :     if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0)
   12547             :     {
   12548          35 :         poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId);
   12549             :     }
   12550          14 :     else if (!osGeometryField.empty())
   12551             :     {
   12552           9 :         poLayer->SetWKTGeometryField(osGeometryField);
   12553             :     }
   12554          49 :     if (pszGridMapping != nullptr)
   12555             :     {
   12556          21 :         poLayer->SetGridMapping(pszGridMapping);
   12557          21 :         CPLFree(pszGridMapping);
   12558             :     }
   12559          49 :     poLayer->SetProfile(nProfileDimId, nParentIndexVarID);
   12560             : 
   12561         574 :     for (size_t j = 0; j < anPotentialVectorVarID.size(); j++)
   12562             :     {
   12563         525 :         int anDimIds[2] = {-1, -1};
   12564         525 :         nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds);
   12565         525 :         if (anDimIds[0] == nVectorDim ||
   12566          24 :             (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId))
   12567             :         {
   12568             : #ifdef NCDF_DEBUG
   12569             :             char szTemp2[NC_MAX_NAME + 1] = {};
   12570             :             CPL_IGNORE_RET_VAL(
   12571             :                 nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2));
   12572             :             CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2);
   12573             : #endif
   12574         525 :             poLayer->AddField(anPotentialVectorVarID[j]);
   12575             :         }
   12576             :     }
   12577             : 
   12578          49 :     if (poLayer->GetLayerDefn()->GetFieldCount() != 0 ||
   12579           0 :         poLayer->GetGeomType() != wkbNone)
   12580             :     {
   12581          49 :         papoLayers.push_back(poLayer);
   12582             :     }
   12583             : 
   12584          98 :     return CE_None;
   12585             : }
   12586             : 
   12587             : // Get all coordinate and boundary variables full names referenced in
   12588             : // a given a NetCDF (or group) ID and its sub-groups.
   12589             : // These variables are identified in other variable's
   12590             : // "coordinates" and "bounds" attribute.
   12591             : // Searching coordinate and boundary variables may need to explore
   12592             : // parents groups (or other groups in case of reference given in form of an
   12593             : // absolute path).
   12594             : // See CF sections 5.2, 5.6 and 7.1
   12595         522 : static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars)
   12596             : {
   12597         522 :     int nVars = 0;
   12598         522 :     NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr));
   12599             : 
   12600        3192 :     for (int v = 0; v < nVars; v++)
   12601             :     {
   12602        2670 :         char *pszTemp = nullptr;
   12603        2670 :         char **papszTokens = nullptr;
   12604        2670 :         if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None)
   12605         445 :             papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp);
   12606        2670 :         CPLFree(pszTemp);
   12607        2670 :         pszTemp = nullptr;
   12608        2670 :         if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None &&
   12609        2670 :             pszTemp != nullptr && !EQUAL(pszTemp, ""))
   12610          17 :             papszTokens = CSLAddString(papszTokens, pszTemp);
   12611        2670 :         CPLFree(pszTemp);
   12612        3956 :         for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr;
   12613             :              i++)
   12614             :         {
   12615        1286 :             char *pszVarFullName = nullptr;
   12616        1286 :             if (NCDFResolveVarFullName(nCdfId, papszTokens[i],
   12617        1286 :                                        &pszVarFullName) == CE_None)
   12618        1260 :                 *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName);
   12619        1286 :             CPLFree(pszVarFullName);
   12620             :         }
   12621        2670 :         CSLDestroy(papszTokens);
   12622             :     }
   12623             : 
   12624             :     // Recurse on sub-groups.
   12625             :     int nSubGroups;
   12626         522 :     int *panSubGroupIds = nullptr;
   12627         522 :     NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds);
   12628         554 :     for (int i = 0; i < nSubGroups; i++)
   12629             :     {
   12630          32 :         NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars);
   12631             :     }
   12632         522 :     CPLFree(panSubGroupIds);
   12633             : 
   12634         522 :     return CE_None;
   12635             : }
   12636             : 
   12637             : // Check if give type is user defined
   12638         918 : bool NCDFIsUserDefinedType(int /*ncid*/, int type)
   12639             : {
   12640         918 :     return type >= NC_FIRSTUSERTYPEID;
   12641             : }
   12642             : 
   12643         558 : char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates)
   12644             : {
   12645             :     // CF conventions use space as the separator for variable names in the
   12646             :     // coordinates attribute, but some products such as
   12647             :     // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description
   12648             :     // use comma.
   12649         558 :     return CSLTokenizeString2(pszCoordinates, ", ", 0);
   12650             : }

Generated by: LCOV version 1.14